[dna-commits] DNA SVN: r470 - in trunk/extensions/dna-connector-federation/src: main/java/org/jboss/dna/connector/federation/merge and 1 other directories.

dna-commits at lists.jboss.org dna-commits at lists.jboss.org
Tue Aug 26 13:21:32 EDT 2008


Author: rhauch
Date: 2008-08-26 13:21:32 -0400 (Tue, 26 Aug 2008)
New Revision: 470

Added:
   trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/SimpleMergeStrategy.java
   trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/SimpleMergeStrategyTest.java
Removed:
   trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/StandardMergeStrategy.java
Modified:
   trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/FederatingCommandExecutor.java
   trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/OneContributionMergeStrategy.java
   trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/OneContributionMergeStrategyTest.java
Log:
DNA-188 - Complete the federating command executor
http://jira.jboss.com/jira/browse/DNA-188

Renamed the StandardMergeStrategy to SimpleMergeStrategy, and fully implemented and tested it to merge multiple contributions.  This merge strategy may not be sufficient for many uses, but it works now and does merge properties.  Note that while it does not merge any children, it does ensure that any same-name siblings (from all contributions) have the proper indexes.

Modified: trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/FederatingCommandExecutor.java
===================================================================
--- trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/FederatingCommandExecutor.java	2008-08-26 09:33:40 UTC (rev 469)
+++ trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/FederatingCommandExecutor.java	2008-08-26 17:21:32 UTC (rev 470)
@@ -41,7 +41,7 @@
 import org.jboss.dna.connector.federation.merge.MergePlan;
 import org.jboss.dna.connector.federation.merge.MergeStrategy;
 import org.jboss.dna.connector.federation.merge.OneContributionMergeStrategy;
-import org.jboss.dna.connector.federation.merge.StandardMergeStrategy;
+import org.jboss.dna.connector.federation.merge.SimpleMergeStrategy;
 import org.jboss.dna.spi.DnaLexicon;
 import org.jboss.dna.spi.ExecutionContext;
 import org.jboss.dna.spi.cache.CachePolicy;
@@ -155,7 +155,7 @@
             if (this.sourceProjections.size() == 1 && this.sourceProjections.get(0).isSimple()) {
                 this.mergingStrategy = new OneContributionMergeStrategy();
             } else {
-                this.mergingStrategy = new StandardMergeStrategy();
+                this.mergingStrategy = new SimpleMergeStrategy();
             }
         }
         assert this.mergingStrategy != null;

Modified: trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/OneContributionMergeStrategy.java
===================================================================
--- trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/OneContributionMergeStrategy.java	2008-08-26 09:33:40 UTC (rev 469)
+++ trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/OneContributionMergeStrategy.java	2008-08-26 17:21:32 UTC (rev 470)
@@ -25,6 +25,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
+import net.jcip.annotations.ThreadSafe;
 import org.jboss.dna.connector.federation.contribution.Contribution;
 import org.jboss.dna.spi.ExecutionContext;
 import org.jboss.dna.spi.graph.IoException;
@@ -34,11 +35,32 @@
 import org.jboss.dna.spi.graph.Path.Segment;
 
 /**
+ * A merge strategy that is optimized for merging when there is a single contribution.
+ * 
  * @author Randall Hauch
  */
+ at ThreadSafe
 public class OneContributionMergeStrategy implements MergeStrategy {
 
+    public static final boolean DEFAULT_REUSE_UUID_FROM_CONTRIBUTION = true;
+
+    private boolean useUuidFromContribution = DEFAULT_REUSE_UUID_FROM_CONTRIBUTION;
+
     /**
+     * @return reuseUuidFromContribution
+     */
+    public boolean isContributionUuidUsedForFederatedNode() {
+        return useUuidFromContribution;
+    }
+
+    /**
+     * @param useUuidFromContribution Sets useUuidFromContribution to the specified value.
+     */
+    public void setContributionUuidUsedForFederatedNode( boolean useUuidFromContribution ) {
+        this.useUuidFromContribution = useUuidFromContribution;
+    }
+
+    /**
      * {@inheritDoc}
      * <p>
      * This method only uses the one and only one non-null {@link Contribution} in the <code>contributions</code>.
@@ -51,10 +73,12 @@
                        List<Contribution> contributions,
                        ExecutionContext context ) {
         assert federatedNode != null;
+        assert context != null;
         assert contributions != null;
         assert contributions.size() > 0;
         Contribution contribution = contributions.get(0);
         assert contribution != null;
+        final boolean findUuid = isContributionUuidUsedForFederatedNode();
         // Copy the children ...
         List<Segment> children = federatedNode.getChildren();
         children.clear();
@@ -72,7 +96,7 @@
         while (propertyIterator.hasNext()) {
             Property property = propertyIterator.next();
             properties.put(property.getName(), property);
-            if (uuid == null && property.getName().getLocalName().equals("uuid") && property.isSingle()) {
+            if (findUuid && uuid == null && property.getName().getLocalName().equals("uuid") && property.isSingle()) {
                 if (uuidFactory == null) uuidFactory = context.getValueFactories().getUuidFactory();
                 try {
                     uuid = uuidFactory.create(property.getValues().next());

Copied: trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/SimpleMergeStrategy.java (from rev 460, trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/StandardMergeStrategy.java)
===================================================================
--- trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/SimpleMergeStrategy.java	                        (rev 0)
+++ trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/SimpleMergeStrategy.java	2008-08-26 17:21:32 UTC (rev 470)
@@ -0,0 +1,275 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.merge;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.UUID;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.connector.federation.contribution.Contribution;
+import org.jboss.dna.spi.ExecutionContext;
+import org.jboss.dna.spi.graph.IoException;
+import org.jboss.dna.spi.graph.Name;
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.PathFactory;
+import org.jboss.dna.spi.graph.Property;
+import org.jboss.dna.spi.graph.PropertyFactory;
+import org.jboss.dna.spi.graph.UuidFactory;
+import org.jboss.dna.spi.graph.ValueComparators;
+import org.jboss.dna.spi.graph.Path.Segment;
+
+/**
+ * This merge strategy simply merges all of the contributions' properties and combines the children according to the order of the
+ * contributions. No children are merged, and all properties are used (except if they are deemed to be duplicates of the property
+ * in other contributions).
+ * 
+ * @author Randall Hauch
+ */
+ at ThreadSafe
+public class SimpleMergeStrategy implements MergeStrategy {
+
+    private boolean removeDuplicateProperties = true;
+
+    /**
+     * @return removeDuplicateProperties
+     */
+    public boolean isRemoveDuplicateProperties() {
+        return removeDuplicateProperties;
+    }
+
+    /**
+     * @param removeDuplicateProperties Sets removeDuplicateProperties to the specified value.
+     */
+    public void setRemoveDuplicateProperties( boolean removeDuplicateProperties ) {
+        this.removeDuplicateProperties = removeDuplicateProperties;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.connector.federation.merge.MergeStrategy#merge(org.jboss.dna.connector.federation.merge.FederatedNode,
+     *      java.util.List, org.jboss.dna.spi.ExecutionContext)
+     */
+    public void merge( FederatedNode federatedNode,
+                       List<Contribution> contributions,
+                       ExecutionContext context ) {
+        assert federatedNode != null;
+        assert context != null;
+        assert contributions != null;
+        assert contributions.size() > 0;
+        PathFactory pathFactory = context.getValueFactories().getPathFactory();
+        // Prepare the federated node ...
+        List<Segment> children = federatedNode.getChildren();
+        children.clear();
+        Map<Name, Integer> childNames = new HashMap<Name, Integer>();
+        Map<Name, Property> properties = federatedNode.getPropertiesByName();
+        properties.clear();
+        UUID uuid = null;
+        UuidFactory uuidFactory = null;
+        final boolean removeDuplicateProperties = isRemoveDuplicateProperties();
+        // Iterate over the set of contributions (in order) ...
+        for (Contribution contribution : contributions) {
+            // Copy the children ...
+            int childListIndex = 0;
+            Iterator<Segment> childIterator = contribution.getChildren();
+            while (childIterator.hasNext()) {
+                Segment child = childIterator.next();
+                int index = Path.NO_INDEX;
+                Integer previous = childNames.put(child.getName(), 1);
+                if (previous != null) {
+                    int previousValue = previous.intValue();
+                    if (previousValue == 1) {
+                        // If the previous value was 1, then go back and look for the child that has no index but that should ...
+                        // Walk backwards, just in case the previous same-name-sibling is closer to the end
+                        for (int i = children.size() - 1; i >= 0; --i) {
+                            Path.Segment childSegment = children.get(i);
+                            if (childSegment.getName().equals(child.getName())) {
+                                children.set(i, pathFactory.createSegment(child.getName(), 1));
+                                break;
+                            }
+                        }
+                    }
+                    // Add the previous value back in ...
+                    childNames.put(child.getName(), ++previousValue);
+                    index = previousValue;
+                }
+                children.add(pathFactory.createSegment(child.getName(), index));
+                ++childListIndex;
+            }
+
+            // Copy the properties ...
+            Iterator<Property> propertyIterator = contribution.getProperties();
+            while (propertyIterator.hasNext()) {
+                Property property = propertyIterator.next();
+                Property existing = properties.put(property.getName(), property);
+                if (existing != null) {
+                    // There's already an existing property, so we need to merge them ...
+                    Property merged = merge(existing, property, context.getPropertyFactory(), removeDuplicateProperties);
+                    properties.put(property.getName(), merged);
+                }
+
+                if (uuid == null && property.getName().getLocalName().equals("uuid") && property.isSingle()) {
+                    if (uuidFactory == null) uuidFactory = context.getValueFactories().getUuidFactory();
+                    try {
+                        uuid = uuidFactory.create(property.getValues().next());
+                    } catch (IoException e) {
+                        // Ignore conversion exceptions
+                        assert uuid == null;
+                    }
+                }
+            }
+        }
+        // If we found a single "uuid" property whose value is a valid UUID ..
+        if (uuid != null) {
+            // then set the UUID on the federated node ...
+            federatedNode.setUuid(uuid);
+        }
+        // Assign the merge plan ...
+        MergePlan mergePlan = MergePlan.create(contributions);
+        federatedNode.setMergePlan(mergePlan);
+    }
+
+    /**
+     * Merge the values from the two properties with the same name, returning a new property with the newly merged values.
+     * <p>
+     * The current algorithm merges the values by concatenating the values from <code>property1</code> and <code>property2</code>,
+     * and if <code>removeDuplicates</code> is true any values in <code>property2</code> that are identical to values found in
+     * <code>property1</code> are skipped.
+     * </p>
+     * 
+     * @param property1 the first property; may not be null, and must have the same {@link Property#getName() name} as
+     *        <code>property2</code>
+     * @param property2 the second property; may not be null, and must have the same {@link Property#getName() name} as
+     *        <code>property1</code>
+     * @param factory the property factory, used to create the result
+     * @param removeDuplicates true if this method removes any values in the second property that duplicate values found in the
+     *        first property.
+     * @return the property that contains the same {@link Property#getName() name} as the input properties, but with values that
+     *         are merged from both of the input properties
+     */
+    protected Property merge( Property property1,
+                              Property property2,
+                              PropertyFactory factory,
+                              boolean removeDuplicates ) {
+        assert property1 != null;
+        assert property2 != null;
+        assert property1.getName().equals(property2.getName());
+        if (property1.isEmpty()) return property2;
+        if (property2.isEmpty()) return property1;
+
+        // If they are both single-valued, then we can use a more efficient algorithm ...
+        if (property1.isSingle() && property2.isSingle()) {
+            Object value1 = property1.getValues().next();
+            Object value2 = property2.getValues().next();
+            if (removeDuplicates && ValueComparators.OBJECT_COMPARATOR.compare(value1, value2) == 0) return property1;
+            return factory.create(property1.getName(), new Object[] {value1, value2});
+        }
+
+        // One or both properties are multi-valued, so use an algorithm that works with in all cases ...
+        if (!removeDuplicates) {
+            Iterator<?> valueIterator = new DualIterator(property1.getValues(), property2.getValues());
+            return factory.create(property1.getName(), valueIterator);
+        }
+
+        // First copy all the values from property 1 ...
+        Object[] values = new Object[property1.size() + property2.size()];
+        int index = 0;
+        for (Object property1Value : property1) {
+            values[index++] = property1Value;
+        }
+        assert index == property1.size();
+        // Now add any values of property2 that don't match a value in property1 ...
+        for (Object property2Value : property2) {
+            // Brute force, go through the values of property1 and compare ...
+            boolean matched = false;
+            for (Object property1Value : property1) {
+                if (ValueComparators.OBJECT_COMPARATOR.compare(property1Value, property2Value) == 0) {
+                    // The values are the same ...
+                    matched = true;
+                    break;
+                }
+            }
+            if (!matched) values[index++] = property2Value;
+        }
+        if (index != values.length) {
+            Object[] newValues = new Object[index];
+            System.arraycopy(values, 0, newValues, 0, index);
+            values = newValues;
+        }
+        return factory.create(property1.getName(), values);
+    }
+
+    protected static class DualIterator implements Iterator<Object> {
+
+        private final Iterator<?>[] iterators;
+        private Iterator<?> current;
+        private int index = 0;
+
+        protected DualIterator( Iterator<?>... iterators ) {
+            assert iterators != null;
+            assert iterators.length > 0;
+            this.iterators = iterators;
+            this.current = this.iterators[0];
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.util.Iterator#hasNext()
+         */
+        public boolean hasNext() {
+            if (this.current != null) return this.current.hasNext();
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.util.Iterator#next()
+         */
+        public Object next() {
+            while (this.current != null) {
+                if (this.current.hasNext()) return this.current.next();
+                // Get the next iterator ...
+                if (++this.index < iterators.length) {
+                    this.current = this.iterators[this.index];
+                } else {
+                    this.current = null;
+                }
+            }
+            throw new NoSuchElementException();
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.util.Iterator#remove()
+         */
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+}


Property changes on: trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/SimpleMergeStrategy.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Deleted: trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/StandardMergeStrategy.java
===================================================================
--- trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/StandardMergeStrategy.java	2008-08-26 09:33:40 UTC (rev 469)
+++ trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/StandardMergeStrategy.java	2008-08-26 17:21:32 UTC (rev 470)
@@ -1,53 +0,0 @@
-/*
- * JBoss, Home of Professional Open Source.
- * Copyright 2008, Red Hat Middleware LLC, and individual contributors
- * as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors. 
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
- */
-package org.jboss.dna.connector.federation.merge;
-
-import java.util.List;
-import org.jboss.dna.connector.federation.contribution.Contribution;
-import org.jboss.dna.spi.ExecutionContext;
-
-/**
- * @author Randall Hauch
- */
-public class StandardMergeStrategy implements MergeStrategy {
-
-    /**
-     * {@inheritDoc}
-     * 
-     * @see org.jboss.dna.connector.federation.merge.MergeStrategy#merge(org.jboss.dna.connector.federation.merge.FederatedNode,
-     *      java.util.List, org.jboss.dna.spi.ExecutionContext)
-     */
-    public void merge( FederatedNode federatedNode,
-                       List<Contribution> contributions,
-                       ExecutionContext context ) {
-
-        // Children whose identity properties are the same will be considered to be the same node ...
-
-        // Assemble the children ...
-
-        // Assemble the properties (and look for UUID property) ...
-
-        // Create a merge plan with the contributions ...
-        MergePlan plan = MergePlan.create(contributions);
-        federatedNode.setMergePlan(plan);
-    }
-}

Modified: trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/OneContributionMergeStrategyTest.java
===================================================================
--- trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/OneContributionMergeStrategyTest.java	2008-08-26 09:33:40 UTC (rev 469)
+++ trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/OneContributionMergeStrategyTest.java	2008-08-26 17:21:32 UTC (rev 470)
@@ -82,7 +82,7 @@
     }
 
     @Test
-    public void shouldMegeTheChildrenFromTheFirstContribution() {
+    public void shouldMergeTheChildrenFromTheFirstContribution() {
         stub(contribution.getChildren()).toReturn(children.iterator());
         stub(contribution.getProperties()).toReturn(properties.values().iterator());
         strategy.merge(node, contributions, context);
@@ -90,7 +90,7 @@
     }
 
     @Test
-    public void shouldMegeThePropertiesFromTheFirstContribution() {
+    public void shouldMergeThePropertiesFromTheFirstContribution() {
         stub(contribution.getChildren()).toReturn(children.iterator());
         stub(contribution.getProperties()).toReturn(properties.values().iterator());
         stub(contribution.getProperties()).toReturn(properties.values().iterator());

Added: trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/SimpleMergeStrategyTest.java
===================================================================
--- trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/SimpleMergeStrategyTest.java	                        (rev 0)
+++ trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/SimpleMergeStrategyTest.java	2008-08-26 17:21:32 UTC (rev 470)
@@ -0,0 +1,304 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.merge;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.stub;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import org.hamcrest.Matcher;
+import org.jboss.dna.common.collection.IsIteratorContaining;
+import org.jboss.dna.connector.federation.contribution.Contribution;
+import org.jboss.dna.spi.ExecutionContext;
+import org.jboss.dna.spi.connector.BasicExecutionContext;
+import org.jboss.dna.spi.graph.Name;
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.Property;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * @author Randall Hauch
+ */
+public class SimpleMergeStrategyTest {
+
+    private SimpleMergeStrategy strategy;
+    private List<Contribution> contributions;
+    private ExecutionContext context;
+    private FederatedNode node;
+
+    @Before
+    public void beforeEach() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        Path path = mock(Path.class);
+        node = new FederatedNode(path, UUID.randomUUID());
+        strategy = new SimpleMergeStrategy();
+        contributions = new LinkedList<Contribution>();
+        context = new BasicExecutionContext();
+        context.getNamespaceRegistry().register("dna", "http://www.jboss.org/dna/something");
+        context.getNamespaceRegistry().register("jcr", "http://www.jcr.org");
+    }
+
+    @Test
+    public void shouldAddChildrenFromOneContribution() {
+        addContribution("source1").addChildren("childA", "childB[1]", "childB[2]");
+        strategy.merge(node, contributions, context);
+        assertThat(node.getChildren(), hasSegments("childA", "childB[1]", "childB[2]"));
+    }
+
+    @Test
+    public void shouldCombinePropertiesFromOneContribution() {
+        addContribution("source1").setProperty("p1", "p1 value");
+        strategy.merge(node, contributions, context);
+        assertThat(node.getProperties().size(), is(1));
+        assertThat(node.getPropertiesByName().get(name("p1")), is(property("p1", "p1 value")));
+    }
+
+    @Test
+    public void shouldAddChildrenFromMultipleContributionsInOrderAndShouldNotChangeSameNameSiblingIndexesWhenChildrenDoNotShareNamesWithChildrenFromDifferentContributions() {
+        addContribution("source1").addChildren("childA", "childB[1]", "childB[2]");
+        addContribution("source2").addChildren("childX", "childY[1]", "childY[2]");
+        strategy.merge(node, contributions, context);
+        assertThat(node.getChildren(), hasSegments("childA", "childB[1]", "childB[2]", "childX", "childY[1]", "childY[2]"));
+    }
+
+    @Test
+    public void shouldAddChildrenFromMultipleContributionsInOrderAndShouldChangeSameNameSiblingIndexesWhenChildrenDoShareNamesWithChildrenFromDifferentContributions() {
+        addContribution("source1").addChildren("childA", "childB[1]", "childB[2]");
+        addContribution("source2").addChildren("childX", "childB", "childY");
+        addContribution("source3").addChildren("childX", "childB");
+        strategy.merge(node, contributions, context);
+        assertThat(node.getChildren(), hasSegments("childA",
+                                                   "childB[1]",
+                                                   "childB[2]",
+                                                   "childX[1]",
+                                                   "childB[3]",
+                                                   "childY",
+                                                   "childX[2]",
+                                                   "childB[4]"));
+    }
+
+    @Test
+    public void shouldCombinePropertiesFromMultipleContributionsAndRemoveNoValuesWhenNoContributionsContainSameProperty() {
+        addContribution("source1").setProperty("p1", "p1 value");
+        addContribution("source2").setProperty("p2", "p2 value");
+        strategy.merge(node, contributions, context);
+        assertThat(node.getProperties().size(), is(2));
+        assertThat(node.getPropertiesByName().get(name("p1")), is(property("p1", "p1 value")));
+        assertThat(node.getPropertiesByName().get(name("p2")), is(property("p2", "p2 value")));
+    }
+
+    @Test
+    public void shouldCombinePropertiesFromMultipleContributionsAndRemoveDuplicateValuesWhenContributionsContainSameProperty() {
+        addContribution("source1").setProperty("p1", "p1 value").setProperty("p12", "1", "2", "3");
+        addContribution("source2").setProperty("p2", "p2 value").setProperty("p12", "3", "4");
+        strategy.merge(node, contributions, context);
+        assertThat(node.getProperties().size(), is(3));
+        assertThat(node.getPropertiesByName().get(name("p1")), is(property("p1", "p1 value")));
+        assertThat(node.getPropertiesByName().get(name("p2")), is(property("p2", "p2 value")));
+        assertThat(node.getPropertiesByName().get(name("p12")), is(property("p12", "1", "2", "3", "4")));
+    }
+
+    @Test
+    public void shouldCombinePropertiesFromMultipleContributionsAndMaintainAllValuesForEveryProperty() {
+        addContribution("source1").setProperty("p1", "p1 value").setProperty("p12", "1", "2", "3");
+        addContribution("source2").setProperty("p2", "p2 value").setProperty("p12", "3", "4");
+        strategy.merge(node, contributions, context);
+        assertThat(node.getProperties().size(), is(3));
+        for (Contribution contribution : contributions) {
+            Iterator<Property> iter = contribution.getProperties();
+            while (iter.hasNext()) {
+                Property contributionProperty = iter.next();
+                Property mergedProperty = node.getPropertiesByName().get(contributionProperty.getName());
+                assertThat(mergedProperty, is(notNullValue()));
+                // Make sure that the merged property has each value ...
+                for (Object contributedValue : contributionProperty.getValuesAsArray()) {
+                    boolean foundValue = false;
+                    for (Object mergedValue : mergedProperty.getValuesAsArray()) {
+                        if (mergedValue.equals(contributedValue)) {
+                            foundValue = true;
+                            break;
+                        }
+                    }
+                    assertThat(foundValue, is(true));
+                }
+            }
+        }
+    }
+
+    @Test
+    public void shouldCombinePropertiesFromMultipleContributionsWhenPropertyValuesAreOfDifferentPropertyTypes() {
+        addContribution("source1").setProperty("p1", "p1 value").setProperty("p12", "1", "2", "3");
+        addContribution("source2").setProperty("p2", "p2 value").setProperty("p12", 3, 4);
+        strategy.merge(node, contributions, context);
+        assertThat(node.getProperties().size(), is(3));
+        assertThat(node.getPropertiesByName().get(name("p1")), is(property("p1", "p1 value")));
+        assertThat(node.getPropertiesByName().get(name("p2")), is(property("p2", "p2 value")));
+        assertThat(node.getPropertiesByName().get(name("p12")), is(property("p12", "1", "2", "3", 4)));
+    }
+
+    @Test
+    public void shouldCreateMergePlanWhenMergingContributions() {
+        addContribution("source1").addChildren("childA", "childB[1]", "childB[2]").setProperty("p1", "p1 value");
+        addContribution("source2").addChildren("childX", "childB", "childY").setProperty("p2", "p2 value");
+        strategy.merge(node, contributions, context);
+        assertThat(node.getMergePlan(), is(notNullValue()));
+        assertThat(node.getMergePlan().getContributionCount(), is(2));
+        assertThat(node.getMergePlan().getContributionFrom("source1"), is(sameInstance(contributions.get(0))));
+        assertThat(node.getMergePlan().getContributionFrom("source2"), is(sameInstance(contributions.get(1))));
+    }
+
+    @Test
+    public void shouldCorrectlyBuildMockUsingContributionBuilder() {
+        assertThat(contributions.size(), is(0));
+        addContribution("source1").addChildren("childA", "childB[1]", "childB[2]").setProperty("p1", "p1 value");
+        assertThat(contributions.size(), is(1));
+        assertThat(contributions.get(0).getChildren(), hasSegmentIterator("childA", "childB[1]", "childB[2]"));
+    }
+
+    @Ignore
+    @Test
+    public void shouldCreateMergePlanInTheFederatedNode() {
+        strategy.merge(node, contributions, context);
+    }
+
+    protected Matcher<List<Path.Segment>> hasSegments( String... segment ) {
+        List<Path.Segment> segments = new ArrayList<Path.Segment>();
+        for (String seg : segment) {
+            segments.add(context.getValueFactories().getPathFactory().createSegment(seg));
+        }
+        return equalTo(segments);
+    }
+
+    protected Matcher<Iterator<Path.Segment>> hasSegmentIterator( String... segment ) {
+        Path.Segment[] segments = new Path.Segment[segment.length];
+        int index = 0;
+        for (String seg : segment) {
+            segments[index++] = context.getValueFactories().getPathFactory().createSegment(seg);
+        }
+        return IsIteratorContaining.hasItems(segments);
+    }
+
+    protected Name name( String name ) {
+        return context.getValueFactories().getNameFactory().create(name);
+    }
+
+    protected Property property( String name,
+                                 Object... values ) {
+        return context.getPropertyFactory().create(name(name), values);
+    }
+
+    protected ContributionBuilder addContribution( String sourceName ) {
+        ContributionBuilder builder = new ContributionBuilder(context, sourceName, contributions);
+        contributions.add(builder.getMock());
+        return builder;
+    }
+
+    protected class ContributionBuilder {
+        protected final Contribution mockContribution;
+        protected final ExecutionContext context;
+        protected final Map<Name, Property> properties = new HashMap<Name, Property>();
+        protected final List<Path.Segment> children = new ArrayList<Path.Segment>();
+
+        protected ContributionBuilder( ExecutionContext context,
+                                       String name,
+                                       List<Contribution> contributions ) {
+            this.context = context;
+            this.mockContribution = Mockito.mock(Contribution.class);
+            stub(mockContribution.getSourceName()).toReturn(name);
+            stub(mockContribution.getChildren()).toAnswer(new Answer<Iterator<Path.Segment>>() {
+                public Iterator<Path.Segment> answer( InvocationOnMock invocation ) throws Throwable {
+                    return ContributionBuilder.this.children.iterator();
+                }
+            });
+            stub(mockContribution.getChildrenCount()).toAnswer(new Answer<Integer>() {
+                public Integer answer( InvocationOnMock invocation ) throws Throwable {
+                    return ContributionBuilder.this.children.size();
+                }
+            });
+            stub(mockContribution.getProperties()).toAnswer(new Answer<Iterator<Property>>() {
+                public Iterator<Property> answer( InvocationOnMock invocation ) throws Throwable {
+                    return ContributionBuilder.this.properties.values().iterator();
+                }
+            });
+            stub(mockContribution.getPropertyCount()).toAnswer(new Answer<Integer>() {
+                public Integer answer( InvocationOnMock invocation ) throws Throwable {
+                    return ContributionBuilder.this.properties.size();
+                }
+            });
+        }
+
+        public Contribution getMock() {
+            return this.mockContribution;
+        }
+
+        public ContributionBuilder addChildren( String... segmentNamesForChildren ) {
+            for (String childSegmentName : segmentNamesForChildren) {
+                children.add(context.getValueFactories().getPathFactory().createSegment(childSegmentName));
+            }
+            return this;
+        }
+
+        public ContributionBuilder addChildren( Name... segmentNamesForChildren ) {
+            for (Name childSegmentName : segmentNamesForChildren) {
+                children.add(context.getValueFactories().getPathFactory().createSegment(childSegmentName));
+            }
+            return this;
+        }
+
+        public ContributionBuilder addChildren( Path.Segment... segmentNamesForChildren ) {
+            for (Path.Segment childSegmentName : segmentNamesForChildren) {
+                children.add(childSegmentName);
+            }
+            return this;
+        }
+
+        public ContributionBuilder setProperty( String name,
+                                                Object... values ) {
+            Name propertyName = context.getValueFactories().getNameFactory().create(name);
+            Property property = context.getPropertyFactory().create(propertyName, values);
+            stub(mockContribution.getProperty(propertyName)).toReturn(property);
+            if (values != null && values.length > 0) {
+                properties.put(propertyName, property);
+            } else {
+                properties.remove(propertyName);
+            }
+            return this;
+        }
+    }
+
+}


Property changes on: trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/SimpleMergeStrategyTest.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain




More information about the dna-commits mailing list