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 }