You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by rg...@apache.org on 2009/03/27 21:53:16 UTC

svn commit: r759346 - in /commons/proper/configuration/trunk: conf/ src/java/org/apache/commons/configuration/ src/java/org/apache/commons/configuration/tree/ src/test/org/apache/commons/configuration/ src/test/org/apache/commons/configuration/tree/ xd...

Author: rgoers
Date: Fri Mar 27 20:53:16 2009
New Revision: 759346

URL: http://svn.apache.org/viewvc?rev=759346&view=rev
Log:
CONFIGURATION-378 Add MergeCombiner

Added:
    commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/MergeCombiner.java
      - copied, changed from r757541, commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/OverrideCombiner.java
    commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestMergeCombiner.java
      - copied, changed from r757541, commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestOverrideCombiner.java
Modified:
    commons/proper/configuration/trunk/conf/testMultiTenentConfigurationBuilder.xml
    commons/proper/configuration/trunk/conf/testcombine1.xml
    commons/proper/configuration/trunk/conf/testcombine2.xml
    commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalConfiguration.java
    commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/NodeCombiner.java
    commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestDefaultConfigurationBuilder.java
    commons/proper/configuration/trunk/xdocs/changes.xml
    commons/proper/configuration/trunk/xdocs/userguide/howto_combinedconfiguration.xml

Modified: commons/proper/configuration/trunk/conf/testMultiTenentConfigurationBuilder.xml
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/conf/testMultiTenentConfigurationBuilder.xml?rev=759346&r1=759345&r2=759346&view=diff
==============================================================================
--- commons/proper/configuration/trunk/conf/testMultiTenentConfigurationBuilder.xml (original)
+++ commons/proper/configuration/trunk/conf/testMultiTenentConfigurationBuilder.xml Fri Mar 27 20:53:16 2009
@@ -5,6 +5,7 @@
     <result delimiterParsingDisabled="true" forceReloadCheck="true"
             config-class="org.apache.commons.configuration.DynamicCombinedConfiguration"
             keyPattern="$${sys:Id}">
+      <nodeCombiner config-class="org.apache.commons.configuration.tree.MergeCombiner"/>
       <expressionEngine
           config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
     </result>

Modified: commons/proper/configuration/trunk/conf/testcombine1.xml
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/conf/testcombine1.xml?rev=759346&r1=759345&r2=759346&view=diff
==============================================================================
--- commons/proper/configuration/trunk/conf/testcombine1.xml (original)
+++ commons/proper/configuration/trunk/conf/testcombine1.xml Fri Mar 27 20:53:16 2009
@@ -49,4 +49,15 @@
       </table>
     </tables>
   </database>
+  <Channels>
+    <Channel id="1" type="half">
+      <Name>My Channel</Name>
+    </Channel>
+    <Channel id="2">
+      <MoreChannelData>more test 2 data</MoreChannelData>
+    </Channel>
+    <Channel id="3" type="half">
+      <Name>Test Channel</Name>
+    </Channel> 
+  </Channels>  
 </config>

Modified: commons/proper/configuration/trunk/conf/testcombine2.xml
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/conf/testcombine2.xml?rev=759346&r1=759345&r2=759346&view=diff
==============================================================================
--- commons/proper/configuration/trunk/conf/testcombine2.xml (original)
+++ commons/proper/configuration/trunk/conf/testcombine2.xml Fri Mar 27 20:53:16 2009
@@ -45,4 +45,18 @@
       </table>
     </tables>
   </database>
+  <Channels>
+    <Channel id="1">
+      <Name>Channel 1</Name>
+      <ChannelData>test 1 data</ChannelData>
+    </Channel>
+    <Channel id="2" type="full">
+      <Name>Channel 2</Name>
+      <ChannelData>test 2 data</ChannelData>
+    </Channel>
+    <Channel id="3" type="full">
+      <Name>Channel 3</Name>
+      <ChannelData>test 3 data</ChannelData>
+    </Channel>
+  </Channels>
 </config>

Modified: commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalConfiguration.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalConfiguration.java?rev=759346&r1=759345&r2=759346&view=diff
==============================================================================
--- commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalConfiguration.java (original)
+++ commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalConfiguration.java Fri Mar 27 20:53:16 2009
@@ -36,6 +36,7 @@
 import org.apache.commons.configuration.tree.DefaultExpressionEngine;
 import org.apache.commons.configuration.tree.ExpressionEngine;
 import org.apache.commons.configuration.tree.NodeAddData;
+import org.apache.commons.configuration.tree.ViewNode;
 import org.apache.commons.lang.StringUtils;
 
 /**
@@ -1331,12 +1332,28 @@
             for (Iterator it = getChildren().iterator(); it.hasNext()
                     && !visitor.terminate();)
             {
-                ((Node) it.next()).visit(visitor, key);
+                Object obj = it.next();
+                if (obj instanceof ViewNode)
+                {
+                    new Node((ViewNode)obj).visit(visitor, key);
+                }
+                else
+                {
+                    ((Node) obj).visit(visitor, key);
+                }
             }
             for (Iterator it = getAttributes().iterator(); it.hasNext()
                     && !visitor.terminate();)
             {
-                ((Node) it.next()).visit(visitor, key);
+                Object obj = it.next();
+                if (obj instanceof ViewNode)
+                {
+                    new Node((ViewNode)obj).visit(visitor, key);
+                }
+                else
+                {
+                    ((Node) obj).visit(visitor, key);
+                }
             }
 
             if (key != null)
@@ -1621,7 +1638,15 @@
                 do
                 {
                     sibling1 = nd;
-                    nd = (Node) children.next();
+                    Object obj = children.next();
+                    if (obj instanceof ViewNode)
+                    {
+                        nd = new Node((ViewNode)obj);  
+                    }
+                    else
+                    {
+                        nd = (Node) obj;
+                    }
                 } while (nd.getReference() != null && children.hasNext());
 
                 if (nd.getReference() == null)
@@ -1631,7 +1656,15 @@
                     newNodes.add(nd);
                     while (children.hasNext())
                     {
-                        nd = (Node) children.next();
+                        Object obj = children.next();
+                        if (obj instanceof ViewNode)
+                        {
+                            nd = new Node((ViewNode)obj);
+                        }
+                        else
+                        {
+                            nd = (Node) obj;
+                        }
                         if (nd.getReference() == null)
                         {
                             newNodes.add(nd);

Copied: commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/MergeCombiner.java (from r757541, commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/OverrideCombiner.java)
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/MergeCombiner.java?p2=commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/MergeCombiner.java&p1=commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/OverrideCombiner.java&r1=757541&r2=759346&rev=759346&view=diff
==============================================================================
--- commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/OverrideCombiner.java (original)
+++ commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/MergeCombiner.java Fri Mar 27 20:53:16 2009
@@ -17,89 +17,77 @@
 package org.apache.commons.configuration.tree;
 
 import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ArrayList;
 
 /**
  * <p>
- * A concrete combiner implementation that is able to construct an override
- * combination.
+ * A specialized implementation of the <code>NodeCombiner</code> interface
+ * that performs a merge from two passed in node hierarchies.
  * </p>
  * <p>
- * An <em>override combination</em> means that nodes in the first node
- * structure take precedence over nodes in the second, or - in other words -
- * nodes of the second structure are only added to the resulting structure if
- * they do not occure in the first one. This is especially suitable for dealing
- * with the properties of configurations that are defined in an
- * <code>override</code> section of a configuration definition file (hence the
- * name).
- * </p>
- * <p>
- * This combiner will iterate over the second node hierarchy and find all nodes
- * that are not contained in the first hierarchy; these are added to the result.
- * If a node can be found in both structures, it is checked whether a
- * combination (in a recursive way) can be constructed for the two, which will
- * then be added. Per default, nodes are combined, which occur only once in both
- * structures. This test is implemented in the <code>canCombine()</code>
- * method.
- * </p>
- * <p>
- * As is true for the <code>{@link UnionCombiner}</code>, for this combiner
- * list nodes are important. The <code>addListNode()</code> can be called to
- * declare certain nodes as list nodes. This has the effect that these nodes
- * will never be combined.
+ * This combiner performs the merge using a few rules:
+ * <ol>
+ * <li>Nodes can be merged when attributes that appear in both have the same value.</li>
+ * <li>Only a single node in the second file is considered a match to the node in the first file.</li>
+ * <li>Attributes in nodes that match are merged.
+ * <li>Nodes in both files that do not match are added to the result.</li>
+ * </ol>
  * </p>
  *
  * @author <a
  * href="http://commons.apache.org/configuration/team-list.html">Commons
  * Configuration team</a>
  * @version $Id$
- * @since 1.3
+ * @since 1.7
  */
-public class OverrideCombiner extends NodeCombiner
+public class MergeCombiner extends NodeCombiner
 {
     /**
-     * Constructs an override combination for the passed in node structures.
+     * Combines the given nodes to a new union node.
      *
-     * @param node1 the first node
-     * @param node2 the second node
-     * @return the resulting combined node structure
+     * @param node1 the first source node
+     * @param node2 the second source node
+     * @return the union node
      */
-    public ConfigurationNode combine(ConfigurationNode node1,
-            ConfigurationNode node2)
+
+    public ConfigurationNode combine(ConfigurationNode node1, ConfigurationNode node2)
+    {
+        ConfigurationNode result = doCombine(node1, node2);
+        printTree(result);
+        return result;
+    }
+    
+    public ConfigurationNode doCombine(ConfigurationNode node1, ConfigurationNode node2)
     {
         ViewNode result = createViewNode();
         result.setName(node1.getName());
+        result.setValue(node1.getValue());
+        addAttributes(result, node1, node2);
 
-        // Process nodes from the first structure, which override the second
+        // Check if nodes can be combined
+        List children2 = new LinkedList(node2.getChildren());
         for (Iterator it = node1.getChildren().iterator(); it.hasNext();)
         {
-            ConfigurationNode child = (ConfigurationNode) it.next();
-            ConfigurationNode child2 = canCombine(node1, node2, child);
+            ConfigurationNode child1 = (ConfigurationNode) it.next();
+            ConfigurationNode child2 = canCombine(node1, node2, child1, children2);
             if (child2 != null)
             {
-                result.addChild(combine(child, child2));
+                result.addChild(doCombine(child1, child2));
+                children2.remove(child2);
             }
             else
             {
-                result.addChild(child);
+                result.addChild(child1);
             }
         }
 
-        // Process nodes from the second structure, which are not contained
-        // in the first structure
-        for (Iterator it = node2.getChildren().iterator(); it.hasNext();)
+        // Add remaining children of node 2
+        for (Iterator it = children2.iterator(); it.hasNext();)
         {
-            ConfigurationNode child = (ConfigurationNode) it.next();
-            if (node1.getChildrenCount(child.getName()) < 1)
-            {
-                result.addChild(child);
-            }
+            result.addChild((ConfigurationNode) it.next());
         }
-
-        // Handle attributes and value
-        addAttributes(result, node1, node2);
-        result.setValue((node1.getValue() != null) ? node1.getValue() : node2
-                .getValue());
-
         return result;
     }
 
@@ -128,11 +116,9 @@
     }
 
     /**
-     * Tests if a child node of the second node can be combined with the given
-     * child node of the first node. If this is the case, the corresponding node
-     * will be returned, otherwise <b>null</b>. This implementation checks
-     * whether the child node occurs only once in both hierarchies and is no
-     * known list node.
+     * Tests if the first node can be combined with the second node. A node can
+     * only be combined if its attributes are all present in the second node and
+     * they all have the same value.
      *
      * @param node1 the first node
      * @param node2 the second node
@@ -140,18 +126,47 @@
      * @return a child of the second node, with which a combination is possible
      */
     protected ConfigurationNode canCombine(ConfigurationNode node1,
-            ConfigurationNode node2, ConfigurationNode child)
+            ConfigurationNode node2, ConfigurationNode child, List children2)
     {
-        if (node2.getChildrenCount(child.getName()) == 1
-                && node1.getChildrenCount(child.getName()) == 1
-                && !isListNode(child))
+        List attrs1 = child.getAttributes();
+        List nodes = new ArrayList();
+
+        List children = node2.getChildren(child.getName());
+        Iterator it = children.iterator();
+        while (it.hasNext())
         {
-            return (ConfigurationNode) node2.getChildren(child.getName())
-                    .get(0);
+            ConfigurationNode node = (ConfigurationNode) it.next();
+            Iterator iter = attrs1.iterator();
+            while (iter.hasNext())
+            {
+                ConfigurationNode attr1 = (ConfigurationNode) iter.next();
+                List list2 = node.getAttributes(attr1.getName());
+                if (list2.size() == 1
+                    && !attr1.getValue().equals(((ConfigurationNode)list2.get(0)).getValue()))
+                {
+                    node = null;
+                    break;
+                }
+            }
+            if (node != null)
+            {
+                nodes.add(node);
+            }
         }
-        else
+
+        if (nodes.size() == 1)
         {
-            return null;
+            return (ConfigurationNode) nodes.get(0);
         }
+        if (nodes.size() > 1 && !isListNode(child))
+        {
+            Iterator iter = nodes.iterator();
+            while (iter.hasNext())
+            {
+                children2.remove(iter.next());
+            }
+        }        
+
+        return null;
     }
-}
+}
\ No newline at end of file

Modified: commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/NodeCombiner.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/NodeCombiner.java?rev=759346&r1=759345&r2=759346&view=diff
==============================================================================
--- commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/NodeCombiner.java (original)
+++ commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/tree/NodeCombiner.java Fri Mar 27 20:53:16 2009
@@ -19,6 +19,8 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.Iterator;
+import java.io.PrintStream;
 
 /**
  * <p>
@@ -52,6 +54,9 @@
  */
 public abstract class NodeCombiner
 {
+    /** Stream to write debug output to */
+    private PrintStream debugStream = null;
+    
     /** Stores a list with node names that are known to be list nodes. */
     protected Set listNodes;
 
@@ -121,4 +126,54 @@
     {
         return new ViewNode();
     }
+
+    /**
+     * Set the output stream to write the tree to.
+     * @param stream The OutputStream.
+     */
+    public void setDebugStream(PrintStream stream)
+    {
+        this.debugStream = stream;
+    }
+        
+    protected void printTree(ConfigurationNode result)
+    {
+        if (debugStream != null)
+        {
+            printTree("", result);
+        }
+    }
+
+    private void printTree(String indent, ConfigurationNode result)
+    {
+        StringBuffer buffer = new StringBuffer(indent).append("<").append(result.getName());
+        Iterator iter = result.getAttributes().iterator();
+        while (iter.hasNext())
+        {
+            ConfigurationNode node = (ConfigurationNode) iter.next();
+            buffer.append(" ").append(node.getName()).append("='").append(node.getValue()).append("'");
+        }
+        buffer.append(">");
+        debugStream.print(buffer.toString());
+        if (result.getValue() != null)
+        {
+            debugStream.print(result.getValue());
+        }
+        boolean newline = false;
+        if (result.getChildrenCount() > 0)
+        {
+            debugStream.print("\n");
+            iter = result.getChildren().iterator();
+            while (iter.hasNext())
+            {
+                printTree(indent + "  ", (ConfigurationNode) iter.next());
+            }
+            newline = true;
+        }
+        if (newline)
+        {
+            debugStream.print(indent);
+        }
+        debugStream.println("</" + result.getName() + ">");
+    }
 }

Modified: commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestDefaultConfigurationBuilder.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestDefaultConfigurationBuilder.java?rev=759346&r1=759345&r2=759346&view=diff
==============================================================================
--- commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestDefaultConfigurationBuilder.java (original)
+++ commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestDefaultConfigurationBuilder.java Fri Mar 27 20:53:16 2009
@@ -885,10 +885,6 @@
         verify("1005", config, 50);
     }
 
-    /** This test doesn't pass and rightfully so. A new "MergeCombiner" needs to be
-     * created so it will.
-     * @throws Exception
-     */  /*
     public void testMultiTenantConfigurationAt() throws Exception
     {
         factory.setFile(MULTI_TENENT_FILE);
@@ -900,7 +896,7 @@
         HierarchicalConfiguration sub2 = config.configurationAt("Channels/Channel[@id='2']");
         assertEquals("Channel 2", sub2.getString("Name"));
         assertEquals("more test 2 data", sub2.getString("MoreChannelData"));
-    } */
+    }
 
     public void testMerge() throws Exception
     {

Copied: commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestMergeCombiner.java (from r757541, commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestOverrideCombiner.java)
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestMergeCombiner.java?p2=commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestMergeCombiner.java&p1=commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestOverrideCombiner.java&r1=757541&r2=759346&rev=759346&view=diff
==============================================================================
--- commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestOverrideCombiner.java (original)
+++ commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/tree/TestMergeCombiner.java Fri Mar 27 20:53:16 2009
@@ -20,13 +20,15 @@
 
 import org.apache.commons.configuration.ConfigurationException;
 import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
 
 /**
- * Test class for OverrideCombiner.
+ * Test class for MergeCombiner.
  *
  * @version $Id$
  */
-public class TestOverrideCombiner extends AbstractCombinerTest
+public class TestMergeCombiner extends AbstractCombinerTest
 {
     /**
      * Creates the combiner.
@@ -35,7 +37,7 @@
      */
     protected NodeCombiner createCombiner()
     {
-        return new OverrideCombiner();
+        return new MergeCombiner();
     }
 
     /**
@@ -79,8 +81,7 @@
                 .getString("base.services.security.login.user"));
         assertEquals("Wrong user type", "default", config
                 .getString("base.services.security.login.user[@type]"));
-        assertEquals("Wrong password", "BeamMeUp", config
-                .getString("base.services.security.login.passwd"));
+        assertNull("Wrong password", config.getString("base.services.security.login.passwd"));
         assertEquals("Wrong password type", "secret", config
                 .getString("base.services.security.login.passwd[@type]"));
     }
@@ -114,29 +115,35 @@
     }
 
     /**
-     * Tests the combination of the table structure. Because the table node is
-     * not declared as a list node the structures will be combined. But this
-     * won't make any difference because the values in the first table override
-     * the values in the second table. Only the node for the table element will
-     * be a ViewNode.
+     * Tests the combination of the table structure. With the merge combiner
+     * both table 1 and table 2 should be present.
      */
-    public void testCombinedTableNoList() throws ConfigurationException
+    public void testCombinedTable() throws ConfigurationException
     {
-        ConfigurationNode tabNode = checkTable(createCombinedConfiguration());
-        assertTrue("Node is not a view node", tabNode instanceof ViewNode);
+        checkTable(createCombinedConfiguration());
     }
 
-    /**
-     * Tests the combination of the table structure when the table node is
-     * declared as a list node. In this case the first table structure
-     * completely overrides the second and will be directly added to the
-     * resulting structure.
-     */
-    public void testCombinedTableList() throws ConfigurationException
+    public void testMerge() throws ConfigurationException
     {
-        combiner.addListNode("table");
-        ConfigurationNode tabNode = checkTable(createCombinedConfiguration());
-        assertFalse("Node is a view node", tabNode instanceof ViewNode);
+        combiner.setDebugStream(System.out);
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        config.setExpressionEngine(new XPathExpressionEngine());
+        assertEquals("Wrong number of Channels", 3, config.getMaxIndex("Channels/Channel"));
+        assertEquals("Bad Channel 1 Name", "My Channel",
+                config.getString("Channels/Channel[@id='1']/Name"));
+        assertEquals("Bad Channel Type", "half",
+                config.getString("Channels/Channel[@id='1']/@type"));
+        assertEquals("Bad Channel 2 Name", "Channel 2",
+                config.getString("Channels/Channel[@id='2']/Name"));
+        assertEquals("Bad Channel Type", "full",
+                config.getString("Channels/Channel[@id='2']/@type"));
+        assertEquals("Bad Channel Data", "test 1 data",
+                config.getString("Channels/Channel[@id='1']/ChannelData"));
+        assertEquals("Bad Channel Data", "test 2 data",
+                config.getString("Channels/Channel[@id='2']/ChannelData"));
+        assertEquals("Bad Channel Data", "more test 2 data",
+                config.getString("Channels/Channel[@id='2']/MoreChannelData"));
+
     }
 
     /**
@@ -147,10 +154,10 @@
      */
     private ConfigurationNode checkTable(HierarchicalConfiguration config)
     {
-        assertEquals("Wrong number of tables", 0, config
+        assertEquals("Wrong number of tables", 1, config
                 .getMaxIndex("database.tables.table"));
         HierarchicalConfiguration c = config
-                .configurationAt("database.tables.table");
+                .configurationAt("database.tables.table(0)");
         assertEquals("Wrong table name", "documents", c.getString("name"));
         assertEquals("Wrong number of fields", 2, c
                 .getMaxIndex("fields.field.name"));
@@ -162,4 +169,4 @@
         assertFalse("No node found", nds.isEmpty());
         return (ConfigurationNode) nds.get(0);
     }
-}
+}
\ No newline at end of file

Modified: commons/proper/configuration/trunk/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/xdocs/changes.xml?rev=759346&r1=759345&r2=759346&view=diff
==============================================================================
--- commons/proper/configuration/trunk/xdocs/changes.xml (original)
+++ commons/proper/configuration/trunk/xdocs/changes.xml Fri Mar 27 20:53:16 2009
@@ -23,6 +23,10 @@
 
   <body>
     <release version="1.7" date="in SVN" description="">
+      <action dev="rgoers" type="add" issue="CONFIGURATION-378">
+        Added MergeCombiner to allow elements in two configurations to be merged when the
+        element and attributes in the first file match those in the second file.
+      </action>
       <action dev="rgoers" type="add" issue="CONFIGURATION-340">
         File system access has been abstracted to a FileSystem interface. Two implementations
         are provided, DefaultFileSystem that behaves in a backward compatible manner and

Modified: commons/proper/configuration/trunk/xdocs/userguide/howto_combinedconfiguration.xml
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/xdocs/userguide/howto_combinedconfiguration.xml?rev=759346&r1=759345&r2=759346&view=diff
==============================================================================
--- commons/proper/configuration/trunk/xdocs/userguide/howto_combinedconfiguration.xml (original)
+++ commons/proper/configuration/trunk/xdocs/userguide/howto_combinedconfiguration.xml Fri Mar 27 20:53:16 2009
@@ -85,10 +85,11 @@
       takes the root nodes of two hierarchical configurations and returns the
       root node of the combined node structure. It is up to a concrete
       implementation how this combined structure will look like. Commons
-      Configuration ships with the two concrete implementations
-      <code><a href="../apidocs/org/apache/commons/configuration/tree/OverrideCombiner.html">OverrideCombiner</a></code>
+      Configuration ships with three concrete implementations
+      <code><a href="../apidocs/org/apache/commons/configuration/tree/OverrideCombiner.html">OverrideCombiner</a></code>,
+      <code><a href="../apidocs/org/apache/commons/configuration/tree/MergeCombiner.html">MergeCombiner</a></code>
       and <code><a href="../apidocs/org/apache/commons/configuration/tree/UnionCombiner.html">UnionCombiner</a></code>,
-      which implement an override and a union semantics respective.
+      which implement an override, merge, and union semantics respectively.
     </p>
     <p>
       Constructing a combination of multiple node hierarchies is not a trivial
@@ -216,6 +217,471 @@
       node combiner would have concluded itself that <code>table</code> is a list
       node and would have acted correspondigly.
     </p>
+    <p>
+      The examples the follow are provided to further illustrate the differences
+      between the combiners that are delivered with Commons Configuration. The first
+      two files are the files that will be combined.
+    </p>
+    <table border='0'>
+    <tr>
+      <th width="50%">testfile1.xml</th>
+      <th width="50%">testfile2.xml</th>
+    </tr>
+    <tr><td width="50%">
+<source><![CDATA[<config>
+  <gui>
+    <bgcolor>green</bgcolor>
+    <selcolor>yellow</selcolor>
+    <level default="2">1</level>
+  </gui>
+  <net>
+    <proxy>
+      <url>http://www.url1.org</url>
+      <url>http://www.url2.org</url>
+      <url>http://www.url3.org</url>
+    </proxy>
+    <service>
+      <url>http://service1.org</url>
+    </service>
+    <server>
+    </server>
+  </net>
+  <base>
+    <services>
+      <security>
+        <login>
+          <user>Admin</user>
+          <passwd type="secret"/>
+        </login>
+      </security>
+    </services>
+  </base>
+  <database>
+    <tables>
+      <table id="1">
+        <name>documents</name>
+        <fields>
+          <field>
+            <name>docid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>docname</name>
+            <type>varchar</type>
+          </field>
+          <field>
+            <name>authorID</name>
+            <type>int</type>
+          </field>
+        </fields>
+      </table>
+    </tables>
+  </database>
+  <Channels>
+    <Channel id="1" type="half">
+      <Name>My Channel</Name>
+    </Channel>
+    <Channel id="2">
+      <MoreChannelData>more test 2 data</MoreChannelData>
+    </Channel>
+    <Channel id="3" type="half">
+      <Name>Test Channel</Name>
+    </Channel>
+    <Channel id="4">
+      <Name>Channel 4</Name>
+    </Channel>
+  </Channels>
+</config>
+]]></source></td><td width="50%">
+<source><![CDATA[<config>
+  <base>
+    <services>
+      <security>
+        <login>
+          <user type="default">scotty</user>
+          <passwd>BeamMeUp</passwd>
+        </login>
+      </security>
+    </services>
+  </base>
+  <gui>
+    <bgcolor>black</bgcolor>
+    <fgcolor>blue</fgcolor>
+    <level min="1">4</level>
+  </gui>
+  <net>
+    <server>
+      <url>http://appsvr1.com</url>
+      <url>http://appsvr2.com</url>
+      <url>http://testsvr.com</url>
+      <url>http://backupsvr.com</url>
+    </server>
+    <service>
+      <url type="2">http://service2.org</url>
+      <url type="2">http://service3.org</url>
+    </service>
+  </net>
+  <database>
+    <tables>
+      <table id="2">
+        <name>tasks</name>
+        <fields>
+          <field>
+            <name>taskid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>taskname</name>
+            <type>varchar</type>
+          </field>
+        </fields>
+      </table>
+    </tables>
+  </database>
+  <Channels>
+    <Channel id="1">
+      <Name>Channel 1</Name>
+      <ChannelData>test 1 data</ChannelData>
+    </Channel>
+    <Channel id="2" type="full">
+      <Name>Channel 2</Name>
+      <ChannelData>test 2 data</ChannelData>
+    </Channel>
+    <Channel id="3" type="full">
+      <Name>Channel 3</Name>
+      <ChannelData>test 3 data</ChannelData>
+    </Channel>
+    <Channel id="4" type="half">
+      <Name>Test Channel 1</Name>
+    </Channel>
+    <Channel id="4" type="full">
+      <Name>Test Channel 2</Name>
+    </Channel>
+  </Channels>
+</config>
+]]></source></td></tr></table>
+      <p>
+        The first listing shows the result of using the <code>OverrideCombiner</code>.
+      </p>
+      <table>
+        <tr><th width="40%">OverrideCombiner Results</th><th width="60%">Notes</th></tr>
+        <tr><td width="40%">
+ <source><![CDATA[<config>
+  <gui>
+    <bgcolor>green</bgcolor>
+    <selcolor>yellow</selcolor>
+    <level default='2' min='1'>1</level>
+    <fgcolor>blue</fgcolor>
+  </gui>
+  <net>
+    <proxy>
+      <url>http://www.url1.org</url>
+      <url>http://www.url2.org</url>
+      <url>http://www.url3.org</url>
+    </proxy>
+    <service>
+      <url>http://service1.org</url>
+    </service>
+    <server>
+      <url>http://appsvr1.com</url>
+      <url>http://appsvr2.com</url>
+      <url>http://testsvr.com</url>
+      <url>http://backupsvr.com</url>
+    </server>
+  </net>
+  <base>
+    <services>
+      <security>
+        <login>
+          <user type='default'>Admin</user>
+          <passwd type='secret'>BeamMeUp</passwd>
+        </login>
+      </security>
+    </services>
+  </base>
+  <database>
+    <tables>
+      <table id='1'>
+        <name>documents</name>
+        <fields>
+          <field>
+            <name>docid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>docname</name>
+            <type>varchar</type>
+          </field>
+          <field>
+            <name>authorID</name>
+            <type>int</type>
+          </field>
+        </fields>
+      </table>
+    </tables>
+  </database>
+  <Channels>
+    <Channel id='1' type='half'>
+      <Name>My Channel</Name>
+    </Channel>
+    <Channel id='2'>
+      <MoreChannelData>more test 2 data</MoreChannelData>
+    </Channel>
+    <Channel id='3' type='half'>
+      <Name>Test Channel</Name>
+    </Channel>
+  </Channels>
+</config>
+]]></source></td><td width="60%">
+      <p>
+        The features that are significant in this file are:
+        <ul>
+          <li>In the gui section each of the child elements only appears once. The level element
+          merges the attributes from the two files and uses the element value of the first file.</li>
+          <li>In the security section the user type attribute was obtained from the second file
+          while the user value came from the first file. Alternately, the password type was
+          obtained from the first file while the value came from the second.</li>
+          <li>Only the data from table 1 was included.</li>
+          <li>Channel 1 in the first file completely overrode Channel 1 in the second file.</li>
+          <li>Channel 2 in the first file completely overrode Channel 2 in the second file. While
+          the attributes were merged in the case of the login elements the type attribute
+          was not merged in this case.</li>
+          <li>Again, only Channel 3 from the first file was included.</li>
+        </ul>
+      </p>
+      <p>
+        How the Channel elements ended up may not at first be obvious. The <code>OverrideCombiner</code>
+        simply noticed that the Channels element had three child elements named Channel and
+        used that to determine that only the contents of the Channels element in the first file
+        would be used.
+      </p></td></tr></table>
+      <p>
+        The next file is the the result of using the <code>UnionCombiner</code>
+      </p>
+      <table>
+        <tr>
+          <th width="40%">UnionCombiner Results</th>
+          <th width="60%">Notes</th>
+        </tr>
+        <tr><td width="40%">
+ <source><![CDATA[<config>
+  <gui>
+    <bgcolor>green</bgcolor>
+    <selcolor>yellow</selcolor>
+    <level default='2'>1</level>
+    <bgcolor>black</bgcolor>
+    <fgcolor>blue</fgcolor>
+    <level min='1'>4</level>
+  </gui>
+  <net>
+    <proxy>
+      <url>http://www.url1.org</url>
+      <url>http://www.url2.org</url>
+      <url>http://www.url3.org</url>
+    </proxy>
+    <service>
+      <url>http://service1.org</url>
+      <url type='2'>http://service2.org</url>
+      <url type='2'>http://service3.org</url>
+    </service>
+    <server></server>
+    <server>
+      <url>http://appsvr1.com</url>
+      <url>http://appsvr2.com</url>
+      <url>http://testsvr.com</url>
+      <url>http://backupsvr.com</url>
+    </server>
+  </net>
+  <base>
+    <services>
+      <security>
+        <login>
+          <user>Admin</user>
+          <passwd type='secret'></passwd>
+          <user type='default'>scotty</user>
+          <passwd>BeamMeUp</passwd>
+        </login>
+      </security>
+    </services>
+  </base>
+  <database>
+    <tables>
+      <table id='1' id='2'>
+        <name>documents</name>
+        <fields>
+          <field>
+            <name>docid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>docname</name>
+            <type>varchar</type>
+          </field>
+          <field>
+            <name>authorID</name>
+            <type>int</type>
+          </field>
+          <field>
+            <name>taskid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>taskname</name>
+            <type>varchar</type>
+          </field>
+        </fields>
+        <name>tasks</name>
+      </table>
+    </tables>
+  </database>
+  <Channels>
+    <Channel id='1' type='half'>
+      <Name>My Channel</Name>
+    </Channel>
+    <Channel id='2'>
+      <MoreChannelData>more test 2 data</MoreChannelData>
+    </Channel>
+    <Channel id='3' type='half'>
+      <Name>Test Channel</Name>
+    </Channel>
+    <Channel id='1'>
+      <Name>Channel 1</Name>
+      <ChannelData>test 1 data</ChannelData>
+    </Channel>
+    <Channel id='2' type='full'>
+      <Name>Channel 2</Name>
+      <ChannelData>test 2 data</ChannelData>
+    </Channel>
+    <Channel id='3' type='full'>
+      <Name>Channel 3</Name>
+      <ChannelData>test 3 data</ChannelData>
+    </Channel>
+  </Channels>
+</config>
+]]></source></td><td width="60%">
+      <p>
+        The feature that is significant in this file is rather obvious. It is just a simple
+        union of the contents of the two files.
+      </p>
+      </td></tr></table>
+      <p>
+        Finally, the last file is the result of using the <code>MergeCombiner</code>
+      </p>
+      <table>
+        <tr>
+          <th width="40%">MergeCombiner Results</th>
+          <th width="60%">Notes</th>
+        </tr>
+        <tr><td width="40%">
+<source><![CDATA[<config>
+  <gui>
+    <bgcolor>green</bgcolor>
+    <selcolor>yellow</selcolor>
+    <level default='2' min='1'>1</level>
+    <fgcolor>blue</fgcolor>
+  </gui>
+  <net>
+    <proxy>
+      <url>http://www.url1.org</url>
+      <url>http://www.url2.org</url>
+      <url>http://www.url3.org</url>
+    </proxy>
+    <service>
+      <url>http://service1.org</url>
+    </service>
+    <server>
+      <url>http://appsvr1.com</url>
+      <url>http://appsvr2.com</url>
+      <url>http://testsvr.com</url>
+      <url>http://backupsvr.com</url>
+    </server>
+  </net>
+  <base>
+    <services>
+      <security>
+        <login>
+          <user type='default'>Admin</user>
+          <passwd type='secret'></passwd>
+        </login>
+      </security>
+    </services>
+  </base>
+  <database>
+    <tables>
+      <table id='1'>
+        <name>documents</name>
+        <fields>
+          <field>
+            <name>docid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>docname</name>
+            <type>varchar</type>
+          </field>
+          <field>
+            <name>authorID</name>
+            <type>int</type>
+          </field>
+        </fields>
+      </table>
+      <table id='2'>
+        <name>tasks</name>
+        <fields>
+          <field>
+            <name>taskid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>taskname</name>
+            <type>varchar</type>
+          </field>
+        </fields>
+      </table>
+    </tables>
+  </database>
+  <Channels>
+    <Channel id='1' type='half'>
+      <Name>My Channel</Name>
+      <ChannelData>test 1 data</ChannelData>
+    </Channel>
+    <Channel id='2' type='full'>
+      <MoreChannelData>more test 2 data</MoreChannelData>
+      <Name>Channel 2</Name>
+      <ChannelData>test 2 data</ChannelData>
+    </Channel>
+    <Channel id='3' type='half'>
+      <Name>Test Channel</Name>
+    </Channel>
+    <Channel id='3' type='full'>
+      <Name>Channel 3</Name>
+      <ChannelData>test 3 data</ChannelData>
+    </Channel>
+  </Channels>
+</config>
+]]></source></td><td width="60%">
+      <p>
+        The features that are significant in this file are:
+        <ul>
+          <li>In the gui section the elements were merged.</li>
+          <li>In the net section the elements were merged, with the exception of the urls.</li>
+          <li>In the security section the user and password were merged. Notice that the
+          empty value for the password from the first file overrode the password in the
+          second file.</li>
+          <li>Both table elements appear</li>
+          <li>Channel 1 and Channel 2 were merged</li>
+          <li>Both Channel 3 elements appear as they were determined to not be the same.</li>
+        </ul>
+      </p>
+      <p>
+        When merging elements attributes play a critical role. If an element has an attribute that
+        appears in both sources, the value of that attribute must be the same for the elements to be
+        merged. Merging is only allowed between a single node in each of the files, so if an element
+        in the first file matches more than one element in the second file no merging will take
+        place and the element from the first file (and its contents) are included and the elements
+        in the second file are not. If the element is marked as a list node then the elements from
+        the second file will also be included.
+      </p></td></tr></table>
     </subsection>
 
 	<subsection name="Constructing a CombinedConfiguration">