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    }