You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by oh...@apache.org on 2007/04/21 16:31:59 UTC
svn commit: r531038 - in /jakarta/commons/proper/configuration/trunk:
src/java/org/apache/commons/configuration/
src/test/org/apache/commons/configuration/ xdocs/ xdocs/userguide/
Author: oheger
Date: Sat Apr 21 07:31:58 2007
New Revision: 531038
URL: http://svn.apache.org/viewvc?view=rev&rev=531038
Log:
CONFIGURATION-264: Added a new mode to SubnodeConfiguration, in which it checks for structural changes of its parent. In this mode it is able to detect reloads, too.
Modified:
jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalConfiguration.java
jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/SubnodeConfiguration.java
jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestSubnodeConfiguration.java
jakarta/commons/proper/configuration/trunk/xdocs/changes.xml
jakarta/commons/proper/configuration/trunk/xdocs/userguide/howto_xml.xml
Modified: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalConfiguration.java
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalConfiguration.java?view=diff&rev=531038&r1=531037&r2=531038
==============================================================================
--- jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalConfiguration.java (original)
+++ jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/HierarchicalConfiguration.java Sat Apr 21 07:31:58 2007
@@ -458,13 +458,36 @@
* <code>SubnodeConfiguration</code> class to obtain further information
* about subnode configurations and when they should be used.
* </p>
+ * <p>
+ * With the <code>supportUpdate</code> flag the behavior of the returned
+ * <code>SubnodeConfiguration</code> regarding updates of its parent
+ * configuration can be determined. A subnode configuration operates on the
+ * same nodes as its parent, so changes at one configuration are normally
+ * directly visible for the other configuration. There are however changes
+ * of the parent configuration, which are not recognized by the subnode
+ * configuration per default. An example for this is a reload operation (for
+ * file-based configurations): Here the complete node set of the parent
+ * configuration is replaced, but the subnode configuration still references
+ * the old nodes. If such changes should be detected by the subnode
+ * configuration, the <code>supportUpdates</code> flag must be set to
+ * <b>true</b>. This causes the subnode configuration to reevaluate the key
+ * used for its creation each time it is accessed. This guarantees that the
+ * subnode configuration always stays in sync with its key, even if the
+ * parent configuration's data significantly changes. If such a change
+ * makes the key invalid - because it now no longer points to exactly one
+ * node -, the subnode configuration is not reconstructed, but keeps its
+ * old data. It is then quasi detached from its parent.
+ * </p>
*
* @param key the key that selects the sub tree
+ * @param supportUpdates a flag whether the returned subnode configuration
+ * should be able to handle updates of its parent
* @return a hierarchical configuration that contains this sub tree
* @see SubnodeConfiguration
- * @since 1.3
+ * @since 1.5
*/
- public SubnodeConfiguration configurationAt(String key)
+ public SubnodeConfiguration configurationAt(String key,
+ boolean supportUpdates)
{
List nodes = fetchNodeList(key);
if (nodes.size() != 1)
@@ -472,7 +495,24 @@
throw new IllegalArgumentException(
"Passed in key must select exactly one node: " + key);
}
- return createSubnodeConfiguration((ConfigurationNode) nodes.get(0));
+ return supportUpdates ? createSubnodeConfiguration(
+ (ConfigurationNode) nodes.get(0), key)
+ : createSubnodeConfiguration((ConfigurationNode) nodes.get(0));
+ }
+
+ /**
+ * Returns a hierarchical subnode configuration for the node specified by
+ * the given key. This is a short form for <code>configurationAt(key,
+ * <b>false</b>)</code>.
+ *
+ * @param key the key that selects the sub tree
+ * @return a hierarchical configuration that contains this sub tree
+ * @see SubnodeConfiguration
+ * @since 1.3
+ */
+ public SubnodeConfiguration configurationAt(String key)
+ {
+ return configurationAt(key, false);
}
/**
@@ -525,6 +565,24 @@
protected SubnodeConfiguration createSubnodeConfiguration(ConfigurationNode node)
{
return new SubnodeConfiguration(this, node);
+ }
+
+ /**
+ * Creates a new subnode configuration for the specified node and sets its
+ * construction key. A subnode configuration created this way will be aware
+ * of structural changes of its parent.
+ *
+ * @param node the node, for which a subnode configuration is to be created
+ * @param subnodeKey the key used to construct the configuration
+ * @return the configuration for the given node
+ * @since 1.5
+ */
+ protected SubnodeConfiguration createSubnodeConfiguration(
+ ConfigurationNode node, String subnodeKey)
+ {
+ SubnodeConfiguration result = createSubnodeConfiguration(node);
+ result.setSubnodeKey(subnodeKey);
+ return result;
}
/**
Modified: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/SubnodeConfiguration.java
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/SubnodeConfiguration.java?view=diff&rev=531038&r1=531037&r2=531038
==============================================================================
--- jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/SubnodeConfiguration.java (original)
+++ jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/SubnodeConfiguration.java Sat Apr 21 07:31:58 2007
@@ -16,6 +16,11 @@
*/
package org.apache.commons.configuration;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
import org.apache.commons.configuration.tree.ConfigurationNode;
/**
@@ -43,6 +48,48 @@
* configuration's root node is involved.
* </p>
* <p>
+ * There are however changes at the parent configuration, which cause the
+ * subnode configuration to become detached. An example for such a change is a
+ * reload operation of a file-based configuration, which replaces all nodes of
+ * the parent configuration. The subnode configuration per default still
+ * references the old nodes. Another example are list structures: a subnode
+ * configuration can be created to point on the <em>i</em>th element of the
+ * list. Now list elements can be added or removed, so that the list elements'
+ * indices change. In such a scenario the subnode configuration would always
+ * point to the same list element, regardless of its current index.
+ * </p>
+ * <p>
+ * To solve these problems and make a subnode configuration aware of
+ * such structural changes of its parent, it is possible to associate a
+ * subnode configuration with a configuration key. This can be done by calling
+ * the <code>setSubnodeKey()</code> method. If here a key is set, the subnode
+ * configuration will evaluate it on each access, thus ensuring that it is
+ * always in sync with its parent. In this mode the subnode configuration really
+ * behaves like a live-view on its parent. The price for this is a decreased
+ * performance because now an additional evaluation has to be performed on each
+ * property access. So this mode should only be used if necessary; if for
+ * instance a subnode configuration is only used for a temporary convenient
+ * access to a complex configuration, there is no need to make it aware for
+ * structural changes of its parent. If a subnode configuration is created
+ * using the <code>{@link HierarchicalConfiguration#configurationAt(String, boolean)
+ * configurationAt()}</code> method of <code>HierarchicalConfiguration</code>
+ * (which should be the preferred way), with an additional boolean parameter it
+ * can be specified whether the resulting subnode configuration should be
+ * aware of structural changes or not. Then the configuration key will be
+ * automatically set.
+ * </p>
+ * <p>
+ * <em>Note:</em> At the moment support for creating a subnode configuration
+ * that is aware of structural changes of its parent from another subnode
+ * configuration (a "sub subnode configuration") is limited. This only works if
+ * <ol><li>the subnode configuration that serves as the parent for the new
+ * subnode configuration is itself associated with a configuration key and</li>
+ * <li>the key passed in to create the new subnode configuration is not too
+ * complex (if configuration keys are used that contain indices, a corresponding
+ * key that is valid from the parent configuration's point of view cannot be
+ * constructed).</li></ol>
+ * </p>
+ * <p>
* When a subnode configuration is created, it inherits the settings of its
* parent configuration, e.g. some flags like the
* <code>throwExceptionOnMissing</code> flag or the settings for handling list
@@ -76,6 +123,9 @@
/** Stores the parent configuration. */
private HierarchicalConfiguration parent;
+ /** Stores the key that was used to construct this configuration.*/
+ private String subnodeKey;
+
/**
* Creates a new instance of <code>SubnodeConfiguration</code> and
* initializes it with the parent configuration and the new root node.
@@ -111,6 +161,80 @@
}
/**
+ * Returns the key that was used to construct this configuration. If here a
+ * non-<b>null</b> value is returned, the subnode configuration will
+ * always check its parent for structural changes and reconstruct itself if
+ * necessary.
+ *
+ * @return the key for selecting this configuration's root node
+ * @since 1.5
+ */
+ public String getSubnodeKey()
+ {
+ return subnodeKey;
+ }
+
+ /**
+ * Sets the key to the root node of this subnode configuration. If here a
+ * key is set, the subnode configuration will behave like a live-view on its
+ * parent for this key. See the class comment for more details.
+ *
+ * @param subnodeKey the key used to construct this configuration
+ * @since 1.5
+ */
+ public void setSubnodeKey(String subnodeKey)
+ {
+ this.subnodeKey = subnodeKey;
+ }
+
+ /**
+ * Returns the root node for this configuration. If a subnode key is set,
+ * this implementation re-evaluates this key to find out if this subnode
+ * configuration needs to be reconstructed. This ensures that the subnode
+ * configuration is always synchronized with its parent configuration.
+ *
+ * @return the root node of this configuration
+ * @since 1.5
+ * @see #setSubnodeKey(String)
+ */
+ public ConfigurationNode getRootNode()
+ {
+ if (getSubnodeKey() != null)
+ {
+ try
+ {
+ List nodes = getParent().fetchNodeList(getSubnodeKey());
+ if (nodes.size() != 1)
+ {
+ // key is invalid, so detach this subnode configuration
+ setSubnodeKey(null);
+ }
+ else
+ {
+ ConfigurationNode currentRoot = (ConfigurationNode) nodes
+ .get(0);
+ if (currentRoot != super.getRootNode())
+ {
+ // the root node was changed due to a change of the
+ // parent
+ setRootNode(currentRoot);
+ }
+ return currentRoot;
+ }
+ }
+ catch (Exception ex)
+ {
+ // Evaluation of the key caused an exception. Probably the
+ // expression engine has changed on the parent. Detach this
+ // configuration, there is not much we can do about this.
+ setSubnodeKey(null);
+ }
+ }
+
+ return super.getRootNode(); // use stored root node
+ }
+
+ /**
* Returns a hierarchical configuration object for the given sub node.
* This implementation will ensure that the returned
* <code>SubnodeConfiguration</code> object will have the same parent than
@@ -122,6 +246,53 @@
protected SubnodeConfiguration createSubnodeConfiguration(ConfigurationNode node)
{
return new SubnodeConfiguration(getParent(), node);
+ }
+
+ /**
+ * Returns a hierarchical configuration object for the given sub node that
+ * is aware of structural changes of its parent. Works like the method with
+ * the same name, but also sets the subnode key for the new subnode
+ * configuration, so it can check whether the parent has been changed. This
+ * only works if this subnode configuration has itself a valid subnode key.
+ * So if a subnode configuration that should be aware of structural changes
+ * is created from an already existing subnode configuration, this subnode
+ * configuration must also be aware of such changes.
+ *
+ * @param node the sub node, for which the configuration is to be created
+ * @param subnodeKey the construction key
+ * @return a hierarchical configuration for this sub node
+ * @since 1.5
+ */
+ protected SubnodeConfiguration createSubnodeConfiguration(
+ ConfigurationNode node, String subnodeKey)
+ {
+ SubnodeConfiguration result = createSubnodeConfiguration(node);
+
+ if (getSubnodeKey() != null)
+ {
+ // construct the correct subnode key
+ // determine path to root node
+ List lstPathToRoot = new ArrayList();
+ ConfigurationNode top = super.getRootNode();
+ ConfigurationNode nd = node;
+ while (nd != top)
+ {
+ lstPathToRoot.add(nd);
+ nd = nd.getParentNode();
+ }
+
+ // construct the keys for the nodes on this path
+ Collections.reverse(lstPathToRoot);
+ String key = getSubnodeKey();
+ for (Iterator it = lstPathToRoot.iterator(); it.hasNext();)
+ {
+ key = getParent().getExpressionEngine().nodeKey(
+ (ConfigurationNode) it.next(), key);
+ }
+ result.setSubnodeKey(key);
+ }
+
+ return result;
}
/**
Modified: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestSubnodeConfiguration.java
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestSubnodeConfiguration.java?view=diff&rev=531038&r1=531037&r2=531038
==============================================================================
--- jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestSubnodeConfiguration.java (original)
+++ jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestSubnodeConfiguration.java Sat Apr 21 07:31:58 2007
@@ -16,12 +16,14 @@
*/
package org.apache.commons.configuration;
+import java.io.File;
import java.util.HashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.configuration.reloading.FileAlwaysReloadingStrategy;
import org.apache.commons.configuration.tree.ConfigurationNode;
import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
@@ -45,6 +47,12 @@
{ "docid", "docname", "author", "dateOfCreation", "version", "size" },
{ "userid", "uname", "firstName", "lastName" } };
+ /** Constant for a test output file.*/
+ private static final File TEST_FILE = new File("target/test.xml");
+
+ /** Constant for an updated table name.*/
+ private static final String NEW_TABLE_NAME = "newTable";
+
/** The parent configuration. */
HierarchicalConfiguration parent;
@@ -64,6 +72,15 @@
nodeCounter = 0;
}
+ protected void tearDown() throws Exception
+ {
+ // remove the test output file if necessary
+ if (TEST_FILE.exists())
+ {
+ TEST_FILE.delete();
+ }
+ }
+
/**
* Tests creation of a subnode config.
*/
@@ -311,6 +328,134 @@
assertEquals("Wrong interpolation in subnode",
"/home/foo/path" + i, sub.getString("dir" + i));
}
+ }
+
+ /**
+ * Tests a reload operation for the parent configuration when the subnode
+ * configuration does not support reloads. Then the new value should not be
+ * detected.
+ */
+ public void testParentReloadNotSupported() throws ConfigurationException
+ {
+ Configuration c = setUpReloadTest(false);
+ assertEquals("Name changed in sub config", TABLE_NAMES[1], config
+ .getString("name"));
+ assertEquals("Name not changed in parent", NEW_TABLE_NAME, c
+ .getString("tables.table(1).name"));
+ }
+
+ /**
+ * Tests a reload operation for the parent configuration when the subnode
+ * configuration does support reloads. The new value should be returned.
+ */
+ public void testParentReloadSupported() throws ConfigurationException
+ {
+ Configuration c = setUpReloadTest(true);
+ assertEquals("Name not changed in sub config", NEW_TABLE_NAME, config
+ .getString("name"));
+ assertEquals("Name not changed in parent", NEW_TABLE_NAME, c
+ .getString("tables.table(1).name"));
+ }
+
+ /**
+ * Tests a reload operation for the parent configuration when the subnode
+ * configuration is aware of reloads, and the parent configuration is
+ * accessed first. The new value should be returned.
+ */
+ public void testParentReloadSupportAccessParent()
+ throws ConfigurationException
+ {
+ Configuration c = setUpReloadTest(true);
+ assertEquals("Name not changed in parent", NEW_TABLE_NAME, c
+ .getString("tables.table(1).name"));
+ assertEquals("Name not changed in sub config", NEW_TABLE_NAME, config
+ .getString("name"));
+ }
+
+ /**
+ * Tests whether reloads work with sub subnode configurations.
+ */
+ public void testParentReloadSubSubnode() throws ConfigurationException
+ {
+ setUpReloadTest(true);
+ SubnodeConfiguration sub = config.configurationAt("fields", true);
+ assertEquals("Wrong subnode key", "tables.table(1).fields", sub
+ .getSubnodeKey());
+ assertEquals("Changed field not detected", "newField", sub
+ .getString("field(0).name"));
+ }
+
+ /**
+ * Tests creating a sub sub config when the sub config is not aware of
+ * changes. Then the sub sub config shouldn't be either.
+ */
+ public void testParentReloadSubSubnodeNoChangeSupport()
+ throws ConfigurationException
+ {
+ setUpReloadTest(false);
+ SubnodeConfiguration sub = config.configurationAt("fields", true);
+ assertNull("Sub sub config is attached to parent", sub.getSubnodeKey());
+ assertEquals("Changed field name returned", TABLE_FIELDS[1][0], sub
+ .getString("field(0).name"));
+ }
+
+ /**
+ * Prepares a test for a reload operation.
+ *
+ * @param supportReload a flag whether the subnode configuration should
+ * support reload operations
+ * @return the parent configuration that can be used for testing
+ * @throws ConfigurationException if an error occurs
+ */
+ private XMLConfiguration setUpReloadTest(boolean supportReload)
+ throws ConfigurationException
+ {
+ XMLConfiguration xmlConf = new XMLConfiguration(parent);
+ xmlConf.setFile(TEST_FILE);
+ xmlConf.save();
+ config = xmlConf.configurationAt("tables.table(1)", supportReload);
+ assertEquals("Wrong table name", TABLE_NAMES[1], config
+ .getString("name"));
+ xmlConf.setReloadingStrategy(new FileAlwaysReloadingStrategy());
+ // Now change the configuration file
+ XMLConfiguration confUpdate = new XMLConfiguration(TEST_FILE);
+ confUpdate.setProperty("tables.table(1).name", NEW_TABLE_NAME);
+ confUpdate.setProperty("tables.table(1).fields.field(0).name",
+ "newField");
+ confUpdate.save();
+ return xmlConf;
+ }
+
+ /**
+ * Tests a manipulation of the parent configuration that causes the subnode
+ * configuration to become invalid. In this case the sub config should be
+ * detached and keep its old values.
+ */
+ public void testParentChangeDetach()
+ {
+ final String key = "tables.table(1)";
+ config = parent.configurationAt(key, true);
+ assertEquals("Wrong subnode key", key, config.getSubnodeKey());
+ assertEquals("Wrong table name", TABLE_NAMES[1], config
+ .getString("name"));
+ parent.clearTree(key);
+ assertEquals("Wrong table name after change", TABLE_NAMES[1], config
+ .getString("name"));
+ assertNull("Sub config was not detached", config.getSubnodeKey());
+ }
+
+ /**
+ * Tests detaching a subnode configuration when an exception is thrown
+ * during reconstruction. This can happen e.g. if the expression engine is
+ * changed for the parent.
+ */
+ public void testParentChangeDetatchException()
+ {
+ config = parent.configurationAt("tables.table(1)", true);
+ parent.setExpressionEngine(new XPathExpressionEngine());
+ assertEquals("Wrong name of table", TABLE_NAMES[1], config
+ .getString("name"));
+ assertNull("Sub config was not detached", config.getSubnodeKey());
}
/**
Modified: jakarta/commons/proper/configuration/trunk/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/configuration/trunk/xdocs/changes.xml?view=diff&rev=531038&r1=531037&r2=531038
==============================================================================
--- jakarta/commons/proper/configuration/trunk/xdocs/changes.xml (original)
+++ jakarta/commons/proper/configuration/trunk/xdocs/changes.xml Sat Apr 21 07:31:58 2007
@@ -23,6 +23,13 @@
<body>
<release version="1.5-SNAPSHOT" date="in SVN" description="">
+ <action dev="oheger" type="add" issue="CONFIGURATION-264">
+ A SubnodeConfiguration per default does not see certain changes of its
+ parent configuration (e.g. reloads). With a new boolean parameter of
+ HierarchicalConfiguration's configurationAt() method a mode can be
+ enabled, in which the subnode configuration checks for such changes and
+ reconstructs itself if necessary.
+ </action>
<action dev="ebourg" type="fix">
byte[] properties are properly saved as data fields in the plist
configurations (PropertyListConfiguration and XMLPropertyListConfiguration).
Modified: jakarta/commons/proper/configuration/trunk/xdocs/userguide/howto_xml.xml
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/configuration/trunk/xdocs/userguide/howto_xml.xml?view=diff&rev=531038&r1=531037&r2=531038
==============================================================================
--- jakarta/commons/proper/configuration/trunk/xdocs/userguide/howto_xml.xml (original)
+++ jakarta/commons/proper/configuration/trunk/xdocs/userguide/howto_xml.xml Sat Apr 21 07:31:58 2007
@@ -393,6 +393,14 @@
String fieldType = sub.getString("type");
...
]]></source>
+ <p>
+ The configurations returned by the <code>configurationAt()</code> and
+ <code>configurationsAt()</code> method are in fact instances of the
+ <a href="apidocs/org/apache/commons/configuration/SubnodeConfiguration.html">
+ <code>SubnodeConfiguration</code></a> class. The API documentation of
+ this class contains more information about its features and
+ limitations.
+ </p>
</subsection>
<subsection name="Adding new properties">
<p>
---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org