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