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">