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 }