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    }