001 /*
002 * JBoss DNA (http://www.jboss.org/dna)
003 * See the COPYRIGHT.txt file distributed with this work for information
004 * regarding copyright ownership. Some portions may be licensed
005 * to Red Hat, Inc. under one or more contributor license agreements.
006 * See the AUTHORS.txt file in the distribution for a full listing of
007 * individual contributors.
008 *
009 * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010 * is licensed to you under the terms of the GNU Lesser General Public License as
011 * published by the Free Software Foundation; either version 2.1 of
012 * the License, or (at your option) any later version.
013 *
014 * JBoss DNA is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017 * Lesser General Public License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this software; if not, write to the Free
021 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023 */
024 package org.jboss.dna.connector.federation.merge.strategy;
025
026 import java.util.HashMap;
027 import java.util.Iterator;
028 import java.util.List;
029 import java.util.Map;
030 import java.util.NoSuchElementException;
031 import java.util.UUID;
032 import net.jcip.annotations.ThreadSafe;
033 import org.jboss.dna.connector.federation.contribution.Contribution;
034 import org.jboss.dna.connector.federation.merge.FederatedNode;
035 import org.jboss.dna.connector.federation.merge.MergePlan;
036 import org.jboss.dna.graph.DnaLexicon;
037 import org.jboss.dna.graph.ExecutionContext;
038 import org.jboss.dna.graph.Location;
039 import org.jboss.dna.graph.property.Name;
040 import org.jboss.dna.graph.property.Path;
041 import org.jboss.dna.graph.property.PathFactory;
042 import org.jboss.dna.graph.property.Property;
043 import org.jboss.dna.graph.property.PropertyFactory;
044 import org.jboss.dna.graph.property.ValueComparators;
045
046 /**
047 * This merge strategy simply merges all of the contributions' properties and combines the children according to the order of the
048 * contributions. No children are merged, and all properties are used (except if they are deemed to be duplicates of the property
049 * in other contributions).
050 *
051 * @author Randall Hauch
052 */
053 @ThreadSafe
054 public class SimpleMergeStrategy implements MergeStrategy {
055
056 private boolean removeDuplicateProperties = true;
057
058 /**
059 * @return removeDuplicateProperties
060 */
061 public boolean isRemoveDuplicateProperties() {
062 return removeDuplicateProperties;
063 }
064
065 /**
066 * @param removeDuplicateProperties Sets removeDuplicateProperties to the specified value.
067 */
068 public void setRemoveDuplicateProperties( boolean removeDuplicateProperties ) {
069 this.removeDuplicateProperties = removeDuplicateProperties;
070 }
071
072 /**
073 * {@inheritDoc}
074 *
075 * @see org.jboss.dna.connector.federation.merge.strategy.MergeStrategy#merge(org.jboss.dna.connector.federation.merge.FederatedNode,
076 * java.util.List, org.jboss.dna.graph.ExecutionContext)
077 */
078 public void merge( FederatedNode federatedNode,
079 List<Contribution> contributions,
080 ExecutionContext context ) {
081 assert federatedNode != null;
082 assert context != null;
083 assert contributions != null;
084 assert contributions.size() > 0;
085
086 final Location location = federatedNode.getActualLocationOfNode();
087 final boolean isRoot = location.hasPath() && location.getPath().isRoot();
088 final boolean removeDuplicateProperties = isRemoveDuplicateProperties();
089 final PathFactory pathFactory = context.getValueFactories().getPathFactory();
090 final Map<Name, Integer> childNames = new HashMap<Name, Integer>();
091 final Map<Name, Property> properties = federatedNode.getPropertiesByName();
092 properties.clear();
093
094 // Record the different ID properties from the federated node and (later) from each contribution
095 final Map<Name, Property> idProperties = new HashMap<Name, Property>();
096 for (Property idProperty : location) {
097 idProperties.put(idProperty.getName(), idProperty);
098 }
099
100 // Iterate over each of the contributions (in order) ...
101 for (Contribution contribution : contributions) {
102 if (contribution.isEmpty()) continue;
103 // If the contribution is a placeholder contribution, then the children should be merged into other children ...
104 if (contribution.isPlaceholder()) {
105 // Iterate over the children and add only if there is not already one ...
106 Iterator<Location> childIterator = contribution.getChildren();
107 while (childIterator.hasNext()) {
108 Location child = childIterator.next();
109 Name childName = child.getPath().getLastSegment().getName();
110 if (!childNames.containsKey(childName)) {
111 childNames.put(childName, 1);
112 Path pathToChild = pathFactory.create(location.getPath(), childName);
113 federatedNode.addChild(child.with(pathToChild));
114 }
115 }
116 } else {
117 // Get the identification properties for each contribution ...
118 Location contributionLocation = contribution.getLocationInSource();
119 for (Property idProperty : contributionLocation) {
120 // Record the property ...
121 Property existing = properties.put(idProperty.getName(), idProperty);
122 if (existing != null) {
123 // There's already an existing property, so we need to merge them ...
124 Property merged = merge(existing, idProperty, context.getPropertyFactory(), removeDuplicateProperties);
125 properties.put(merged.getName(), merged);
126 }
127 }
128
129 // Accumulate the children ...
130 Iterator<Location> childIterator = contribution.getChildren();
131 while (childIterator.hasNext()) {
132 Location child = childIterator.next();
133 Name childName = child.getPath().getLastSegment().getName();
134 int index = Path.DEFAULT_INDEX;
135 Integer previous = childNames.put(childName, 1);
136 if (previous != null) {
137 int previousValue = previous.intValue();
138 // Correct the index in the child name map ...
139 childNames.put(childName, ++previousValue);
140 index = previousValue;
141 }
142 Path pathToChild = pathFactory.create(location.getPath(), childName, index);
143 federatedNode.addChild(child.with(pathToChild)); // keep the same identifiers
144 }
145
146 // Add in the properties ...
147 Iterator<Property> propertyIter = contribution.getProperties();
148 while (propertyIter.hasNext()) {
149 Property property = propertyIter.next();
150 // Skip the "uuid" property on all root nodes ...
151 if (isRoot && property.getName().getLocalName().equals("uuid")) continue;
152
153 // Record the property ...
154 Property existing = properties.put(property.getName(), property);
155 if (existing != null && !existing.equals(property)) {
156 // There's already an existing property, so we need to merge them ...
157 Property merged = merge(existing, property, context.getPropertyFactory(), removeDuplicateProperties);
158 properties.put(property.getName(), merged);
159 }
160 }
161 }
162 }
163
164 if (idProperties.size() != 0) {
165 // Update the location based upon the merged ID properties ...
166 Location newLocation = location;
167 for (Property idProperty : idProperties.values()) {
168 newLocation = newLocation.with(idProperty);
169 }
170 federatedNode.setActualLocationOfNode(newLocation);
171 } else {
172 Location newLocation = location.with(UUID.randomUUID());
173 federatedNode.setActualLocationOfNode(newLocation);
174 }
175
176 // Look for the UUID property on the location, and update the federated node ...
177 Property uuidProperty = federatedNode.getActualLocationOfNode().getIdProperty(DnaLexicon.UUID);
178 assert uuidProperty != null;
179 properties.put(uuidProperty.getName(), uuidProperty);
180
181 // Assign the merge plan ...
182 MergePlan mergePlan = MergePlan.create(contributions);
183 federatedNode.setMergePlan(mergePlan);
184 Property mergePlanProperty = context.getPropertyFactory().create(DnaLexicon.MERGE_PLAN, (Object)mergePlan);
185 properties.put(mergePlanProperty.getName(), mergePlanProperty);
186 }
187
188 /**
189 * Merge the values from the two properties with the same name, returning a new property with the newly merged values.
190 * <p>
191 * The current algorithm merges the values by concatenating the values from <code>property1</code> and <code>property2</code>,
192 * and if <code>removeDuplicates</code> is true any values in <code>property2</code> that are identical to values found in
193 * <code>property1</code> are skipped.
194 * </p>
195 *
196 * @param property1 the first property; may not be null, and must have the same {@link Property#getName() name} as
197 * <code>property2</code>
198 * @param property2 the second property; may not be null, and must have the same {@link Property#getName() name} as
199 * <code>property1</code>
200 * @param factory the property factory, used to create the result
201 * @param removeDuplicates true if this method removes any values in the second property that duplicate values found in the
202 * first property.
203 * @return the property that contains the same {@link Property#getName() name} as the input properties, but with values that
204 * are merged from both of the input properties
205 */
206 protected Property merge( Property property1,
207 Property property2,
208 PropertyFactory factory,
209 boolean removeDuplicates ) {
210 assert property1 != null;
211 assert property2 != null;
212 assert property1.getName().equals(property2.getName());
213 if (property1.isEmpty()) return property2;
214 if (property2.isEmpty()) return property1;
215
216 // If they are both single-valued, then we can use a more efficient algorithm ...
217 if (property1.isSingle() && property2.isSingle()) {
218 Object value1 = property1.getValues().next();
219 Object value2 = property2.getValues().next();
220 if (removeDuplicates && ValueComparators.OBJECT_COMPARATOR.compare(value1, value2) == 0) return property1;
221 return factory.create(property1.getName(), new Object[] {value1, value2});
222 }
223
224 // One or both properties are multi-valued, so use an algorithm that works with in all cases ...
225 if (!removeDuplicates) {
226 Iterator<?> valueIterator = new DualIterator(property1.getValues(), property2.getValues());
227 return factory.create(property1.getName(), valueIterator);
228 }
229
230 // First copy all the values from property 1 ...
231 Object[] values = new Object[property1.size() + property2.size()];
232 int index = 0;
233 for (Object property1Value : property1) {
234 values[index++] = property1Value;
235 }
236 assert index == property1.size();
237 // Now add any values of property2 that don't match a value in property1 ...
238 for (Object property2Value : property2) {
239 // Brute force, go through the values of property1 and compare ...
240 boolean matched = false;
241 for (Object property1Value : property1) {
242 if (ValueComparators.OBJECT_COMPARATOR.compare(property1Value, property2Value) == 0) {
243 // The values are the same ...
244 matched = true;
245 break;
246 }
247 }
248 if (!matched) values[index++] = property2Value;
249 }
250 if (index != values.length) {
251 Object[] newValues = new Object[index];
252 System.arraycopy(values, 0, newValues, 0, index);
253 values = newValues;
254 }
255 return factory.create(property1.getName(), values);
256 }
257
258 protected static class DualIterator implements Iterator<Object> {
259
260 private final Iterator<?>[] iterators;
261 private Iterator<?> current;
262 private int index = 0;
263
264 protected DualIterator( Iterator<?>... iterators ) {
265 assert iterators != null;
266 assert iterators.length > 0;
267 this.iterators = iterators;
268 this.current = this.iterators[0];
269 }
270
271 /**
272 * {@inheritDoc}
273 *
274 * @see java.util.Iterator#hasNext()
275 */
276 public boolean hasNext() {
277 if (this.current != null) return this.current.hasNext();
278 return false;
279 }
280
281 /**
282 * {@inheritDoc}
283 *
284 * @see java.util.Iterator#next()
285 */
286 public Object next() {
287 while (this.current != null) {
288 if (this.current.hasNext()) return this.current.next();
289 // Get the next iterator ...
290 if (++this.index < iterators.length) {
291 this.current = this.iterators[this.index];
292 } else {
293 this.current = null;
294 }
295 }
296 throw new NoSuchElementException();
297 }
298
299 /**
300 * {@inheritDoc}
301 *
302 * @see java.util.Iterator#remove()
303 */
304 public void remove() {
305 throw new UnsupportedOperationException();
306 }
307 }
308
309 protected static class Child {
310 private final Location location;
311 private final boolean placeholder;
312
313 protected Child( Location location,
314 boolean placeholder ) {
315 assert location != null;
316 this.location = location;
317 this.placeholder = placeholder;
318 }
319
320 /**
321 * {@inheritDoc}
322 *
323 * @see java.lang.Object#hashCode()
324 */
325 @Override
326 public int hashCode() {
327 return location.hasIdProperties() ? location.getIdProperties().hashCode() : location.getPath()
328 .getLastSegment()
329 .getName()
330 .hashCode();
331 }
332
333 /**
334 * {@inheritDoc}
335 *
336 * @see java.lang.Object#equals(java.lang.Object)
337 */
338 @Override
339 public boolean equals( Object obj ) {
340 if (obj == this) return true;
341 if (obj instanceof Child) {
342 Child that = (Child)obj;
343 if (this.placeholder && that.placeholder) {
344 // If both are placeholders, then compare just the name ...
345 assert this.location.hasPath();
346 assert that.location.hasPath();
347 Name thisName = this.location.getPath().getLastSegment().getName();
348 Name thatName = that.location.getPath().getLastSegment().getName();
349 return thisName.equals(thatName);
350 }
351 if (location.hasIdProperties() && that.location.hasIdProperties()) {
352 List<Property> thisIds = location.getIdProperties();
353 List<Property> thatIds = that.location.getIdProperties();
354 if (thisIds.size() != thatIds.size()) return false;
355 return thisIds.containsAll(thatIds);
356 }
357 // One or both do not have identification properties, so delegate to the locations...
358 return this.location.equals(that.location);
359 }
360 return false;
361 }
362
363 /**
364 * {@inheritDoc}
365 *
366 * @see java.lang.Object#toString()
367 */
368 @Override
369 public String toString() {
370 return location.toString();
371 }
372 }
373 }