Author: oheger
Date: Fri May 10 19:16:40 2013
New Revision: 1481154
URL: http://svn.apache.org/r1481154
Log:
Reworked SubnodeConfiguration to use a Synchronizer.
A SubnodeConfiguration per default shares its Synchronizer with its parent
because both configurations operate on the same node set. In the past
SubnodeConfiguration instances created with a subnode key (for detecting
structural changes of its parent) checked their root nodes on each read
access. Now such checks are performed when the parent configuration is
changed.
Modified:
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/BaseHierarchicalConfiguration.java
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/INIConfiguration.java
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/SubnodeConfiguration.java
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestBaseHierarchicalConfigurationSynchronization.java
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestHierarchicalConfiguration.java
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestSubnodeConfiguration.java
Modified:
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/BaseHierarchicalConfiguration.java
URL:
http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/BaseHierarchicalConfiguration.java?rev=1481154&r1=1481153&r2=1481154&view=diff
==============================================================================
---
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/BaseHierarchicalConfiguration.java
(original)
+++
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/BaseHierarchicalConfiguration.java
Fri May 10 19:16:40 2013
@@ -21,12 +21,15 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.Stack;
+import java.util.WeakHashMap;
import org.apache.commons.configuration.event.ConfigurationEvent;
import org.apache.commons.configuration.event.ConfigurationListener;
@@ -177,11 +180,20 @@ public class BaseHierarchicalConfigurati
private transient ExpressionEngine expressionEngine;
/**
+ * A map for managing the {@code SubnodeConfiguration} instances created
+ * from this configuration.
+ */
+ private Map<SubnodeConfiguration, Object> subConfigs;
+
+ /** A listener for reacting on changes to update sub configurations. */
+ private ConfigurationListener changeListener;
+
+ /**
* Creates a new instance of {@code BaseHierarchicalConfiguration}.
*/
public BaseHierarchicalConfiguration()
{
- rootNode = new DefaultConfigurationNode();
+ this(new DefaultConfigurationNode());
}
/**
@@ -195,11 +207,19 @@ public class BaseHierarchicalConfigurati
*/
public BaseHierarchicalConfiguration(HierarchicalConfiguration c)
{
- this();
- if (c != null)
- {
- rootNode = copyRootNode(c);
- }
+ this(copyRootNode(c));
+ }
+
+ /**
+ * Creates a new instance of {@code BaseHierarchicalConfiguration} with the
+ * passed in node as root node.
+ *
+ * @param root the root node (not <b>null</b>)
+ * @since 2.0
+ */
+ protected BaseHierarchicalConfiguration(ConfigurationNode root)
+ {
+ rootNode = root;
}
/**
@@ -627,15 +647,22 @@ public class BaseHierarchicalConfigurati
public SubnodeConfiguration configurationAt(String key,
boolean supportUpdates)
{
- List<ConfigurationNode> nodes = fetchNodeList(key);
- if (nodes.size() != 1)
+ beginWrite();
+ try
{
- throw new IllegalArgumentException(
- "Passed in key must select exactly one node: " + key);
+ List<ConfigurationNode> nodes = fetchNodeList(key);
+ if (nodes.size() != 1)
+ {
+ throw new IllegalArgumentException(
+ "Passed in key must select exactly one node: " + key);
+ }
+ return createAndInitializeSubnodeConfiguration(nodes.get(0), key,
+ supportUpdates);
+ }
+ finally
+ {
+ endWrite();
}
- return supportUpdates ? createSubnodeConfiguration(
- nodes.get(0), key)
- : createSubnodeConfiguration(nodes.get(0));
}
/**
@@ -706,13 +733,23 @@ public class BaseHierarchicalConfigurati
*/
public List<SubnodeConfiguration> configurationsAt(String key)
{
- List<ConfigurationNode> nodes = fetchNodeList(key);
- List<SubnodeConfiguration> configs = new
ArrayList<SubnodeConfiguration>(nodes.size());
- for (ConfigurationNode node : nodes)
+ beginWrite();
+ try
+ {
+ List<ConfigurationNode> nodes = fetchNodeList(key);
+ List<SubnodeConfiguration> configs =
+ new ArrayList<SubnodeConfiguration>(nodes.size());
+ for (ConfigurationNode node : nodes)
+ {
+ configs.add(createAndInitializeSubnodeConfiguration(node, null,
+ false));
+ }
+ return configs;
+ }
+ finally
{
- configs.add(createSubnodeConfiguration(node));
+ endWrite();
}
- return configs;
}
/**
@@ -734,20 +771,29 @@ public class BaseHierarchicalConfigurati
*/
public List<SubnodeConfiguration> childConfigurationsAt(String key)
{
- List<ConfigurationNode> nodes = fetchNodeList(key);
- if (nodes.size() != 1)
+ beginWrite();
+ try
{
- return Collections.emptyList();
- }
+ List<ConfigurationNode> nodes = fetchNodeList(key);
+ if (nodes.size() != 1)
+ {
+ return Collections.emptyList();
+ }
- ConfigurationNode parent = nodes.get(0);
- List<SubnodeConfiguration> subs =
- new ArrayList<SubnodeConfiguration>(parent.getChildrenCount());
- for (ConfigurationNode c : parent.getChildren())
+ ConfigurationNode parent = nodes.get(0);
+ List<SubnodeConfiguration> subs =
+ new ArrayList<SubnodeConfiguration>(
+ parent.getChildrenCount());
+ for (ConfigurationNode c : parent.getChildren())
+ {
+ subs.add(createAndInitializeSubnodeConfiguration(c, null,
false));
+ }
+ return subs;
+ }
+ finally
{
- subs.add(createSubnodeConfiguration(c));
+ endWrite();
}
- return subs;
}
/**
@@ -763,27 +809,13 @@ public class BaseHierarchicalConfigurati
}
/**
- * Creates a subnode configuration for the specified node. This method is
- * called by {@code configurationAt()} and
- * {@code configurationsAt()}.
- *
- * @param node the node, for which a subnode configuration is to be created
- * @return the configuration for the given node
- * @since 1.3
- */
- protected SubnodeConfiguration
createSubnodeConfiguration(ConfigurationNode node)
- {
- SubnodeConfiguration result = new SubnodeConfiguration(this, node);
- registerSubnodeConfiguration(result);
- return result;
- }
-
- /**
- * 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.
+ * Creates a new {@code SubnodeConfiguration} for the specified node and
+ * sets its construction key. If the key is not <b>null</b>, a
+ * {@code SubnodeConfiguration} 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 node the node, for which a {@code SubnodeConfiguration} is to be
+ * created
* @param subnodeKey the key used to construct the configuration
* @return the configuration for the given node
* @since 1.5
@@ -791,9 +823,7 @@ public class BaseHierarchicalConfigurati
protected SubnodeConfiguration createSubnodeConfiguration(
ConfigurationNode node, String subnodeKey)
{
- SubnodeConfiguration result = createSubnodeConfiguration(node);
- result.setSubnodeKey(subnodeKey);
- return result;
+ return new SubnodeConfiguration(this, node, subnodeKey);
}
/**
@@ -811,22 +841,117 @@ public class BaseHierarchicalConfigurati
}
/**
- * Registers this instance at the given subnode configuration. This
- * implementation will register a change listener, so that modifications of
- * the subnode configuration can be tracked.
+ * Creates a new {@code SubnodeConfiguration} instance from this
+ * configuration and initializes it. This method also takes care that data
+ * structures are created to manage all {@code SubnodeConfiguration}
+ * instances with support for updates. They are stored, so that they can be
+ * triggered when this configuration is changed.
+ *
+ * @param node the root node of the new {@code SubnodeConfiguration}
+ * @param key the key to this node
+ * @param supportUpdates a flag whether updates are supported
+ * @return the newly created and initialized {@code SubnodeConfiguration}
+ * @since 2.0
+ */
+ protected final SubnodeConfiguration
createAndInitializeSubnodeConfiguration(
+ ConfigurationNode node, String key, boolean supportUpdates)
+ {
+ String subnodeKey = supportUpdates ? key : null;
+ SubnodeConfiguration sub = createSubnodeConfiguration(node,
subnodeKey);
+
+ if (changeListener == null)
+ {
+ changeListener = createChangeListener();
+ subConfigs = new WeakHashMap<SubnodeConfiguration, Object>();
+ addConfigurationListener(changeListener);
+ }
+ sub.addConfigurationListener(changeListener);
+ sub.initSubConfigManagementData(subConfigs, changeListener);
+ sub.setSynchronizer(getSynchronizer());
+
+ if (supportUpdates)
+ {
+ // store this configuration so it can later be validated
+ subConfigs.put(sub, Boolean.TRUE);
+ }
+ return sub;
+ }
+
+ /**
+ * Initializes the data related to the management of
+ * {@code SubnodeConfiguration} instances. This method is called each time
a
+ * new {@code SubnodeConfiguration} was created. A configuration and its
+ * {@code SubnodeConfiguration} instances operate on the same set of data.
*
- * @param config the subnode configuration
- * @since 1.5
+ * @param subMap the map with all {@code SubnodeConfiguration} instances
+ * @param listener the listener for reacting on changes
+ */
+ void initSubConfigManagementData(Map<SubnodeConfiguration, Object> subMap,
+ ConfigurationListener listener)
+ {
+ subConfigs = subMap;
+ changeListener = listener;
+ }
+
+ /**
+ * Creates a listener which reacts on all changes on this configuration or
+ * one of its {@code SubnodeConfiguration} instances. If such a change is
+ * detected, some updates have to be performed.
+ *
+ * @return the newly created change listener
*/
- void registerSubnodeConfiguration(SubnodeConfiguration config)
+ private ConfigurationListener createChangeListener()
{
- config.addConfigurationListener(new ConfigurationListener()
+ return new ConfigurationListener()
{
public void configurationChanged(ConfigurationEvent event)
{
- subnodeConfigurationChanged(event);
+ nodeStructureChanged(event);
}
- });
+ };
+ }
+
+ /**
+ * A change on the node structure of this configuration has been detected.
+ * This can be caused either by an update of this configuration or by one
if
+ * its {@code SubnodeConfiguration} instances. This method calls
+ * {@link #subnodeConfigurationChanged(ConfigurationEvent)} if necessary
and
+ * ensures that all {@code SubnodeConfiguration} instances are validated.
+ * Note: when this method is called, a write lock is held on this
+ * configuration.
+ *
+ * @param event the change event
+ */
+ private void nodeStructureChanged(ConfigurationEvent event)
+ {
+ if (this != event.getSource())
+ {
+ subnodeConfigurationChanged(event);
+ }
+
+ if (!event.isBeforeUpdate() && EVENT_SUBNODE_CHANGED !=
event.getType())
+ {
+ validSubnodeConfigurations(event);
+ }
+ }
+
+ /**
+ * Triggers validation on all {@code SubnodeConfiguration} instances
created
+ * by this configuration.
+ *
+ * @param event the change event
+ */
+ private void validSubnodeConfigurations(ConfigurationEvent event)
+ {
+ Set<SubnodeConfiguration> subs =
+ new HashSet<SubnodeConfiguration>(subConfigs.keySet());
+ for (SubnodeConfiguration sub : subs)
+ {
+ if (sub != event.getSource())
+ {
+ sub.validateRootNode();
+ }
+ }
}
/**
@@ -1264,11 +1389,16 @@ public class BaseHierarchicalConfigurati
/**
* Creates a copy of the node structure of the passed in configuration.
*
- * @param c the configuration whose nodes are to be copied
+ * @param c the configuration whose nodes are to be copied (may be
<b>null</b>)
* @return the copied root node
*/
private static ConfigurationNode copyRootNode(HierarchicalConfiguration c)
{
+ if (c == null)
+ {
+ return new DefaultConfigurationNode();
+ }
+
CloneVisitor visitor = new CloneVisitor();
c.lock(LockMode.READ);
try
Modified:
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/INIConfiguration.java
URL:
http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/INIConfiguration.java?rev=1481154&r1=1481153&r2=1481154&view=diff
==============================================================================
---
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/INIConfiguration.java
(original)
+++
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/INIConfiguration.java
Fri May 10 19:16:40 2013
@@ -274,7 +274,7 @@ public class INIConfiguration extends Ba
out.print(section);
out.print("]");
out.println();
- subset = createSubnodeConfiguration(getSectionNode(section));
+ subset = createSubnodeConfiguration(getSectionNode(section),
null);
}
else
{
@@ -779,7 +779,7 @@ public class INIConfiguration extends Ba
{
// the passed in key does not map to exactly one node
// obtain the node for the section, create it on demand
- return new SubnodeConfiguration(this, getSectionNode(name));
+ return
createAndInitializeSubnodeConfiguration(getSectionNode(name), null, false);
}
}
}
@@ -827,7 +827,7 @@ public class INIConfiguration extends Ba
}
}
- return createSubnodeConfiguration(parent);
+ return createAndInitializeSubnodeConfiguration(parent, null, false);
}
/**
Modified:
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/SubnodeConfiguration.java
URL:
http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/SubnodeConfiguration.java?rev=1481154&r1=1481153&r2=1481154&view=diff
==============================================================================
---
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/SubnodeConfiguration.java
(original)
+++
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/SubnodeConfiguration.java
Fri May 10 19:16:40 2013
@@ -20,7 +20,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import org.apache.commons.configuration.reloading.Reloadable;
import org.apache.commons.configuration.tree.ConfigurationNode;
/**
@@ -50,9 +49,8 @@ import org.apache.commons.configuration.
* <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
+ * {@code clearTree()} operation, which replaces the sub tree to which this
+ * configuration's root node belongs. 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
@@ -66,8 +64,8 @@ import org.apache.commons.configuration.
* 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
+ * performance because now additional evaluation has to be performed to keep
+ * the root node up-to-date. 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
@@ -95,10 +93,19 @@ import org.apache.commons.configuration.
* {@code throwExceptionOnMissing} flag or the settings for handling list
* delimiters) or the expression engine. If these settings are changed later in
* either the subnode or the parent configuration, the changes are not visible
- * for each other. So you could create a subnode configuration, change its
+ * for each other. So you could create a subnode configuration, and change its
* expression engine without affecting the parent configuration.
* </p>
* <p>
+ * Because the {@code SubnodeConfiguration} operates on the same nodes
+ * structure as its parent it uses the same {@code Synchronizer} instance per
+ * default. This means that locks held on one {@code SubnodeConfiguration}
+ * also impact the parent configuration and all of its other {@code
SubnodeConfiguration}
+ * objects. You should not change this without a good reason! Otherwise, there
+ * is the risk of data corruption when multiple threads access these
+ * configuration concurrently.
+ * </p>
+ * <p>
* From its purpose this class is quite similar to
* {@link SubsetConfiguration}. The difference is that a subset
* configuration of a hierarchical configuration may combine multiple
@@ -108,6 +115,13 @@ import org.apache.commons.configuration.
* class instead of {@code SubsetConfiguration} because creating a subset
* configuration is more expensive than creating a subnode configuration.
* </p>
+ * <p>
+ * It is strongly recommended to create {@code SubnodeConfiguration} instances
+ * only through the {@code configurationAt()} methods of a hierarchical
+ * configuration. These methods ensure that all necessary initializations are
+ * done. Creating instances manually without doing proper initialization may
+ * break some of the functionality provided by this class.
+ * </p>
*
* @since 1.3
* @author <a
@@ -115,7 +129,7 @@ import org.apache.commons.configuration.
* Configuration team</a>
* @version $Id$
*/
-public class SubnodeConfiguration extends HierarchicalReloadableConfiguration
+public class SubnodeConfiguration extends BaseHierarchicalConfiguration
{
/**
* The serial version UID.
@@ -129,15 +143,18 @@ public class SubnodeConfiguration extend
private String subnodeKey;
/**
- * Creates a new instance of {@code SubnodeConfiguration} and
- * initializes it with the parent configuration and the new root node.
+ * Creates a new instance of {@code SubnodeConfiguration} and initializes
it
+ * with the parent configuration and the new root node.
*
* @param parent the parent configuration
- * @param root the root node of this subnode configuration
+ * @param root the root node of this {@code SubnodeConfiguration}
+ * @param subKey the key associated with this {@code SubnodeConfiguration}
+ * (can be <b>null</b>)
*/
- public SubnodeConfiguration(BaseHierarchicalConfiguration parent,
ConfigurationNode root)
+ public SubnodeConfiguration(BaseHierarchicalConfiguration parent,
+ ConfigurationNode root, String subKey)
{
- super(parent instanceof Reloadable ? ((Reloadable)
parent).getReloadLock() : null);
+ super(root);
if (parent == null)
{
throw new IllegalArgumentException(
@@ -148,8 +165,8 @@ public class SubnodeConfiguration extend
throw new IllegalArgumentException("Root node must not be null!");
}
- setRootNode(root);
this.parent = parent;
+ subnodeKey = subKey;
initFromParent(parent);
initInterpolator();
}
@@ -175,7 +192,15 @@ public class SubnodeConfiguration extend
*/
public String getSubnodeKey()
{
- return subnodeKey;
+ beginRead();
+ try
+ {
+ return subnodeKey;
+ }
+ finally
+ {
+ endRead();
+ }
}
/**
@@ -188,56 +213,15 @@ public class SubnodeConfiguration extend
*/
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)
- */
- @Override
- public ConfigurationNode getRootNode()
- {
- if (getSubnodeKey() != null)
+ beginWrite();
+ try
{
- try
- {
- List<ConfigurationNode> nodes =
getParent().fetchNodeList(getSubnodeKey());
- if (nodes.size() != 1)
- {
- // key is invalid, so detach this subnode configuration
- setSubnodeKey(null);
- }
- else
- {
- ConfigurationNode currentRoot = nodes.get(0);
- if (currentRoot != super.getRootNode())
- {
- // the root node was changed due to a change of the
- // parent
- fireEvent(EVENT_SUBNODE_CHANGED, null, null, true);
- setRootNode(currentRoot);
- fireEvent(EVENT_SUBNODE_CHANGED, null, null, false);
- }
- 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);
- }
+ this.subnodeKey = subnodeKey;
+ }
+ finally
+ {
+ endWrite();
}
-
- return super.getRootNode(); // use stored root node
}
/**
@@ -253,26 +237,17 @@ public class SubnodeConfiguration extend
*/
public void clearAndDetachFromParent()
{
- clear();
- setSubnodeKey(null); // always detach
- getParent().removeNode(getRootNode());
- }
-
- /**
- * Returns a hierarchical configuration object for the given sub node.
- * This implementation will ensure that the returned
- * {@code SubnodeConfiguration} object will have the same parent than
- * this object.
- *
- * @param node the sub node, for which the configuration is to be created
- * @return a hierarchical configuration for this sub node
- */
- @Override
- protected SubnodeConfiguration
createSubnodeConfiguration(ConfigurationNode node)
- {
- SubnodeConfiguration result = new SubnodeConfiguration(getParent(),
node);
- getParent().registerSubnodeConfiguration(result);
- return result;
+ beginWrite();
+ try
+ {
+ clearInternal();
+ subnodeKey = null; // always detach
+ getParent().removeNode(getRootNode());
+ }
+ finally
+ {
+ endWrite();
+ }
}
/**
@@ -286,40 +261,18 @@ public class SubnodeConfiguration extend
* 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
+ * @param subKey the construction key
* @return a hierarchical configuration for this sub node
* @since 1.5
*/
@Override
protected SubnodeConfiguration createSubnodeConfiguration(
- ConfigurationNode node, String subnodeKey)
+ ConfigurationNode node, String subKey)
{
- SubnodeConfiguration result = createSubnodeConfiguration(node);
-
- if (getSubnodeKey() != null)
- {
- // construct the correct subnode key
- // determine path to root node
- List<ConfigurationNode> lstPathToRoot = new
ArrayList<ConfigurationNode>();
- 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 (ConfigurationNode pathNode : lstPathToRoot)
- {
- key = getParent().getExpressionEngine().nodeKey(pathNode, key);
- }
- result.setSubnodeKey(key);
- }
-
- return result;
+ String key =
+ (subKey != null && subnodeKey != null) ?
constructSubKeyForSubnodeConfig(node)
+ : null;
+ return new SubnodeConfiguration(getParent(), node, key);
}
/**
@@ -350,6 +303,47 @@ public class SubnodeConfiguration extend
}
/**
+ * Validates this configuration's root node. This method checks whether the
+ * key associated with this {@code SubnodeConfiguration} (if any) still
+ * points to a valid node in the parent configuration. If not, the key is
+ * cleared, and this configuration is now detached from its parent.
+ */
+ void validateRootNode()
+ {
+ if (subnodeKey != null)
+ {
+ try
+ {
+ List<ConfigurationNode> nodes =
getParent().fetchNodeList(subnodeKey);
+ if (nodes.size() != 1)
+ {
+ // key is invalid, so detach this subnode configuration
+ subnodeKey = null;
+ }
+ else
+ {
+ ConfigurationNode currentRoot = nodes.get(0);
+ if (currentRoot != super.getRootNode())
+ {
+ // the root node was changed due to a change of the
+ // parent
+ fireEvent(EVENT_SUBNODE_CHANGED, null, null, true);
+ setRootNode(currentRoot);
+ fireEvent(EVENT_SUBNODE_CHANGED, null, null, false);
+ }
+ }
+ }
+ 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.
+ subnodeKey = null;
+ }
+ }
+ }
+
+ /**
* Initializes the {@code ConfigurationInterpolator} for this sub
configuration.
* This is a standard {@code ConfigurationInterpolator} which also
references
* the {@code ConfigurationInterpolator} of the parent configuration.
@@ -358,4 +352,34 @@ public class SubnodeConfiguration extend
{
getInterpolator().setParentInterpolator(getParent().getInterpolator());
}
+
+ /**
+ * Constructs the key for a {@code SubnodeConfiguration} for associating it
+ * with a node in the parent configuration. This method creates a canonical
+ * key based on the path from the given node to the root node.
+ *
+ * @param node the root node for the new {@code SubnodeConfiguration}
+ * @return the key for this {@code SubnodeConfiguration}
+ */
+ private String constructSubKeyForSubnodeConfig(ConfigurationNode node)
+ {
+ List<ConfigurationNode> lstPathToRoot =
+ new ArrayList<ConfigurationNode>();
+ 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 = subnodeKey;
+ for (ConfigurationNode pathNode : lstPathToRoot)
+ {
+ key = getParent().getExpressionEngine().nodeKey(pathNode, key);
+ }
+ return key;
+ }
}
Modified:
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestBaseHierarchicalConfigurationSynchronization.java
URL:
http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestBaseHierarchicalConfigurationSynchronization.java?rev=1481154&r1=1481153&r2=1481154&view=diff
==============================================================================
---
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestBaseHierarchicalConfigurationSynchronization.java
(original)
+++
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestBaseHierarchicalConfigurationSynchronization.java
Fri May 10 19:16:40 2013
@@ -17,10 +17,14 @@
package org.apache.commons.configuration;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.Collections;
+import java.util.List;
import org.apache.commons.configuration.SynchronizerTestImpl.Methods;
import org.apache.commons.configuration.io.FileHandler;
@@ -130,4 +134,121 @@ public class TestBaseHierarchicalConfigu
sync.verify(Methods.BEGIN_READ, Methods.END_READ);
assertNotSame("Synchronizer was copied", sync, copy.getSynchronizer());
}
+
+ /**
+ * Tests whether synchronization is performed when constructing a
+ * SubnodeConfiguration.
+ */
+ @Test
+ public void testConfigurationAtSynchronized()
+ {
+ SubnodeConfiguration sub = config.configurationAt("element2");
+ assertEquals("Wrong property", "I'm complex!",
+ sub.getString("subelement.subsubelement"));
+ sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE, Methods.BEGIN_READ,
+ Methods.END_READ);
+ }
+
+ /**
+ * Tests whether synchronization is performed when constructing multiple
+ * SubnodeConfiguration objects.
+ */
+ @Test
+ public void testConfigurationsAtSynchronized()
+ {
+ List<SubnodeConfiguration> subs = config.configurationsAt("list.item");
+ assertFalse("No subnode configurations", subs.isEmpty());
+ sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE);
+ }
+
+ /**
+ * Tests whether childConfigurationsAt() is correctly synchronized.
+ */
+ @Test
+ public void testChildConfigurationsAtSynchronized()
+ {
+ List<SubnodeConfiguration> subs =
config.childConfigurationsAt("clear");
+ assertFalse("No subnode configurations", subs.isEmpty());
+ sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE);
+ }
+
+ /**
+ * Tests whether synchronization is performed when setting the key of a
+ * SubnodeConfiguration.
+ */
+ @Test
+ public void testSetSubnodeKeySynchronized()
+ {
+ SubnodeConfiguration sub = config.configurationAt("element2");
+ assertNull("Got a subnode key", sub.getSubnodeKey());
+ sub.setSubnodeKey("element2");
+ // 1 x configurationAt(), 1 x getSubnodeKey(), 1 x setSubnodeKey()
+ sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE, Methods.BEGIN_READ,
+ Methods.END_READ, Methods.BEGIN_WRITE, Methods.END_WRITE);
+ }
+
+ /**
+ * Tests whether synchronization is performed when querying the key of a
+ * SubnodeConfiguration.
+ */
+ @Test
+ public void testGetSubnodeKeySynchronized()
+ {
+ SubnodeConfiguration sub = config.configurationAt("element2", true);
+ assertEquals("Wrong subnode key", "element2", sub.getSubnodeKey());
+ // 1 x configurationAt(), 1 x getSubnodeKey()
+ sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE,
+ Methods.BEGIN_READ, Methods.END_READ);
+ }
+
+ /**
+ * Tests whether updates on nodes are communicated to all
+ * SubnodeConfigurations of a configuration.
+ */
+ @Test
+ public void testSubnodeUpdate()
+ {
+ config.addProperty("element2.test", Boolean.TRUE);
+ SubnodeConfiguration sub = config.configurationAt("element2", true);
+ SubnodeConfiguration subsub = sub.configurationAt("subelement", true);
+ config.clearTree("element2.subelement");
+ assertNotNull("Sub1 detached", sub.getSubnodeKey());
+ assertNull("Sub2 still attached", subsub.getSubnodeKey());
+ }
+
+ /**
+ * Tests whether updates caused by a SubnodeConfiguration are communicated
+ * to all other SubnodeConfigurations.
+ */
+ @Test
+ public void testSubnodeUpdateBySubnode()
+ {
+ SubnodeConfiguration sub = config.configurationAt("element2", true);
+ SubnodeConfiguration subsub = sub.configurationAt("subelement", true);
+ SubnodeConfiguration sub2 =
+ config.configurationAt("element2.subelement", true);
+ sub.clearTree("subelement");
+ // 3 x configurationAt(), 1 x clearTree()
+ sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE,
+ Methods.BEGIN_WRITE, Methods.END_WRITE, Methods.BEGIN_WRITE,
+ Methods.END_WRITE, Methods.BEGIN_WRITE, Methods.END_WRITE);
+ assertNull("Sub2 still attached", sub2.getSubnodeKey());
+ assertNull("Subsub still attached", subsub.getSubnodeKey());
+ }
+
+ /**
+ * Tests whether a SubnodeConfiguration's clearAndDetachFromParent() method
+ * is correctly synchronized.
+ */
+ @Test
+ public void testSubnodeClearAndDetachFromParentSynchronized()
+ {
+ SubnodeConfiguration sub = config.configurationAt("element2", true);
+ sub.clearAndDetachFromParent();
+ assertFalse("Node not removed", config.containsKey("element2"));
+ // configurationAt() + clearTree() + containsKey()
+ sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE,
+ Methods.BEGIN_WRITE, Methods.END_WRITE, Methods.BEGIN_READ,
+ Methods.END_READ);
+ }
}
Modified:
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestHierarchicalConfiguration.java
URL:
http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestHierarchicalConfiguration.java?rev=1481154&r1=1481153&r2=1481154&view=diff
==============================================================================
---
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestHierarchicalConfiguration.java
(original)
+++
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestHierarchicalConfiguration.java
Fri May 10 19:16:40 2013
@@ -596,16 +596,13 @@ public class TestHierarchicalConfigurati
@Test
public void testImmutableConfigurationAtSupportUpdates()
{
- String[] tables2 = new String[tables.length];
- for(int i = 0; i < tables2.length; i++)
- {
- tables2[i] = tables[i] + "_other";
- }
+ String newTableName = tables[1] + "_other";
ImmutableHierarchicalConfiguration subConfig =
config.immutableConfigurationAt("tables.table(1)", true);
- config.clear();
- fillConfiguration(tables2);
- assertEquals("Name not updated", tables2[1],
subConfig.getString("name"));
+ config.addProperty("tables.table(-1).name", newTableName);
+ config.clearTree("tables.table(1)");
+ assertEquals("Name not updated", newTableName,
+ subConfig.getString("name"));
}
/**
@@ -1044,7 +1041,9 @@ public class TestHierarchicalConfigurati
@Test
public void testInitCopyNull()
{
- BaseHierarchicalConfiguration copy = new
BaseHierarchicalConfiguration(null);
+ BaseHierarchicalConfiguration copy =
+ new BaseHierarchicalConfiguration(
+ (HierarchicalConfiguration) null);
assertTrue("Configuration not empty", copy.isEmpty());
}
Modified:
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestSubnodeConfiguration.java
URL:
http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestSubnodeConfiguration.java?rev=1481154&r1=1481153&r2=1481154&view=diff
==============================================================================
---
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestSubnodeConfiguration.java
(original)
+++
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestSubnodeConfiguration.java
Fri May 10 19:16:40 2013
@@ -21,8 +21,6 @@ import static org.junit.Assert.assertNul
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
-import java.io.File;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -34,13 +32,10 @@ import org.apache.commons.configuration.
import org.apache.commons.configuration.event.ConfigurationListener;
import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
import org.apache.commons.configuration.interpol.Lookup;
-import org.apache.commons.configuration.io.FileHandler;
import org.apache.commons.configuration.tree.ConfigurationNode;
import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
/**
* Test case for SubnodeConfiguration.
@@ -65,9 +60,8 @@ public class TestSubnodeConfiguration
/** Constant for an updated table name.*/
private static final String NEW_TABLE_NAME = "newTable";
- /** A helper object for creating temporary files. */
- @Rule
- public TemporaryFolder folder = new TemporaryFolder();
+ /** The key used for the SubnodeConfiguration. */
+ private static final String SUB_KEY = "tables.table(1)";
/** The parent configuration. */
private BaseHierarchicalConfiguration parent;
@@ -104,7 +98,7 @@ public class TestSubnodeConfiguration
@Test(expected = IllegalArgumentException.class)
public void testInitSubNodeConfigWithNullParent()
{
- config = new SubnodeConfiguration(null, getSubnodeRoot(parent));
+ config = new SubnodeConfiguration(null, getSubnodeRoot(parent), null);
}
/**
@@ -114,7 +108,7 @@ public class TestSubnodeConfiguration
@Test(expected = IllegalArgumentException.class)
public void testInitSubNodeConfigWithNullNode()
{
- config = new SubnodeConfiguration(parent, null);
+ config = new SubnodeConfiguration(parent, null, null);
}
/**
@@ -390,7 +384,7 @@ public class TestSubnodeConfiguration
@Test
public void testParentReloadNotSupported() throws ConfigurationException
{
- Configuration c = setUpReloadTest(false);
+ Configuration c = setUpLiveUpdateTest(false);
assertEquals("Name changed in sub config", TABLE_NAMES[1], config
.getString("name"));
assertEquals("Name not changed in parent", NEW_TABLE_NAME, c
@@ -404,7 +398,7 @@ public class TestSubnodeConfiguration
@Test
public void testParentReloadSupported() throws ConfigurationException
{
- Configuration c = setUpReloadTest(true);
+ Configuration c = setUpLiveUpdateTest(true);
assertEquals("Name not changed in sub config", NEW_TABLE_NAME, config
.getString("name"));
assertEquals("Name not changed in parent", NEW_TABLE_NAME, c
@@ -417,11 +411,11 @@ public class TestSubnodeConfiguration
@Test
public void testParentReloadEvents() throws ConfigurationException
{
- setUpReloadTest(true);
+ config = parent.configurationAt(SUB_KEY, true);
ConfigurationListenerTestImpl l = new ConfigurationListenerTestImpl();
config.addConfigurationListener(l);
- config.getString("name");
- assertEquals("Wrong number of events", 2, l.events.size());
+ updateParent();
+ assertEquals("Wrong number of events", 4, l.events.size());
boolean before = true;
for (ConfigurationEvent e : l.events)
{
@@ -445,7 +439,7 @@ public class TestSubnodeConfiguration
public void testParentReloadSupportAccessParent()
throws ConfigurationException
{
- Configuration c = setUpReloadTest(true);
+ Configuration c = setUpLiveUpdateTest(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
@@ -458,7 +452,7 @@ public class TestSubnodeConfiguration
@Test
public void testParentReloadSubSubnode() throws ConfigurationException
{
- setUpReloadTest(true);
+ setUpLiveUpdateTest(true);
SubnodeConfiguration sub = config.configurationAt("fields", true);
assertEquals("Wrong subnode key", "tables.table(1).fields", sub
.getSubnodeKey());
@@ -474,7 +468,7 @@ public class TestSubnodeConfiguration
public void testParentReloadSubSubnodeNoChangeSupport()
throws ConfigurationException
{
- setUpReloadTest(false);
+ setUpLiveUpdateTest(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
@@ -482,44 +476,39 @@ public class TestSubnodeConfiguration
}
/**
- * Prepares a test for a reload operation.
+ * Prepares a test for updates of a SubnodeConfiguration if the node
+ * structure of the parent changes. This method replaces the nodes for the
+ * tables with new ones.
*
- * @param supportReload a flag whether the subnode configuration should
- * support reload operations
+ * @param supportReload a flag whether the SubnodeConfiguration 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
+ private HierarchicalConfiguration setUpLiveUpdateTest(boolean
supportReload)
{
- try
- {
- File testFile = folder.newFile();
- XMLConfiguration xmlConf = new XMLConfiguration(parent);
- FileHandler handler = new FileHandler(xmlConf);
- handler.setFile(testFile);
- handler.save();
- config = xmlConf.configurationAt("tables.table(1)", supportReload);
- assertEquals("Wrong table name", TABLE_NAMES[1],
- config.getString("name"));
- // Now change the configuration file
- XMLConfiguration confUpdate = new XMLConfiguration();
- FileHandler handler2 = new FileHandler(confUpdate);
- handler2.setFile(testFile);
- handler2.load();
- confUpdate.setProperty("tables.table(1).name", NEW_TABLE_NAME);
- confUpdate.setProperty("tables.table(1).fields.field(0).name",
- "newField");
- handler2.save();
- // Reload parent configuration
- xmlConf.clear();
- handler.load();
- return xmlConf;
- }
- catch (IOException ioex)
+ config = parent.configurationAt(SUB_KEY, supportReload);
+ updateParent();
+ return parent;
+ }
+
+ /**
+ * Updates the parent configuration. Replaces the node structure so that
+ * an attached SubnodeConfiguration should be removed now.
+ */
+ private void updateParent()
+ {
+ String[] tableNamesNew = TABLE_NAMES.clone();
+ String[][] fieldNamesNew = new String[TABLE_FIELDS.length][];
+ for(int i = 0; i < TABLE_FIELDS.length; i++)
{
- throw new ConfigurationException(ioex);
+ fieldNamesNew[i] = TABLE_FIELDS[i].clone();
}
+ tableNamesNew[1] = NEW_TABLE_NAME;
+ fieldNamesNew[1][0] = "newField";
+ addTableData(parent, tableNamesNew, fieldNamesNew);
+ String keyClear = "tables.table(0)";
+ parent.clearTree(keyClear);
+ parent.clearTree(keyClear);
}
/**
@@ -530,7 +519,7 @@ public class TestSubnodeConfiguration
@Test
public void testParentChangeDetach()
{
- final String key = "tables.table(1)";
+ final String key = SUB_KEY;
config = parent.configurationAt(key, true);
assertEquals("Wrong subnode key", key, config.getSubnodeKey());
assertEquals("Wrong table name", TABLE_NAMES[1], config
@@ -549,8 +538,9 @@ public class TestSubnodeConfiguration
@Test
public void testParentChangeDetatchException()
{
- config = parent.configurationAt("tables.table(1)", true);
+ config = parent.configurationAt(SUB_KEY, true);
parent.setExpressionEngine(new XPathExpressionEngine());
+ parent.addProperty("newProp", "value");
assertEquals("Wrong name of table", TABLE_NAMES[1], config
.getString("name"));
assertNull("Sub config was not detached", config.getSubnodeKey());
@@ -580,16 +570,30 @@ public class TestSubnodeConfiguration
return super.createNode(name);
}
};
- for (int i = 0; i < TABLE_NAMES.length; i++)
+ addTableData(conf, TABLE_NAMES, TABLE_FIELDS);
+ return conf;
+ }
+
+ /**
+ * Appends properties for table names and their fields to the given
+ * configuration.
+ *
+ * @param conf the configuration to be filled
+ * @param tableNames an array with the names of the tables to add
+ * @param fields and array with the field names per table
+ */
+ private static void addTableData(Configuration conf, String[] tableNames,
+ String[][] fields)
+ {
+ for (int i = 0; i < tableNames.length; i++)
{
- conf.addProperty("tables.table(-1).name", TABLE_NAMES[i]);
- for (int j = 0; j < TABLE_FIELDS[i].length; j++)
+ conf.addProperty("tables.table(-1).name", tableNames[i]);
+ for (int j = 0; j < fields[i].length; j++)
{
conf.addProperty("tables.table.fields.field(-1).name",
- TABLE_FIELDS[i][j]);
+ fields[i][j]);
}
}
- return conf;
}
/**
@@ -610,7 +614,7 @@ public class TestSubnodeConfiguration
*/
protected void setUpSubnodeConfig()
{
- config = new SubnodeConfiguration(parent, getSubnodeRoot(parent));
+ config = new SubnodeConfiguration(parent, getSubnodeRoot(parent),
null);
}
/**