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.properties.IoException; 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.UuidFactory; 043 import org.jboss.dna.graph.properties.ValueComparators; 044 import org.jboss.dna.graph.properties.Path.Segment; 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 PathFactory pathFactory = context.getValueFactories().getPathFactory(); 086 // Prepare the federated node ... 087 List<Segment> children = federatedNode.getChildren(); 088 children.clear(); 089 Map<Name, Integer> childNames = new HashMap<Name, Integer>(); 090 Map<Name, Property> properties = federatedNode.getPropertiesByName(); 091 properties.clear(); 092 UUID uuid = null; 093 UuidFactory uuidFactory = null; 094 final boolean removeDuplicateProperties = isRemoveDuplicateProperties(); 095 // Iterate over the set of contributions (in order) ... 096 for (Contribution contribution : contributions) { 097 // If the contribution is a placeholder contribution, then the children should be merged into other children ... 098 if (contribution.isPlaceholder()) { 099 // Iterate over the children and add only if there is not already one ... 100 Iterator<Segment> childIterator = contribution.getChildren(); 101 while (childIterator.hasNext()) { 102 Segment child = childIterator.next(); 103 if (!childNames.containsKey(child.getName())) { 104 childNames.put(child.getName(), 1); 105 children.add(pathFactory.createSegment(child.getName())); 106 } 107 } 108 109 } else { 110 // Copy the children ... 111 Iterator<Segment> childIterator = contribution.getChildren(); 112 while (childIterator.hasNext()) { 113 Segment child = childIterator.next(); 114 int index = Path.NO_INDEX; 115 Integer previous = childNames.put(child.getName(), 1); 116 if (previous != null) { 117 int previousValue = previous.intValue(); 118 // Correct the index in the child name map ... 119 childNames.put(child.getName(), ++previousValue); 120 index = previousValue; 121 } 122 children.add(pathFactory.createSegment(child.getName(), index)); 123 } 124 125 // Copy the properties ... 126 Iterator<Property> propertyIterator = contribution.getProperties(); 127 while (propertyIterator.hasNext()) { 128 Property property = propertyIterator.next(); 129 // Skip the "uuid" property on all root nodes ... 130 if (federatedNode.getPath().isRoot() && property.getName().getLocalName().equals("uuid")) continue; 131 Property existing = properties.put(property.getName(), property); 132 if (existing != null) { 133 // There's already an existing property, so we need to merge them ... 134 Property merged = merge(existing, property, context.getPropertyFactory(), removeDuplicateProperties); 135 properties.put(property.getName(), merged); 136 } 137 138 if (uuid == null && property.getName().getLocalName().equals("uuid") && property.isSingle()) { 139 if (uuidFactory == null) uuidFactory = context.getValueFactories().getUuidFactory(); 140 try { 141 uuid = uuidFactory.create(property.getValues().next()); 142 } catch (IoException e) { 143 // Ignore conversion exceptions 144 assert uuid == null; 145 } 146 } 147 } 148 } 149 } 150 // If we found a single "uuid" property whose value is a valid UUID .. 151 if (uuid != null) { 152 // then set the UUID on the federated node ... 153 federatedNode.setUuid(uuid); 154 } 155 // Set the UUID as a property ... 156 Property uuidProperty = context.getPropertyFactory().create(DnaLexicon.UUID, federatedNode.getUuid()); 157 properties.put(uuidProperty.getName(), uuidProperty); 158 159 // Assign the merge plan ... 160 MergePlan mergePlan = MergePlan.create(contributions); 161 federatedNode.setMergePlan(mergePlan); 162 } 163 164 /** 165 * Merge the values from the two properties with the same name, returning a new property with the newly merged values. 166 * <p> 167 * The current algorithm merges the values by concatenating the values from <code>property1</code> and <code>property2</code>, 168 * and if <code>removeDuplicates</code> is true any values in <code>property2</code> that are identical to values found in 169 * <code>property1</code> are skipped. 170 * </p> 171 * 172 * @param property1 the first property; may not be null, and must have the same {@link Property#getName() name} as 173 * <code>property2</code> 174 * @param property2 the second property; may not be null, and must have the same {@link Property#getName() name} as 175 * <code>property1</code> 176 * @param factory the property factory, used to create the result 177 * @param removeDuplicates true if this method removes any values in the second property that duplicate values found in the 178 * first property. 179 * @return the property that contains the same {@link Property#getName() name} as the input properties, but with values that 180 * are merged from both of the input properties 181 */ 182 protected Property merge( Property property1, 183 Property property2, 184 PropertyFactory factory, 185 boolean removeDuplicates ) { 186 assert property1 != null; 187 assert property2 != null; 188 assert property1.getName().equals(property2.getName()); 189 if (property1.isEmpty()) return property2; 190 if (property2.isEmpty()) return property1; 191 192 // If they are both single-valued, then we can use a more efficient algorithm ... 193 if (property1.isSingle() && property2.isSingle()) { 194 Object value1 = property1.getValues().next(); 195 Object value2 = property2.getValues().next(); 196 if (removeDuplicates && ValueComparators.OBJECT_COMPARATOR.compare(value1, value2) == 0) return property1; 197 return factory.create(property1.getName(), new Object[] {value1, value2}); 198 } 199 200 // One or both properties are multi-valued, so use an algorithm that works with in all cases ... 201 if (!removeDuplicates) { 202 Iterator<?> valueIterator = new DualIterator(property1.getValues(), property2.getValues()); 203 return factory.create(property1.getName(), valueIterator); 204 } 205 206 // First copy all the values from property 1 ... 207 Object[] values = new Object[property1.size() + property2.size()]; 208 int index = 0; 209 for (Object property1Value : property1) { 210 values[index++] = property1Value; 211 } 212 assert index == property1.size(); 213 // Now add any values of property2 that don't match a value in property1 ... 214 for (Object property2Value : property2) { 215 // Brute force, go through the values of property1 and compare ... 216 boolean matched = false; 217 for (Object property1Value : property1) { 218 if (ValueComparators.OBJECT_COMPARATOR.compare(property1Value, property2Value) == 0) { 219 // The values are the same ... 220 matched = true; 221 break; 222 } 223 } 224 if (!matched) values[index++] = property2Value; 225 } 226 if (index != values.length) { 227 Object[] newValues = new Object[index]; 228 System.arraycopy(values, 0, newValues, 0, index); 229 values = newValues; 230 } 231 return factory.create(property1.getName(), values); 232 } 233 234 protected static class DualIterator implements Iterator<Object> { 235 236 private final Iterator<?>[] iterators; 237 private Iterator<?> current; 238 private int index = 0; 239 240 protected DualIterator( Iterator<?>... iterators ) { 241 assert iterators != null; 242 assert iterators.length > 0; 243 this.iterators = iterators; 244 this.current = this.iterators[0]; 245 } 246 247 /** 248 * {@inheritDoc} 249 * 250 * @see java.util.Iterator#hasNext() 251 */ 252 public boolean hasNext() { 253 if (this.current != null) return this.current.hasNext(); 254 return false; 255 } 256 257 /** 258 * {@inheritDoc} 259 * 260 * @see java.util.Iterator#next() 261 */ 262 public Object next() { 263 while (this.current != null) { 264 if (this.current.hasNext()) return this.current.next(); 265 // Get the next iterator ... 266 if (++this.index < iterators.length) { 267 this.current = this.iterators[this.index]; 268 } else { 269 this.current = null; 270 } 271 } 272 throw new NoSuchElementException(); 273 } 274 275 /** 276 * {@inheritDoc} 277 * 278 * @see java.util.Iterator#remove() 279 */ 280 public void remove() { 281 throw new UnsupportedOperationException(); 282 } 283 284 } 285 }