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 }