Author: rgoers Date: Mon Mar 30 23:00:32 2009 New Revision: 760225 URL: http://svn.apache.org/viewvc?rev=760225&view=rev Log: CONFIGURATION-378 Add MergeCombiner
Added: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/MergeCombiner.java commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/tree/TestMergeCombiner.java Modified: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/NodeCombiner.java commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDefaultConfigurationBuilder.java commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiTenentConfigurationBuilder.xml commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine1.xml commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine2.xml commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_combinedconfiguration.xml Added: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/MergeCombiner.java URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/MergeCombiner.java?rev=760225&view=auto ============================================================================== --- commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/MergeCombiner.java (added) +++ commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/MergeCombiner.java Mon Mar 30 23:00:32 2009 @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.configuration2.tree; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ArrayList; + +/** + * <p> + * A specialized implementation of the <code>NodeCombiner</code> interface + * that performs a merge from two passed in node hierarchies. + * </p> + * <p> + * 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.7 + */ +public class MergeCombiner extends NodeCombiner +{ + /** + * Combines the given nodes to a new union node. + * + * @param node1 the first source node + * @param node2 the second source node + * @return the union node + */ + + 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); + + // Check if nodes can be combined + List children2 = new LinkedList(node2.getChildren()); + for (Iterator it = node1.getChildren().iterator(); it.hasNext();) + { + ConfigurationNode child1 = (ConfigurationNode) it.next(); + ConfigurationNode child2 = canCombine(node1, node2, child1, children2); + if (child2 != null) + { + result.addChild(doCombine(child1, child2)); + children2.remove(child2); + } + else + { + result.addChild(child1); + } + } + + // Add remaining children of node 2 + for (Iterator it = children2.iterator(); it.hasNext();) + { + result.addChild((ConfigurationNode) it.next()); + } + return result; + } + + /** + * Handles the attributes during a combination process. First all attributes + * of the first node will be added to the result. Then all attributes of the + * second node, which are not contained in the first node, will also be + * added. + * + * @param result the resulting node + * @param node1 the first node + * @param node2 the second node + */ + protected void addAttributes(ViewNode result, ConfigurationNode node1, + ConfigurationNode node2) + { + result.appendAttributes(node1); + for (Iterator it = node2.getAttributes().iterator(); it.hasNext();) + { + ConfigurationNode attr = (ConfigurationNode) it.next(); + if (node1.getAttributeCount(attr.getName()) == 0) + { + result.addAttribute(attr); + } + } + } + + /** + * 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 + * @param child the child node (of the first node) + * @return a child of the second node, with which a combination is possible + */ + protected ConfigurationNode canCombine(ConfigurationNode node1, + ConfigurationNode node2, ConfigurationNode child, List children2) + { + List attrs1 = child.getAttributes(); + List nodes = new ArrayList(); + + List children = node2.getChildren(child.getName()); + Iterator it = children.iterator(); + while (it.hasNext()) + { + 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); + } + } + + if (nodes.size() == 1) + { + 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/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/NodeCombiner.java URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/NodeCombiner.java?rev=760225&r1=760224&r2=760225&view=diff ============================================================================== --- commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/NodeCombiner.java (original) +++ commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/NodeCombiner.java Mon Mar 30 23:00:32 2009 @@ -20,6 +20,8 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; +import java.util.List; +import java.io.PrintStream; /** * <p> @@ -51,6 +53,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<String> listNodes; @@ -119,4 +124,51 @@ { 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()); + for (ConfigurationNode node : result.getAttributes()) + { + 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"); + for (ConfigurationNode node : result.getChildren()) + { + printTree(indent + " ", node); + } + newline = true; + } + if (newline) + { + debugStream.print(indent); + } + debugStream.println("</" + result.getName() + ">"); + } } Modified: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDefaultConfigurationBuilder.java URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDefaultConfigurationBuilder.java?rev=760225&r1=760224&r2=760225&view=diff ============================================================================== --- commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDefaultConfigurationBuilder.java (original) +++ commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDefaultConfigurationBuilder.java Mon Mar 30 23:00:32 2009 @@ -880,10 +880,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); @@ -895,7 +891,7 @@ HierarchicalConfiguration sub2 = config.configurationAt("Channels/chann...@id='2']"); assertEquals("Channel 2", sub2.getString("Name")); assertEquals("more test 2 data", sub2.getString("MoreChannelData")); - } */ + } public void testMerge() throws Exception { Added: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/tree/TestMergeCombiner.java URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/tree/TestMergeCombiner.java?rev=760225&view=auto ============================================================================== --- commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/tree/TestMergeCombiner.java (added) +++ commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/tree/TestMergeCombiner.java Mon Mar 30 23:00:32 2009 @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.configuration2.tree; + +import java.util.List; + +import org.apache.commons.configuration2.ConfigurationException; +import org.apache.commons.configuration2.HierarchicalConfiguration; +import org.apache.commons.configuration2.XMLConfiguration; +import org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine; + +/** + * Test class for MergeCombiner. + * + * @version $Id: $ + */ +public class TestMergeCombiner extends AbstractCombinerTest +{ + /** + * Creates the combiner. + * + * @return the combiner + */ + protected NodeCombiner createCombiner() + { + return new MergeCombiner(); + } + + /** + * Tests combination of simple elements. + */ + public void testSimpleValues() throws ConfigurationException + { + HierarchicalConfiguration config = createCombinedConfiguration(); + assertEquals("Wrong number of bgcolors", 0, config + .getMaxIndex("gui.bgcolor")); + assertEquals("Wrong bgcolor", "green", config.getString("gui.bgcolor")); + assertEquals("Wrong selcolor", "yellow", config + .getString("gui.selcolor")); + assertEquals("Wrong fgcolor", "blue", config.getString("gui.fgcolor")); + assertEquals("Wrong level", 1, config.getInt("gui.level")); + } + + /** + * Tests combination of attributes. + */ + public void testAttributes() throws ConfigurationException + { + HierarchicalConfiguration config = createCombinedConfiguration(); + assertEquals("Wrong value of min attribute", 1, config + .getInt("gui.lev...@min]")); + assertEquals("Wrong value of default attribute", 2, config + .getInt("gui.lev...@default]")); + assertEquals("Wrong number of id attributes", 0, config + .getMaxIndex("database.tables.table(0)[...@id]")); + assertEquals("Wrong value of table id", 1, config + .getInt("database.tables.table(0)[...@id]")); + } + + /** + * Tests whether property values are correctly overridden. + */ + public void testOverrideValues() throws ConfigurationException + { + HierarchicalConfiguration config = createCombinedConfiguration(); + assertEquals("Wrong user", "Admin", config + .getString("base.services.security.login.user")); + assertEquals("Wrong user type", "default", config + .getString("base.services.security.login.us...@type]")); + assertNull("Wrong password", config.getString("base.services.security.login.passwd")); + assertEquals("Wrong password type", "secret", config + .getString("base.services.security.login.pass...@type]")); + } + + /** + * Tests if a list from the first node structure overrides a list in the + * second structure. + */ + public void testListFromFirstStructure() throws ConfigurationException + { + HierarchicalConfiguration config = createCombinedConfiguration(); + assertEquals("Wrong number of services", 0, config + .getMaxIndex("net.service.url")); + assertEquals("Wrong service", "http://service1.org", config + .getString("net.service.url")); + assertFalse("Type attribute available", config + .containsKey("net.service.u...@type]")); + } + + /** + * Tests if a list from the second structure is added if it is not defined + * in the first structure. + */ + public void testListFromSecondStructure() throws ConfigurationException + { + HierarchicalConfiguration config = createCombinedConfiguration(); + assertEquals("Wrong number of servers", 3, config + .getMaxIndex("net.server.url")); + assertEquals("Wrong server", "http://testsvr.com", config + .getString("net.server.url(2)")); + } + + /** + * Tests the combination of the table structure. With the merge combiner + * both table 1 and table 2 should be present. + */ + public void testCombinedTable() throws ConfigurationException + { + checkTable(createCombinedConfiguration()); + } + + public void testMerge() throws ConfigurationException + { + //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/chann...@id='1']/Name")); + assertEquals("Bad Channel Type", "half", + config.getString("Channels/chann...@id='1']/@type")); + assertEquals("Bad Channel 2 Name", "Channel 2", + config.getString("Channels/chann...@id='2']/Name")); + assertEquals("Bad Channel Type", "full", + config.getString("Channels/chann...@id='2']/@type")); + assertEquals("Bad Channel Data", "test 1 data", + config.getString("Channels/chann...@id='1']/ChannelData")); + assertEquals("Bad Channel Data", "test 2 data", + config.getString("Channels/chann...@id='2']/ChannelData")); + assertEquals("Bad Channel Data", "more test 2 data", + config.getString("Channels/chann...@id='2']/MoreChannelData")); + + } + + /** + * Helper method for checking the combined table structure. + * + * @param config the config + * @return the node for the table element + */ + private ConfigurationNode checkTable(HierarchicalConfiguration config) + { + assertEquals("Wrong number of tables", 1, config + .getMaxIndex("database.tables.table")); + HierarchicalConfiguration c = config + .configurationAt("database.tables.table(0)"); + assertEquals("Wrong table name", "documents", c.getString("name")); + assertEquals("Wrong number of fields", 2, c + .getMaxIndex("fields.field.name")); + assertEquals("Wrong field", "docname", c + .getString("fields.field(1).name")); + + List nds = config.getExpressionEngine().query(config.getRootNode(), + "database.tables.table"); + assertFalse("No node found", nds.isEmpty()); + return (ConfigurationNode) nds.get(0); + } +} \ No newline at end of file Modified: commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiTenentConfigurationBuilder.xml URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiTenentConfigurationBuilder.xml?rev=760225&r1=760224&r2=760225&view=diff ============================================================================== --- commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiTenentConfigurationBuilder.xml (original) +++ commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiTenentConfigurationBuilder.xml Mon Mar 30 23:00:32 2009 @@ -5,6 +5,7 @@ <result delimiterParsingDisabled="true" forceReloadCheck="true" config-class="org.apache.commons.configuration2.DynamicCombinedConfiguration" keyPattern="$${sys:Id}"> + <nodeCombiner config-class="org.apache.commons.configuration2.tree.MergeCombiner"/> <expressionEngine config-class="org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine"/> </result> Modified: commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine1.xml URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine1.xml?rev=760225&r1=760224&r2=760225&view=diff ============================================================================== --- commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine1.xml (original) +++ commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine1.xml Mon Mar 30 23:00:32 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/branches/configuration2_experimental/src/test/resources/testcombine2.xml URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine2.xml?rev=760225&r1=760224&r2=760225&view=diff ============================================================================== --- commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine2.xml (original) +++ commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine2.xml Mon Mar 30 23:00:32 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/branches/configuration2_experimental/xdocs/changes.xml URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml?rev=760225&r1=760224&r2=760225&view=diff ============================================================================== --- commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml (original) +++ commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml Mon Mar 30 23:00:32 2009 @@ -85,6 +85,10 @@ </release> <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/branches/configuration2_experimental/xdocs/userguide/howto_combinedconfiguration.xml URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_combinedconfiguration.xml?rev=760225&r1=760224&r2=760225&view=diff ============================================================================== --- commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_combinedconfiguration.xml (original) +++ commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_combinedconfiguration.xml Mon Mar 30 23:00:32 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/configuration2/tree/OverrideCombiner.html">OverrideCombiner</a></code> + Configuration ships with three concrete implementations + <code><a href="../apidocs/org/apache/commons/configuration2/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/configuration2/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">