Index: xdocs/changes.xml
===================================================================
--- xdocs/changes.xml	(revision 713226)
+++ xdocs/changes.xml	(working copy)
@@ -24,6 +24,10 @@
   <body>
     <release version="1.6" date="in SVN" description="">
       <action dev="oheger" type="add" issue="CONFIGURATION-349" due-to="Ralph Goers">
+        Added MultiFileHierarchicalConfiguration, DynamicCombinedConfiguration
+        and PatternSubtreeConfigurationWrapper. 
+      </action>
+      <action dev="oheger" type="add" issue="CONFIGURATION-349" due-to="Ralph Goers">
         The visibility of DefaultConfigurationBuilder.XMLConfigurationProvider
         was changed from package local to public. This makes it easier to
         implement providers that create configuration classes derived from
Index: xdocs/userguide/howto_multitenant.xml
===================================================================
--- xdocs/userguide/howto_multitenant.xml	(revision 713226)
+++ xdocs/userguide/howto_multitenant.xml	(working copy)
@@ -18,227 +18,131 @@
 
 <document>
 
- <properties>
-  <title>File-based Configurations</title>
-  <author email="oheger@apache.org">Oliver Heger</author>
- </properties>
+  <properties>
+    <title>Mutli-tenant Configurations</title>
+    <author email="rgoers@apache.org">Ralph Goers</author>
+  </properties>
 
-<body>
-    <section name="File-based Configurations">
+  <body>
+    <section name="Multi-tenant Configurations">
+      <p>
+        In a multi-tenant environment a single instance of the application
+        while run on behalf of many clients. Typically, this will require
+        that each client have its own unique configuration. The simplest
+        approach to enable an application to be multi-tenant is for it
+        to not really be aware of it at all. This requires that the
+        configuration framework take on some of the responsility for
+        making the application work correctly.
+      </p>
+      <p>
+        One approach to enable this support in a web application might be
+        to use a Servlet Filter and then use the Log4j or SLF4J MDC to
+        save the attributes needed to identify the client. These attributes
+        can then be used to identify the specific client configuration to
+        be used. The classes described below use this technique to allow
+        configurations to transparently provide the configuration appropriate
+        for the clients.
+      </p>
+
+      <subsection name="MultiFileHierarchicalConfiguration">
         <p>
-            Often configuration properties are stored in files on the user's hard
-          disk, e.g. in .properties files or as XML documents. Configuration
-          classes that deal with such properties need to provide typical operations
-          like loading or saving files. The files to be processed can be specified
-          in several different flavors like <code>java.io.File</code> objects,
-          relative or absolute path names, or URLs.
+          The constructor for this class accepts a pattern. The pattern can
+          contain keys that will be resolved using the ConfigurationInterpolator
+          on each call to a method in the class. The configuration file will then
+          be located using the resolved pattern and a new XMLConfiguration
+          will be created and cached for subsequent requests. The ExpressionEngine,
+          ReloadingStrategy and listeners will be propogated to each of the
+          created configurations.
         </p>
+      </subsection>
+      <subsection name="DynamicCombinedConfiguration">
         <p>
-          To provide a consistent way of dealing with configuration files in
-          Commons Configuration the <code><a href="apidocs/org/apache/commons/configuration/FileConfiguration.html">FileConfiguration</a></code>
-          interface exists. <code>FileConfiguration</code> defines a standard
-          API for accessing files and is implemented by many configuration
-          implementations, including <code>PropertiesConfiguration</code> and
-          <code>XMLConfiguration</code>.
+          The CombinedConfiguration class allows multiple configuration files to be
+          merged together. However, it will not merge a MultiFileHierarchicalConfiguration
+          properly since the underlying configuration file will be different depending
+          on the resolution of the location pattern. DynamicCombinedConfiguration
+          solves this by creating a new CombinedConfiguration for each pattern.
         </p>
+      </subsection>
+      <subsection name="Sample Configuration">
         <p>
-          In the following sections we take a closer look at the methods of the
-          <code>FileConfiguration</code> interface and how they are used.
+           This sample configuration illustrates how to use DynamicCombinedConfiguration
+           in combination with MultiFileHierarchicalConfiguration to create a multi-tenant
+           configuration.
         </p>
-
-        <subsection name="Specifying the file">
-          <p>
-            The <code>FileConfiguration</code> interface contains several
-            methods for specifying the file to be loaded. The following variants
-            are supported:
-            <ul>
-              <li>With the <code>setFile()</code> method the data file can be
-              specified as a <code>java.io.File</code> object.</li>
-              <li>The <code>setURL()</code> takes a <code>java.net.URL</code>
-              as argument; the file will be loaded from this URL.</li>
-              <li>The methods <code>setFileName()</code> and <code>setBasePath()</code>
-              allows to specify the path of the data file. The base path is
-              important if relative paths are to be resolved based on this file.</li>
-            </ul>
-          </p>
-          <p>
-            While a <code>File</code> or a URL uniquely identify a file, the
-            situation is a bit ambigous when only a base path and a file name are
-            set. These can be arbitrary strings (even full URLs) whose exact
-            meaning must be detected when the file is loaded. For this purpose
-            file-based configurations perform the following checks (in this
-            order):
-            <ul>
-              <li>If the combination from base path and file name is a full URL
-              that points to an existing file, this URL will be used to load
-              the file.</li>
-              <li>If the combination from base path and file name is an absolute
-              file name and this file exists, it will be loaded.</li>
-              <li>If the combination from base path and file name is a relative
-              file path that points to an existing file, this file will be loaded.</li>
-              <li>If a file with the specified name exists in the user's home
-              directory, this file will be loaded.</li>
-              <li>Otherwise the file name is interpreted as a resource name, and
-              it is checked whether the data file can be loaded from the classpath.</li>
-            </ul>
-            If all these checks fail, a <code>ConfigurationException</code> will
-            be thrown.
-          </p>
-        </subsection>
-
-        <subsection name="Loading">
-          <p>
-            After the file name has been defined using one of the methods mentioned
-            above, the <code>load()</code> method can be called. This method tries
-            to locate the file and open it. If this fails, a <code>ConfigurationException</code>
-            is thrown.
-          </p>
-          <p>
-            The <code>FileConfiguration</code> interface defines multiple overloaded
-            <code>load()</code> methods. The one that takes no argument will
-            always operate on the file name that has been set earlier. All
-            other methods allow to specify the source to be loaded. This can be
-            done as <code>java.io.File</code>, <code>java.net.URL</code>, string
-            (containing either an absolute or relative path), input stream, or
-            reader. When using these variants of the <code>load()</code> method
-            be aware of two things:
-            <ol>
-              <li>They do not change the configuration's file name. To do this
-              you have to explicitely call one of the setter methods.</li>
-              <li>The <code>load()</code> methods do not empty the
-              configuration before new data is loaded. This makes it easy to
-              construct union configurations by simply calling <code>load()</code>
-              multiple times. But if you want to reuse a <code>Configuration</code>
-              object and load a different file, remember to call the
-              <code>clear()</code> method first to ensure that old properties are
-              wiped out.</li>
-            </ol>
-          </p>
-          <p>
-            File-based configurations typically define a set of constructors that
-            correspond to the various setter methods for defining the data file.
-            These constructors will set the file and then invoke the <code>load()</code>
-            method. So creating a file-based configuration object and loading its
-            content can be done in a single step.
-          </p>
-        </subsection>
-
-        <subsection name="Saving">
-          <p>
-            Saving is implemented analogously to loading: There is a no argument
-            <code>save()</code> method that will use the internal file name. Then
-            for each <code>load()</code> method a corresponding <code>save()</code>
-            method exists that will write the data contained in the configuration
-            to different targets.
-          </p>
-          <p>
-            An example for loading, manipulating, and saving a configuration
-            (based on a <a href="howto_properties.html"><code>PropertiesConfiguration</code></a>)
-            could look as follows:
-          </p>
-<source>
-PropertiesConfiguration config = new PropertiesConfiguration("usergui.properties");
-config.setProperty("colors.background", "#000000);
-config.save();
-</source>
-          <p>
-            You can also save a copy of the configuration to another file:
-          </p>
-<source>
-PropertiesConfiguration config = new PropertiesConfiguration("usergui.properties");
-config.setProperty("colors.background", "#000000);
-config.save("usergui.backup.properties);
-</source>
-        </subsection>
-
-        <subsection name="Automatic Saving">
-          <p>
-            If you want to ensure that every modification of a configuration
-            object is immideately written to disk, you can enable the automatic
-            saving mode. This is done through the <code>setAutoSave()</code>
-            method as shown in the following example:
-          </p>
-<source>
-PropertiesConfiguration config = new PropertiesConfiguration("usergui.properties");
-config.setAutoSave(true);
-config.setProperty("colors.background", "#000000); // the configuration is saved after this call
-</source>
-          <p>
-            Be careful with this mode when you have many updates on your
-            configuration. This will lead to many I/O operations, too.
-          </p>
-        </subsection>
-
-        <subsection name="Automatic Reloading">
+        <source><![CDATA[
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <header>
+    <result delimiterParsingDisabled="true" forceReloadCheck="true"
+            config-class="org.apache.commons.configuration.DynamicCombinedConfiguration"
+            keyPattern="${sys:Id}">
+      <expressionEngine
+          config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <providers>
+      <provider config-tag="multifile"
+         config-class="org.apache.commons.configuration.DefaultConfigurationBuilder$FileConfigurationProvider"
+         configurationClass="org.apache.commons.configuration.MultiFileHierarchicalConfiguration"/>
+    </providers>
+  </header>
+  <override>
+    <multifile filePattern="/opt/configs/${sys:Id}/config.xml" config-name="clientConfig"/>
+    <xml fileName="/opt/configs/default/config.xml" config-name="defaultConfig"/>
+  </override>
+</configuration>
+]]></source>
+      </subsection>
+      <subsection name="PatternSubtreeConfigurationWrapper">
         <p>
-          A common issue with file-based configurations is to handle the
-          reloading of the data file when it changes. This is especially important
-          if you have long running applications and do not want to restart them
-          when a configuration file was updated. Commons Configuration has the
-          concept of so called <em>reloading strategies</em> that can be
-          associated with a file-based configuration. Such a strategy monitors
-          a configuration file and is able to detect changes. A default reloading
-          strategy is <code><a href="apidocs/org/apache/commons/configuration/reloading/FileChangedReloadingStrategy.html">FileChangedReloadingStrategy</a></code>.
-          It can be set on a file-based configuration as follows:
+          Applications are often composed of many components each of which need their
+          own configuration. This can be accomodated by having a configuration file
+          per component, but this can make things hard to manage when there are many
+          clients and many components. A second approach is to combine them into
+          a single configuration file. However, this either means the subcomponent
+          has to be aware of the surrounding configuration and navigate past it or the
+          application must be provided just the portion of the configuration it
+          can process. PatternSubtreeConfigurationWrapper can be used for this purpose.
         </p>
-<source>
-PropertiesConfiguration config = new PropertiesConfiguration("usergui.properties");
-config.setReloadingStrategy(new FileChangedReloadingStrategy());
-</source>
         <p>
-          <code>FileChangedReloadingStrategy</code> works as follows: On every
-          property access the configuration checks its associated reloading
-          strategy. <code>FileChangedReloadingStrategy</code> will then obtain
-          the last modification date of the configuration file and check whether
-          it has changed since the last access. If this is the case, a reload is
-          triggered. To avoid often disk access when multiple properties are
-          queried from the configuration, a <em>refresh delay</em> can be set on
-          the reloading strategy. This is a time in milli seconds with the meaning
-          that the reloading strategy will only once check the file's last
-          modification time in the period specified here.
+          Normal practice when using dependency injection frameworks is to have the
+          attributes needed to make components work correctly injected into them.
+          When working with Commons Configuration this works very well. Components
+          simply need to have a HierarchicalConfiguration attribute along with
+          a corresponding setter and getter. The injection framework can then be
+          used to provide the component with the correct configuration using
+          PatternSubtreeConfigurationWrapper as shown in the next example.
         </p>
-        </subsection>
-
-        <subsection name="Managed Reloading">
         <p>
-          <code>ManagedReloadingStrategy</code> is an alternative to automatic
-          reloading. It allows to hot-reload properties on a running application
-          but only when requested by admin. The <code>refresh()</code> method
-          will force a reload of the configuration source.
-        </p>
-        <p>
-          A typical use of this feature is to setup ManagedReloadingStrategy as
-          a JMX MBean. The following code sample uses Springframework
-          MBeanExporter to expose the ManagedReloadingStrategy to the JMX
-          console :
-<source>
-<![CDATA[
-<!-- A file based configuration bean -->
-<bean id="configuration" class="(...).PropertiesConfiguration">
-    <constructor-arg type="java.net.URL" value="file:${user.home}/custom.properties"/>
-    <property name="reloadingStrategy" ref="reloadingStrategy"/>
-</bean>
-
-<!-- The managed reloading strategy for the configuration bean -->
-<bean id="reloadingStrategy" class="...ManagedReloadingStrategy"/>
-
-<!-- The MBeanExporter that exposes reloadingStrategy to the JMX console -->
-<bean id="mbeanMetadataExporter" class="org.springframework.jmx.export.MBeanExporter">
-    <property name="server" ref="mbeanServer"/>
-    <property name="beans">
-        <map>
-            <entry key="myApp:bean=configuration" value-ref="reloadingStrategy"/>
-        </map>
+        <source><![CDATA[
+  <bean id="configurationBuilder"
+        class="org.apache.commons.configuration.DefaultConfigurationBuilder">
+    <property name="fileName">
+      <value>configuration.xml</value>
     </property>
-</bean>
-]]>
-</source>
-          With this configuration, the JMX console will expose the
-          "myApp:bean=configuration" MBean and it's refresh operation.
+  </bean>
+  <bean id="applicationConfig" factory-bean="configurationBuilder"
+        factory-method="getConfiguration">
+  </bean>
+  <bean id="subcomponentConfig"
+        class="org.apache.commons.configuration.PatternSubtreeConfigurationWrapper"
+        autowire='autodetect'>
+    <constructor-arg index="0">
+      <ref bean="applicationConfig"/>
+    </constructor-arg>
+    <constructor-arg index="1" value="/Components/MyComponent"
+  </bean>
+  <bean id='MyComponent' class='org.test.MyComponent' autowire='autodetect'>
+    <property name="configuration">
+      <ref bean="subcomponentConfig"/>
+    </property>
+  </bean>
+]]></source>
         </p>
-        </subsection>
+      </subsection>
     </section>
 
-</body>
+  </body>
 
 </document>

Property changes on: xdocs/userguide/howto_multitenant.xml
___________________________________________________________________
Name: svn:mergeinfo
   + 

Index: xdocs/userguide/user_guide.xml
===================================================================
--- xdocs/userguide/user_guide.xml	(revision 713226)
+++ xdocs/userguide/user_guide.xml	(working copy)
@@ -132,6 +132,13 @@
         <li><a href="howto_configurationbuilder.html#An_example">An example</a></li>
         <li><a href="howto_configurationbuilder.html#Extending_the_configuration_definition_file_format">Extending the configuration definition file format</a></li>
       </ul>
+      <li><a href="howto_multitenant.html#Multi-tenant Configurations">Multi-tenant Configurations</a></li>
+      <ul>
+        <li><a href="howto_multitenant.html#MultiFileHierarchicalConfiguration">MultiFileHierarchicalConfiguration</a></li>
+        <li><a href="howto_multitenant.html#DynamicCombinedConfiguration">DynamicCombinedConfiguration</a></li>
+        <li><a href="howto_multitenant.html#Sample Configuration">Sample Configuration</a></li>
+        <li><a href="howto_multitenant.html#PatternSubtreeConfigurationWrapper">PatternSubtreeConfigurationWrapper</a></li>
+      </ul>
       <li><a href="howto_events.html#Configuration_Events">Configuration Events</a></li>
       <ul>
         <li><a href="howto_events.html#Configuration_listeners">Configuration listeners</a></li>
Index: conf/testMultiConfiguration_default.xml
===================================================================
--- conf/testMultiConfiguration_default.xml	(revision 0)
+++ conf/testMultiConfiguration_default.xml	(revision 0)
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<configuration>
+  <colors>
+    <background>#40404040</background>
+    <text>#000000</text>
+    <header>#444444</header>
+    <link normal="#040404" visited="#404040"/>
+    <default>${colors.default}</default>
+  </colors>
+  <rowsPerPage>50</rowsPerPage>
+  <buttons>
+    <name>OK-4,Cancel-4,Help-4</name>
+  </buttons>
+  <numberFormat pattern="###\,###.##"/>
+</configuration>
\ No newline at end of file
Index: conf/testMultiConfiguration_1001.xml
===================================================================
--- conf/testMultiConfiguration_1001.xml	(revision 0)
+++ conf/testMultiConfiguration_1001.xml	(revision 0)
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<configuration>
+  <colors>
+    <background>#808080</background>
+    <text>#000000</text>
+    <header>#008000</header>
+    <link normal="#000080" visited="#800080"/>
+    <default>${colors.header}</default>
+  </colors>
+  <rowsPerPage>15</rowsPerPage>
+  <buttons>
+    <name>OK,Cancel,Help</name>
+  </buttons>
+  <numberFormat pattern="###\,###.##"/>
+</configuration>
Index: conf/testMultiConfiguration_1002.xml
===================================================================
--- conf/testMultiConfiguration_1002.xml	(revision 0)
+++ conf/testMultiConfiguration_1002.xml	(revision 0)
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<configuration>
+  <colors>
+    <background>#2222222</background>
+    <text>#000000</text>
+    <header>#222222</header>
+    <link normal="#020202" visited="#202020"/>
+    <default>${colors.header3}</default>
+  </colors>
+  <rowsPerPage>25</rowsPerPage>
+  <buttons>
+    <name>OK-2,Cancel-2,Help-2</name>
+  </buttons>
+  <numberFormat pattern="###\,###.##"/>
+</configuration>
Index: conf/testMultiConfiguration_1003.xml
===================================================================
--- conf/testMultiConfiguration_1003.xml	(revision 0)
+++ conf/testMultiConfiguration_1003.xml	(revision 0)
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<configuration>
+  <colors>
+    <background>#333333</background>
+    <text>#FFFFFF</text>
+    <header>#333333</header>
+    <link normal="#030303" visited="#303030"/>
+    <default>${colors.header3}</default>
+  </colors>
+  <rowsPerPage>35</rowsPerPage>
+  <buttons>
+    <name>OK-3,Cancel-3,Help-3</name>
+  </buttons>
+  <numberFormat pattern="###\,###.##"/>
+</configuration>
Index: conf/testMultiConfiguration_1004.xml
===================================================================
--- conf/testMultiConfiguration_1004.xml	(revision 0)
+++ conf/testMultiConfiguration_1004.xml	(revision 0)
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<configuration>
+  <colors>
+    <default>${colors.header4}</default>
+  </colors>
+  <buttons>
+    <name>OK-1,Cancel-2,Help-3</name>
+  </buttons>
+  <numberFormat pattern="###\,###.##"/>
+</configuration>
\ No newline at end of file
Index: conf/testPatternSubtreeConfig.xml
===================================================================
--- conf/testPatternSubtreeConfig.xml	(revision 0)
+++ conf/testPatternSubtreeConfig.xml	(revision 0)
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<Configuration>
+  <BusinessClient name="1001">
+    <colors>
+      <background>#808080</background>
+      <text>#000000</text>
+      <header>#008000</header>
+      <link normal="#000080" visited="#800080"/>
+      <default>${colors.header}</default>
+    </colors>
+    <rowsPerPage>15</rowsPerPage>
+    <buttons>
+      <name>OK,Cancel,Help</name>
+    </buttons>
+    <numberFormat pattern="###\,###.##"/>
+  </BusinessClient>
+  <BusinessClient name="1002">
+    <colors>
+      <background>#2222222</background>
+      <text>#000000</text>
+      <header>#222222</header>
+      <link normal="#020202" visited="#202020"/>
+      <default>${colors.header3}</default>
+    </colors>
+    <rowsPerPage>25</rowsPerPage>
+    <buttons>
+      <name>OK-2,Cancel-2,Help-2</name>
+    </buttons>
+    <numberFormat pattern="###\,###.##"/>
+  </BusinessClient>
+  <BusinessClient name="1003">
+    <colors>
+      <background>#333333</background>
+      <text>#FFFFFF</text>
+      <header>#333333</header>
+      <link normal="#030303" visited="#303030"/>
+      <default>${colors.header3}</default>
+    </colors>
+    <rowsPerPage>35</rowsPerPage>
+    <buttons>
+      <name>OK-3,Cancel-3,Help-3</name>
+    </buttons>
+    <numberFormat pattern="###\,###.##"/>
+  </BusinessClient>
+</Configuration>
\ No newline at end of file
Index: src/test/org/apache/commons/configuration/TestDynamicCombinedConfiguration.java
===================================================================
--- src/test/org/apache/commons/configuration/TestDynamicCombinedConfiguration.java	(revision 0)
+++ src/test/org/apache/commons/configuration/TestDynamicCombinedConfiguration.java	(revision 0)
@@ -0,0 +1,54 @@
+package org.apache.commons.configuration;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import junit.framework.TestCase;
+
+/**
+ *
+ */
+public class TestDynamicCombinedConfiguration extends TestCase
+{
+    private static String PATTERN ="${sys:Id}";
+    private static String PATTERN1 = "target/test-classes/testMultiConfiguration_${sys:Id}.xml";
+    private static String DEFAULT_FILE = "target/test-classes/testMultiConfiguration_default.xml";
+
+    /**
+     * Create the test case
+     *
+     * @param testName name of the test case
+     */
+    public TestDynamicCombinedConfiguration( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * @return the suite of tests being tested
+     */
+    public static Test suite()
+    {
+        return new TestSuite( TestDynamicCombinedConfiguration.class );
+    }
+
+    public void testConfiguration() throws Exception
+    {
+        DynamicCombinedConfiguration config = new DynamicCombinedConfiguration();
+        config.setKeyPattern(PATTERN);
+        MultiFileHierarchicalConfiguration multi = new MultiFileHierarchicalConfiguration(PATTERN1);
+        config.addConfiguration(multi, "Multi");
+        XMLConfiguration xml = new XMLConfiguration(DEFAULT_FILE);
+        config.addConfiguration(xml, "Default");
+
+        verify("1001", config, 15);
+        verify("1002", config, 25);
+        verify("1003", config, 35);
+        verify("1004", config, 50);
+    }
+
+    private void verify(String key, DynamicCombinedConfiguration config, int rows)
+    {
+        System.setProperty("Id", key);
+        assertTrue(config.getInt("rowsPerPage") == rows);
+    }
+}
Index: src/test/org/apache/commons/configuration/TestMultiFileHierarchicalConfiguration.java
===================================================================
--- src/test/org/apache/commons/configuration/TestMultiFileHierarchicalConfiguration.java	(revision 0)
+++ src/test/org/apache/commons/configuration/TestMultiFileHierarchicalConfiguration.java	(revision 0)
@@ -0,0 +1,56 @@
+package org.apache.commons.configuration;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
+
+/**
+ * Unit test for simple MultiConfigurationTest.
+ */
+public class TestMultiFileHierarchicalConfiguration
+    extends TestCase
+{
+    private static String PATTERN1 = "target/test-classes/testMultiConfiguration_${sys:Id}.xml";
+    
+    /**
+     * Create the test case
+     *
+     * @param testName name of the test case
+     */
+    public TestMultiFileHierarchicalConfiguration( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * @return the suite of tests being tested
+     */
+    public static Test suite()
+    {
+        return new TestSuite( TestMultiFileHierarchicalConfiguration.class );
+    }
+
+    /**
+     * Rigourous Test :-)
+     */
+    public void testMultiConfiguration()
+    {
+        //set up a reloading strategy
+        FileChangedReloadingStrategy strategy = new FileChangedReloadingStrategy();
+        strategy.setRefreshDelay(10000);
+        
+        MultiFileHierarchicalConfiguration config = new MultiFileHierarchicalConfiguration(PATTERN1);
+        config.setReloadingStrategy(strategy);
+
+        System.setProperty("Id", "1001");
+        assertTrue(config.getInt("rowsPerPage") == 15);
+
+        System.setProperty("Id", "1002");
+        assertTrue(config.getInt("rowsPerPage") == 25);
+
+        System.setProperty("Id", "1003");
+        assertTrue(config.getInt("rowsPerPage") == 35);
+    }
+}
Index: src/test/org/apache/commons/configuration/TestPatternSubtreeConfiguration.java
===================================================================
--- src/test/org/apache/commons/configuration/TestPatternSubtreeConfiguration.java	(revision 0)
+++ src/test/org/apache/commons/configuration/TestPatternSubtreeConfiguration.java	(revision 0)
@@ -0,0 +1,68 @@
+package org.apache.commons.configuration;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
+import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
+
+import java.io.File;
+
+/**
+ * Unit test for simple MultiConfigurationTest.
+ */
+public class TestPatternSubtreeConfiguration extends TestCase
+{
+    private static String CONFIG_FILE = "target/test-classes/testPatternSubtreeConfig.xml";
+    private static String PATTERN = "BusinessClient[@name='${sys:Id}']";
+    private XMLConfiguration conf;
+
+    /**
+     * Create the test case
+     *
+     * @param testName name of the test case
+     */
+    public TestPatternSubtreeConfiguration( String testName )
+    {
+        super( testName );
+    }
+
+    /**
+     * @return the suite of tests being tested
+     */
+    public static Test suite() throws Exception
+    {
+        return new TestSuite(TestPatternSubtreeConfiguration.class );
+    }
+
+    protected void setUp() throws Exception
+    {
+        conf = new XMLConfiguration();
+        conf.setFile(new File(CONFIG_FILE));
+        conf.load();
+    }
+
+    /**
+     * Rigourous Test :-)
+     */
+    public void testMultiConfiguration()
+    {
+        //set up a reloading strategy
+        FileChangedReloadingStrategy strategy = new FileChangedReloadingStrategy();
+        strategy.setRefreshDelay(10000);
+
+        PatternSubtreeConfigurationWrapper config = new PatternSubtreeConfigurationWrapper(this.conf, PATTERN);
+        config.setReloadingStrategy(strategy);
+        config.setExpressionEngine(new XPathExpressionEngine());
+
+        System.setProperty("Id", "1001");
+        assertTrue(config.getInt("rowsPerPage") == 15);
+
+        System.setProperty("Id", "1002");
+        assertTrue(config.getInt("rowsPerPage") == 25);
+
+        System.setProperty("Id", "1003");
+        assertTrue(config.getInt("rowsPerPage") == 35);
+    }
+}
\ No newline at end of file
Index: src/java/org/apache/commons/configuration/PatternSubtreeConfigurationWrapper.java
===================================================================
--- src/java/org/apache/commons/configuration/PatternSubtreeConfigurationWrapper.java	(revision 0)
+++ src/java/org/apache/commons/configuration/PatternSubtreeConfigurationWrapper.java	(revision 0)
@@ -0,0 +1,441 @@
+package org.apache.commons.configuration;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.Collection;
+import java.io.Writer;
+import java.io.Reader;
+
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.apache.commons.configuration.event.ConfigurationErrorListener;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.ExpressionEngine;
+
+/**
+ * Wraps a HierarchicalConfiguration and allows subtrees to be access via a configured path with
+ * replaceable tokens derived from the ConfigurationInterpolator. When used with injection frameworks
+ * such as Spring it allows components to be injected with subtrees of the configuration.
+ */
+public class PatternSubtreeConfigurationWrapper extends AbstractHierarchicalFileConfiguration
+{
+    private final AbstractHierarchicalFileConfiguration config;
+    private final String path;
+    private final boolean trailing;
+    private boolean init = false;
+
+    /*
+     * Prevent recursion while resolving unprefixed properties.
+     */
+    private static ThreadLocal recursive = new ThreadLocal() {
+        protected synchronized Object initialValue() {
+            return Boolean.FALSE;
+        }
+    };
+
+    /**
+     * Constructor
+     * @param config The Configuration to be wrapped.
+     * @param path The base path pattern.
+     */
+    public PatternSubtreeConfigurationWrapper(AbstractHierarchicalFileConfiguration config, String path)
+    {
+        this.config = config;
+        this.path = path;
+        this.trailing = path.endsWith("/");
+        this.init = true;
+    }
+
+    public void addProperty(String key, Object value)
+    {
+        config.addProperty(makePath(key), value);
+    }
+
+    public void clear()
+    {
+        getConfig().clear();
+    }
+
+    public void clearProperty(String key)
+    {
+        config.clearProperty(makePath(key));
+    }
+
+    public boolean containsKey(String key)
+    {
+        return config.containsKey(makePath(key));
+    }
+
+    public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
+    {
+        return config.getBigDecimal(makePath(key), defaultValue);
+    }
+
+    public BigDecimal getBigDecimal(String key)
+    {
+        return config.getBigDecimal(makePath(key));
+    }
+
+    public BigInteger getBigInteger(String key, BigInteger defaultValue)
+    {
+        return config.getBigInteger(makePath(key), defaultValue);
+    }
+
+    public BigInteger getBigInteger(String key)
+    {
+        return config.getBigInteger(makePath(key));
+    }
+
+    public boolean getBoolean(String key, boolean defaultValue)
+    {
+        return config.getBoolean(makePath(key), defaultValue);
+    }
+
+    public Boolean getBoolean(String key, Boolean defaultValue)
+    {
+        return config.getBoolean(makePath(key), defaultValue);
+    }
+
+    public boolean getBoolean(String key)
+    {
+        return config.getBoolean(makePath(key));
+    }
+
+    public byte getByte(String key, byte defaultValue)
+    {
+        return config.getByte(makePath(key), defaultValue);
+    }
+
+    public Byte getByte(String key, Byte defaultValue)
+    {
+        return config.getByte(makePath(key), defaultValue);
+    }
+
+    public byte getByte(String key)
+    {
+        return config.getByte(makePath(key));
+    }
+
+    public double getDouble(String key, double defaultValue)
+    {
+        return config.getDouble(makePath(key), defaultValue);
+    }
+
+    public Double getDouble(String key, Double defaultValue)
+    {
+        return config.getDouble(makePath(key), defaultValue);
+    }
+
+    public double getDouble(String key)
+    {
+        return config.getDouble(makePath(key));
+    }
+
+    public float getFloat(String key, float defaultValue)
+    {
+        return config.getFloat(makePath(key), defaultValue);
+    }
+
+    public Float getFloat(String key, Float defaultValue)
+    {
+        return config.getFloat(makePath(key), defaultValue);
+    }
+
+    public float getFloat(String key)
+    {
+        return config.getFloat(makePath(key));
+    }
+
+    public int getInt(String key, int defaultValue)
+    {
+        return config.getInt(makePath(key), defaultValue);
+    }
+
+    public int getInt(String key)
+    {
+        return config.getInt(makePath(key));
+    }
+
+    public Integer getInteger(String key, Integer defaultValue)
+    {
+        return config.getInteger(makePath(key), defaultValue);
+    }
+
+    public Iterator getKeys()
+    {
+        return config.getKeys(makePath());
+    }
+
+    public Iterator getKeys(String prefix)
+    {
+        return config.getKeys(makePath(prefix));
+    }
+
+    public List getList(String key, List defaultValue)
+    {
+        return config.getList(makePath(key), defaultValue);
+    }
+
+    public List getList(String key)
+    {
+        return config.getList(makePath(key));
+    }
+
+    public long getLong(String key, long defaultValue)
+    {
+        return config.getLong(makePath(key), defaultValue);
+    }
+
+    public Long getLong(String key, Long defaultValue)
+    {
+        return config.getLong(makePath(key), defaultValue);
+    }
+
+    public long getLong(String key)
+    {
+        return config.getLong(makePath(key));
+    }
+
+    public Properties getProperties(String key)
+    {
+        return config.getProperties(makePath(key));
+    }
+
+    public Object getProperty(String key)
+    {
+        return config.getProperty(makePath(key));
+    }
+
+    public short getShort(String key, short defaultValue)
+    {
+        return config.getShort(makePath(key), defaultValue);
+    }
+
+    public Short getShort(String key, Short defaultValue)
+    {
+        return config.getShort(makePath(key), defaultValue);
+    }
+
+    public short getShort(String key)
+    {
+        return config.getShort(makePath(key));
+    }
+
+    public String getString(String key, String defaultValue)
+    {
+        return config.getString(makePath(key), defaultValue);
+    }
+
+    public String getString(String key)
+    {
+        return config.getString(makePath(key));
+    }
+
+    public String[] getStringArray(String key)
+    {
+        return config.getStringArray(makePath(key));
+    }
+
+    public boolean isEmpty()
+    {
+        return getConfig().isEmpty();
+    }
+
+    public void setProperty(String key, Object value)
+    {
+        getConfig().setProperty(key, value);
+    }
+
+    public Configuration subset(String prefix)
+    {
+        return getConfig().subset(prefix);
+    }
+
+    public Node getRoot()
+    {
+        return getConfig().getRoot();
+    }
+
+    public void setRoot(Node node)
+    {
+        if (init)
+        {
+            getConfig().setRoot(node);
+        }
+        else
+        {
+            super.setRoot(node);
+        }
+    }
+
+    public ConfigurationNode getRootNode()
+    {
+        return getConfig().getRootNode();
+    }
+
+    public void setRootNode(ConfigurationNode rootNode)
+    {
+        if (init)
+        {
+            getConfig().setRootNode(rootNode);
+        }
+        else
+        {
+            super.setRootNode(rootNode);
+        }
+    }
+
+    public ExpressionEngine getExpressionEngine()
+    {
+        return config.getExpressionEngine();
+    }
+
+    public void setExpressionEngine(ExpressionEngine expressionEngine)
+    {
+        if (init)
+        {
+            config.setExpressionEngine(expressionEngine);
+        }
+        else
+        {
+            super.setExpressionEngine(expressionEngine);
+        }
+    }
+
+    public void addNodes(String key, Collection nodes)
+    {
+        getConfig().addNodes(key, nodes);
+    }
+
+    public SubnodeConfiguration configurationAt(String key, boolean supportUpdates)
+    {
+        return config.configurationAt(makePath(key), supportUpdates);
+    }
+
+    public SubnodeConfiguration configurationAt(String key)
+    {
+        return config.configurationAt(makePath(key));
+    }
+
+    public List configurationsAt(String key)
+    {
+        return config.configurationsAt(makePath(key));
+    }
+
+    public void clearTree(String key)
+    {
+        config.clearTree(makePath(key));
+    }
+
+    public int getMaxIndex(String key)
+    {
+        return config.getMaxIndex(makePath(key));
+    }
+
+    public Configuration interpolatedConfiguration()
+    {
+        return getConfig().interpolatedConfiguration();
+    }
+
+    public void addConfigurationListener(ConfigurationListener l)
+    {
+        getConfig().addConfigurationListener(l);
+    }
+
+    public boolean removeConfigurationListener(ConfigurationListener l)
+    {
+        return getConfig().removeConfigurationListener(l);
+    }
+
+    public Collection getConfigurationListeners()
+    {
+        return getConfig().getConfigurationListeners();
+    }
+
+    public void clearConfigurationListeners()
+    {
+        getConfig().clearConfigurationListeners();
+    }
+
+    public void addErrorListener(ConfigurationErrorListener l)
+    {
+        getConfig().addErrorListener(l);
+    }
+
+    public boolean removeErrorListener(ConfigurationErrorListener l)
+    {
+        return getConfig().removeErrorListener(l);
+    }
+
+    public void clearErrorListeners()
+    {
+        getConfig().clearErrorListeners();
+    }
+
+    public void save(Writer writer) throws ConfigurationException
+    {
+        config.save(writer);
+    }
+
+    public void load(Reader reader) throws ConfigurationException
+    {
+        config.load(reader);
+    }
+
+    public Collection getErrorListeners()
+    {
+        return getConfig().getErrorListeners();
+    }
+
+    protected Object resolveContainerStore(String key)
+    {
+        if (((Boolean)recursive.get()).booleanValue())
+        {
+            return null;
+        }
+        recursive.set(Boolean.TRUE);
+        try
+        {
+            return super.resolveContainerStore(key);
+        }
+        finally
+        {
+            recursive.set(Boolean.FALSE);
+        }
+    }
+
+    private HierarchicalConfiguration getConfig()
+    {
+        return config.configurationAt(makePath());
+    }
+
+    private String makePath()
+    {
+        String pathPattern = (trailing) ? path.substring(0, path.length() - 1) : path;
+        return getSubstitutor().replace(pathPattern);
+    }
+
+    /*
+     * Resolve the root expression and then add the item being retrieved. Insert a
+     * separator character as required.
+     */
+    private String makePath(String item)
+    {
+        String pathPattern;
+        if ((item.length() == 0 || item.startsWith("/")) && trailing)
+        {
+            pathPattern = path.substring(0, path.length() - 1);
+        }
+        else  if (!item.startsWith("/") || !trailing)
+        {
+            pathPattern = path + "/";
+        }
+        else
+        {
+            pathPattern = path;
+        }
+        return getSubstitutor().replace(pathPattern) + item;
+    }
+}
\ No newline at end of file
Index: src/java/org/apache/commons/configuration/DynamicCombinedConfiguration.java
===================================================================
--- src/java/org/apache/commons/configuration/DynamicCombinedConfiguration.java	(revision 0)
+++ src/java/org/apache/commons/configuration/DynamicCombinedConfiguration.java	(revision 0)
@@ -0,0 +1,823 @@
+/*
+ * 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.configuration;
+
+import org.apache.commons.configuration.event.ConfigurationErrorListener;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.apache.commons.configuration.tree.NodeCombiner;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.ExpressionEngine;
+
+import java.util.List;
+import java.util.Iterator;
+import java.util.Collection;
+import java.util.Set;
+import java.util.Properties;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+/**
+ *
+ */
+public class DynamicCombinedConfiguration extends CombinedConfiguration
+{
+    private Map configs = new HashMap();
+
+    /** Stores a list with the contained configurations. */
+    private List configurations = new ArrayList();
+
+    /** Stores a map with the named configurations. */
+    private Map namedConfigurations = new HashMap();
+
+    private String keyPattern;
+
+    /** Stores the combiner. */
+    private NodeCombiner nodeCombiner;
+
+    /*
+     * Prevent recursion while resolving unprefixed properties.
+     */
+    private static ThreadLocal recursive = new ThreadLocal() {
+        protected synchronized Object initialValue() {
+            return Boolean.FALSE;
+        }
+    };
+
+    /**
+     * Creates a new instance of <code>CombinedConfiguration</code> and
+     * initializes the combiner to be used.
+     *
+     * @param comb the node combiner (can be <b>null</b>, then a union combiner
+     * is used as default)
+     */
+    public DynamicCombinedConfiguration(NodeCombiner comb)
+    {
+        super();
+        setNodeCombiner(comb);
+    }
+
+    /**
+     * Creates a new instance of <code>CombinedConfiguration</code> that uses
+     * a union combiner.
+     *
+     * @see org.apache.commons.configuration.tree.UnionCombiner
+     */
+    public DynamicCombinedConfiguration()
+    {
+        super();
+    }
+
+    public void setKeyPattern(String pattern)
+    {
+        this.keyPattern = pattern;
+    }
+
+    public String getKeyPattern()
+    {
+        return this.keyPattern;
+    }
+
+    /**
+     * Returns the node combiner that is used for creating the combined node
+     * structure.
+     *
+     * @return the node combiner
+     */
+    public NodeCombiner getNodeCombiner()
+    {
+        return nodeCombiner;
+    }
+
+    /**
+     * Sets the node combiner. This object will be used when the combined node
+     * structure is to be constructed. It must not be <b>null</b>, otherwise an
+     * <code>IllegalArgumentException</code> exception is thrown. Changing the
+     * node combiner causes an invalidation of this combined configuration, so
+     * that the new combiner immediately takes effect.
+     *
+     * @param nodeCombiner the node combiner
+     */
+    public void setNodeCombiner(NodeCombiner nodeCombiner)
+    {
+        if (nodeCombiner == null)
+        {
+            throw new IllegalArgumentException(
+                    "Node combiner must not be null!");
+        }
+        this.nodeCombiner = nodeCombiner;
+        invalidateAll();
+    }
+    /**
+     * Adds a new configuration to this combined configuration. It is possible
+     * (but not mandatory) to give the new configuration a name. This name must
+     * be unique, otherwise a <code>ConfigurationRuntimeException</code> will
+     * be thrown. With the optional <code>at</code> argument you can specify
+     * where in the resulting node structure the content of the added
+     * configuration should appear. This is a string that uses dots as property
+     * delimiters (independent on the current expression engine). For instance
+     * if you pass in the string <code>&quot;database.tables&quot;</code>,
+     * all properties of the added configuration will occur in this branch.
+     *
+     * @param config the configuration to add (must not be <b>null</b>)
+     * @param name the name of this configuration (can be <b>null</b>)
+     * @param at the position of this configuration in the combined tree (can be
+     * <b>null</b>)
+     */
+    public void addConfiguration(AbstractConfiguration config, String name,
+            String at)
+    {
+        ConfigData cd = new ConfigData(config, name, at);
+        configurations.add(cd);
+        if (name != null)
+        {
+            namedConfigurations.put(name, config);
+        }
+    }
+       /**
+     * Returns the number of configurations that are contained in this combined
+     * configuration.
+     *
+     * @return the number of contained configurations
+     */
+    public int getNumberOfConfigurations()
+    {
+        return configurations.size();
+    }
+
+    /**
+     * Returns the configuration at the specified index. The contained
+     * configurations are numbered in the order they were added to this combined
+     * configuration. The index of the first configuration is 0.
+     *
+     * @param index the index
+     * @return the configuration at this index
+     */
+    public Configuration getConfiguration(int index)
+    {
+        ConfigData cd = (ConfigData)configurations.get(index);
+        return cd.getConfiguration();
+    }
+
+    /**
+     * Returns the configuration with the given name. This can be <b>null</b>
+     * if no such configuration exists.
+     *
+     * @param name the name of the configuration
+     * @return the configuration with this name
+     */
+    public Configuration getConfiguration(String name)
+    {
+        return (Configuration)namedConfigurations.get(name);
+    }
+
+    /**
+     * Returns a set with the names of all configurations contained in this
+     * combined configuration. Of course here are only these configurations
+     * listed, for which a name was specified when they were added.
+     *
+     * @return a set with the names of the contained configurations (never
+     * <b>null</b>)
+     */
+    public Set getConfigurationNames()
+    {
+        return namedConfigurations.keySet();
+    }
+
+    /**
+     * Removes the configuration with the specified name.
+     *
+     * @param name the name of the configuration to be removed
+     * @return the removed configuration (<b>null</b> if this configuration
+     * was not found)
+     */
+    public Configuration removeConfiguration(String name)
+    {
+        Configuration conf = getConfiguration(name);
+        if (conf != null)
+        {
+            removeConfiguration(conf);
+        }
+        return conf;
+    }
+
+    /**
+     * Removes the specified configuration from this combined configuration.
+     *
+     * @param config the configuration to be removed
+     * @return a flag whether this configuration was found and could be removed
+     */
+    public boolean removeConfiguration(Configuration config)
+    {
+        for (int index = 0; index < getNumberOfConfigurations(); index++)
+        {
+            if ((((ConfigData)configurations.get(index)).getConfiguration() == config))
+            {
+                removeConfigurationAt(index);
+
+            }
+        }
+
+        return super.removeConfiguration(config);
+    }
+
+    /**
+     * Removes the configuration at the specified index.
+     *
+     * @param index the index
+     * @return the removed configuration
+     */
+    public Configuration removeConfigurationAt(int index)
+    {
+        ConfigData cd = (ConfigData)configurations.remove(index);
+        if (cd.getName() != null)
+        {
+            namedConfigurations.remove(cd.getName());
+        }
+        return super.removeConfigurationAt(index);
+    }
+    /**
+     * Returns the configuration root node of this combined configuration. This
+     * method will construct a combined node structure using the current node
+     * combiner if necessary.
+     *
+     * @return the combined root node
+     */
+    public ConfigurationNode getRootNode()
+    {
+        return getCurrentConfig().getRootNode();
+    }
+
+    public void setRootNode(ConfigurationNode rootNode)
+    {
+        if (configs != null)
+        {
+            this.getCurrentConfig().setRootNode(rootNode);
+        }
+        else
+        {
+            super.setRootNode(rootNode);
+        }
+    }
+
+    public void addProperty(String key, Object value)
+    {
+        this.getCurrentConfig().addProperty(key, value);
+    }
+
+    public void clear()
+    {
+        if (configs != null)
+        {
+            this.getCurrentConfig().clear();
+        }
+    }
+
+    public void clearProperty(String key)
+    {
+        this.getCurrentConfig().clearProperty(key);
+    }
+
+    public boolean containsKey(String key)
+    {
+        return this.getCurrentConfig().containsKey(key);
+    }
+
+    public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
+    {
+        return this.getCurrentConfig().getBigDecimal(key, defaultValue);
+    }
+
+    public BigDecimal getBigDecimal(String key)
+    {
+        return this.getCurrentConfig().getBigDecimal(key);
+    }
+
+    public BigInteger getBigInteger(String key, BigInteger defaultValue)
+    {
+        return this.getCurrentConfig().getBigInteger(key, defaultValue);
+    }
+
+    public BigInteger getBigInteger(String key)
+    {
+        return this.getCurrentConfig().getBigInteger(key);
+    }
+
+    public boolean getBoolean(String key, boolean defaultValue)
+    {
+        return this.getCurrentConfig().getBoolean(key, defaultValue);
+    }
+
+    public Boolean getBoolean(String key, Boolean defaultValue)
+    {
+        return this.getCurrentConfig().getBoolean(key, defaultValue);
+    }
+
+    public boolean getBoolean(String key)
+    {
+        return this.getCurrentConfig().getBoolean(key);
+    }
+
+    public byte getByte(String key, byte defaultValue)
+    {
+        return this.getCurrentConfig().getByte(key, defaultValue);
+    }
+
+    public Byte getByte(String key, Byte defaultValue)
+    {
+        return this.getCurrentConfig().getByte(key, defaultValue);
+    }
+
+    public byte getByte(String key)
+    {
+        return this.getCurrentConfig().getByte(key);
+    }
+
+    public double getDouble(String key, double defaultValue)
+    {
+        return this.getCurrentConfig().getDouble(key, defaultValue);
+    }
+
+    public Double getDouble(String key, Double defaultValue)
+    {
+        return this.getCurrentConfig().getDouble(key, defaultValue);
+    }
+
+    public double getDouble(String key)
+    {
+        return this.getCurrentConfig().getDouble(key);
+    }
+
+    public float getFloat(String key, float defaultValue)
+    {
+        return this.getCurrentConfig().getFloat(key, defaultValue);
+    }
+
+    public Float getFloat(String key, Float defaultValue)
+    {
+        return this.getCurrentConfig().getFloat(key, defaultValue);
+    }
+
+    public float getFloat(String key)
+    {
+        return this.getCurrentConfig().getFloat(key);
+    }
+
+    public int getInt(String key, int defaultValue)
+    {
+        return this.getCurrentConfig().getInt(key, defaultValue);
+    }
+
+    public int getInt(String key)
+    {
+        return this.getCurrentConfig().getInt(key);
+    }
+
+    public Integer getInteger(String key, Integer defaultValue)
+    {
+        return this.getCurrentConfig().getInteger(key, defaultValue);
+    }
+
+    public Iterator getKeys()
+    {
+        return this.getCurrentConfig().getKeys();
+    }
+
+    public Iterator getKeys(String prefix)
+    {
+        return this.getCurrentConfig().getKeys(prefix);
+    }
+
+    public List getList(String key, List defaultValue)
+    {
+        return this.getCurrentConfig().getList(key, defaultValue);
+    }
+
+    public List getList(String key)
+    {
+        return this.getCurrentConfig().getList(key);
+    }
+
+    public long getLong(String key, long defaultValue)
+    {
+        return this.getCurrentConfig().getLong(key, defaultValue);
+    }
+
+    public Long getLong(String key, Long defaultValue)
+    {
+        return this.getCurrentConfig().getLong(key, defaultValue);
+    }
+
+    public long getLong(String key)
+    {
+        return this.getCurrentConfig().getLong(key);
+    }
+
+    public Properties getProperties(String key)
+    {
+        return this.getCurrentConfig().getProperties(key);
+    }
+
+    public Object getProperty(String key)
+    {
+        return this.getCurrentConfig().getProperty(key);
+    }
+
+    public short getShort(String key, short defaultValue)
+    {
+        return this.getCurrentConfig().getShort(key, defaultValue);
+    }
+
+    public Short getShort(String key, Short defaultValue)
+    {
+        return this.getCurrentConfig().getShort(key, defaultValue);
+    }
+
+    public short getShort(String key)
+    {
+        return this.getCurrentConfig().getShort(key);
+    }
+
+    public String getString(String key, String defaultValue)
+    {
+        return this.getCurrentConfig().getString(key, defaultValue);
+    }
+
+    public String getString(String key)
+    {
+        return this.getCurrentConfig().getString(key);
+    }
+
+    public String[] getStringArray(String key)
+    {
+        return this.getCurrentConfig().getStringArray(key);
+    }
+
+    public boolean isEmpty()
+    {
+        return this.getCurrentConfig().isEmpty();
+    }
+
+    public void setProperty(String key, Object value)
+    {
+        if (configs != null)
+        {
+            this.getCurrentConfig().setProperty(key, value);
+        }
+    }
+
+    public Configuration subset(String prefix)
+    {
+        return this.getCurrentConfig().subset(prefix);
+    }
+
+    public Node getRoot()
+    {
+        return this.getCurrentConfig().getRoot();
+    }
+
+    public void setRoot(Node node)
+    {
+        if (configs != null)
+        {
+            this.getCurrentConfig().setRoot(node);
+        }
+        else
+        {
+            super.setRoot(node);
+        }
+    }
+
+    public ExpressionEngine getExpressionEngine()
+    {
+        return super.getExpressionEngine();
+    }
+
+    public void setExpressionEngine(ExpressionEngine expressionEngine)
+    {
+        super.setExpressionEngine(expressionEngine);
+    }
+
+    public void addNodes(String key, Collection nodes)
+    {
+        this.getCurrentConfig().addNodes(key, nodes);
+    }
+
+    public SubnodeConfiguration configurationAt(String key, boolean supportUpdates)
+    {
+        return this.getCurrentConfig().configurationAt(key, supportUpdates);
+    }
+
+    public SubnodeConfiguration configurationAt(String key)
+    {
+        return this.getCurrentConfig().configurationAt(key);
+    }
+
+    public List configurationsAt(String key)
+    {
+        return this.getCurrentConfig().configurationsAt(key);
+    }
+
+    public void clearTree(String key)
+    {
+        this.getCurrentConfig().clearTree(key);
+    }
+
+    public int getMaxIndex(String key)
+    {
+        return this.getCurrentConfig().getMaxIndex(key);
+    }
+
+    public Configuration interpolatedConfiguration()
+    {
+        return this.getCurrentConfig().interpolatedConfiguration();
+    }
+
+
+    /**
+     * Returns the configuration source, in which the specified key is defined.
+     * This method will determine the configuration node that is identified by
+     * the given key. The following constellations are possible:
+     * <ul>
+     * <li>If no node object is found for this key, <b>null</b> is returned.</li>
+     * <li>If the key maps to multiple nodes belonging to different
+     * configuration sources, a <code>IllegalArgumentException</code> is
+     * thrown (in this case no unique source can be determined).</li>
+     * <li>If exactly one node is found for the key, the (child) configuration
+     * object, to which the node belongs is determined and returned.</li>
+     * <li>For keys that have been added directly to this combined
+     * configuration and that do not belong to the namespaces defined by
+     * existing child configurations this configuration will be returned.</li>
+     * </ul>
+     *
+     * @param key the key of a configuration property
+     * @return the configuration, to which this property belongs or <b>null</b>
+     * if the key cannot be resolved
+     * @throws IllegalArgumentException if the key maps to multiple properties
+     * and the source cannot be determined, or if the key is <b>null</b>
+     */
+    public Configuration getSource(String key)
+    {
+        if (key == null)
+        {
+            throw new IllegalArgumentException("Key must not be null!");
+        }
+        return getCurrentConfig().getSource(key);
+    }
+
+    public void addConfigurationListener(ConfigurationListener l)
+    {
+        super.addConfigurationListener(l);
+
+        Iterator iter = configs.values().iterator();
+        while (iter.hasNext())
+        {
+            CombinedConfiguration config = (CombinedConfiguration)iter.next();
+            config.addConfigurationListener(l);
+        }
+    }
+
+    public boolean removeConfigurationListener(ConfigurationListener l)
+    {
+        Iterator iter = configs.values().iterator();
+        while (iter.hasNext())
+        {
+            CombinedConfiguration config = (CombinedConfiguration)iter.next();
+            config.removeConfigurationListener(l);
+        }
+        return super.removeConfigurationListener(l);
+    }
+
+    public Collection getConfigurationListeners()
+    {
+        return super.getConfigurationListeners();
+    }
+
+    public void clearConfigurationListeners()
+    {
+        Iterator iter = configs.values().iterator();
+        while (iter.hasNext())
+        {
+            CombinedConfiguration config = (CombinedConfiguration)iter.next();
+            config.clearConfigurationListeners();
+        }
+        super.clearConfigurationListeners();
+    }
+
+    public void addErrorListener(ConfigurationErrorListener l)
+    {
+        Iterator iter = configs.values().iterator();
+        while (iter.hasNext())
+        {
+            CombinedConfiguration config = (CombinedConfiguration)iter.next();
+            config.addErrorListener(l);
+        }
+        super.addErrorListener(l);
+    }
+
+    public boolean removeErrorListener(ConfigurationErrorListener l)
+    {
+        Iterator iter = configs.values().iterator();
+        while (iter.hasNext())
+        {
+            CombinedConfiguration config = (CombinedConfiguration)iter.next();
+            config.removeErrorListener(l);
+        }
+        return super.removeErrorListener(l);
+    }
+
+    public void clearErrorListeners()
+    {
+        Iterator iter = configs.values().iterator();
+        while (iter.hasNext())
+        {
+            CombinedConfiguration config = (CombinedConfiguration)iter.next();
+            config.clearErrorListeners();
+        }
+        super.clearErrorListeners();
+    }
+
+    public Collection getErrorListeners()
+    {
+        return super.getErrorListeners();
+    }
+
+
+
+    /**
+     * Returns a copy of this object. This implementation performs a deep clone,
+     * i.e. all contained configurations will be cloned, too. For this to work,
+     * all contained configurations must be cloneable. Registered event
+     * listeners won't be cloned. The clone will use the same node combiner than
+     * the original.
+     *
+     * @return the copied object
+     */
+    public Object clone()
+    {
+        return super.clone();
+    }
+
+
+
+    /**
+     * Invalidates the current combined configuration. This means that the next time a
+     * property is accessed the combined node structure must be re-constructed.
+     * Invalidation of a combined configuration also means that an event of type
+     * <code>EVENT_COMBINED_INVALIDATE</code> is fired. Note that while other
+     * events most times appear twice (once before and once after an update),
+     * this event is only fired once (after update).
+     */
+    public void invalidate()
+    {
+        getCurrentConfig().invalidate();
+    }
+
+    public void invalidateAll()
+    {
+        if (configs == null)
+        {
+            return;
+        }
+        Iterator iter = configs.values().iterator();
+        while (iter.hasNext())
+        {
+           CombinedConfiguration config = (CombinedConfiguration)iter.next();
+           config.invalidate();
+        }
+    }
+
+    /*
+     * Don't allow resolveContainerStore to be called recursively.
+     * @param key The key to resolve.
+     * @return The value of the key.
+     */
+    protected Object resolveContainerStore(String key)
+    {
+        if (((Boolean)recursive.get()).booleanValue())
+        {
+            return null;
+        }
+        recursive.set(Boolean.TRUE);
+        try
+        {
+            return super.resolveContainerStore(key);
+        }
+        finally
+        {
+            recursive.set(Boolean.FALSE);
+        }
+    }
+
+    private CombinedConfiguration getCurrentConfig()
+    {
+        String key = getSubstitutor().replace(keyPattern);
+        CombinedConfiguration config;
+        synchronized(getNodeCombiner())
+        {
+            config = (CombinedConfiguration)configs.get(key);
+            if (config == null)
+            {
+                config = new CombinedConfiguration(getNodeCombiner());
+                config.setExpressionEngine(this.getExpressionEngine());
+                Iterator iter = config.getErrorListeners().iterator();
+                while (iter.hasNext())
+                {
+                    ConfigurationErrorListener listener = (ConfigurationErrorListener)iter.next();
+                    config.addErrorListener(listener);
+                }
+                iter = config.getConfigurationListeners().iterator();
+                while (iter.hasNext())
+                {
+                    ConfigurationListener listener = (ConfigurationListener)iter.next();
+                    config.addConfigurationListener(listener);
+                }
+                config.setForceReloadCheck(isForceReloadCheck());
+                iter = configurations.iterator();
+                while (iter.hasNext())
+                {
+                    ConfigData data = (ConfigData)iter.next();
+                    config.addConfiguration(data.getConfiguration(), data.getName(),
+                            data.getAt());
+                }
+                configs.put(key, config);
+            }
+        }
+        return config;
+    }
+
+
+    class ConfigData
+    {
+        /** Stores a reference to the configuration. */
+        private AbstractConfiguration configuration;
+
+        /** Stores the name under which the configuration is stored. */
+        private String name;
+
+        /** Stores the at string.*/
+        private String at;
+
+                /**
+         * Creates a new instance of <code>ConfigData</code> and initializes
+         * it.
+         *
+         * @param config the configuration
+         * @param n the name
+         * @param at the at position
+         */
+        public ConfigData(AbstractConfiguration config, String n, String at)
+        {
+            configuration = config;
+            name = n;
+            this.at = at;
+        }
+
+                /**
+         * Returns the stored configuration.
+         *
+         * @return the configuration
+         */
+        public AbstractConfiguration getConfiguration()
+        {
+            return configuration;
+        }
+
+        /**
+         * Returns the configuration's name.
+         *
+         * @return the name
+         */
+        public String getName()
+        {
+            return name;
+        }
+
+        /**
+         * Returns the at position of this configuration.
+         *
+         * @return the at position
+         */
+        public String getAt()
+        {
+            return at;
+        }
+
+    }
+}
Index: src/java/org/apache/commons/configuration/MultiFileHierarchicalConfiguration.java
===================================================================
--- src/java/org/apache/commons/configuration/MultiFileHierarchicalConfiguration.java	(revision 0)
+++ src/java/org/apache/commons/configuration/MultiFileHierarchicalConfiguration.java	(revision 0)
@@ -0,0 +1,666 @@
+/*
+ * 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.configuration;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Collection;
+import java.util.HashMap;
+import java.net.URL;
+import java.net.MalformedURLException;
+import java.io.FileNotFoundException;
+import java.io.Writer;
+import java.io.Reader;
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.configuration.event.ConfigurationErrorListener;
+import org.apache.commons.configuration.event.ConfigurationListener;
+import org.apache.commons.configuration.event.ConfigurationEvent;
+import org.apache.commons.configuration.event.ConfigurationErrorEvent;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.ExpressionEngine;
+
+/**
+ * This class provides access to multiple configuration files that reside in a location that
+ * can be specified by a pattern allowing applications to be multi-tenant.  For example,
+ * providing a pattern of "file:///opt/config/${product}/${client}/config.xml" will result in
+ * "product" and "client" being resolved on every call. The configuration resulting from the
+ * resolved pattern will be saved for future access.
+ */
+public class MultiFileHierarchicalConfiguration extends AbstractHierarchicalFileConfiguration
+    implements ConfigurationListener, ConfigurationErrorListener
+{
+    private final Map configurationsMap = new HashMap();
+    private String pattern;
+    private boolean init = false;
+    private static final String FILE_URL_PREFIX = "file:";
+    /*
+     * Prevent recursion while resolving unprefixed properties.
+     */
+    private static ThreadLocal recursive = new ThreadLocal() {
+        protected synchronized Object initialValue() {
+            return Boolean.FALSE;
+        }
+    };
+
+    /**
+     * Default Constructor.
+     */
+    public MultiFileHierarchicalConfiguration()
+    {
+        super();
+        this.init = true;
+    }
+
+    /**
+     * Construct the configuration with the specified pattern.
+     * @param pathPattern The pattern to use to locate configuration files.
+     */
+    public MultiFileHierarchicalConfiguration(String pathPattern)
+    {
+        super();
+        this.pattern = pathPattern;
+        this.init = true;
+    }
+
+    /**
+     * Set the File pattern
+     * @param pathPattern The pattern for the path to the configuration.
+     */
+    public void setFilePattern(String pathPattern)
+    {
+        this.pattern = pathPattern;
+    }
+
+    /**
+     * Creates the file configuration delegate, i.e. the object that implements
+     * functionality required by the <code>FileConfiguration</code> interface.
+     * This base implementation will return an instance of the
+     * <code>FileConfigurationDelegate</code> class. Derived classes may
+     * override it to create a different delegate object.
+     *
+     * @return the file configuration delegate
+     */
+    protected FileConfigurationDelegate createDelegate()
+    {
+        return new FileConfigurationDelegate();
+    }
+
+    public void addProperty(String key, Object value)
+    {
+        this.getConfiguration().addProperty(key, value);
+    }
+
+    public void clear()
+    {
+        this.getConfiguration().clear();
+    }
+
+    public void clearProperty(String key)
+    {
+        this.getConfiguration().clearProperty(key);
+    }
+
+    public boolean containsKey(String key)
+    {
+        return this.getConfiguration().containsKey(key);
+    }
+
+    public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
+    {
+        return this.getConfiguration().getBigDecimal(key, defaultValue);
+    }
+
+    public BigDecimal getBigDecimal(String key)
+    {
+        return this.getConfiguration().getBigDecimal(key);
+    }
+
+    public BigInteger getBigInteger(String key, BigInteger defaultValue)
+    {
+        return this.getConfiguration().getBigInteger(key, defaultValue);
+    }
+
+    public BigInteger getBigInteger(String key)
+    {
+        return this.getConfiguration().getBigInteger(key);
+    }
+
+    public boolean getBoolean(String key, boolean defaultValue)
+    {
+        return this.getConfiguration().getBoolean(key, defaultValue);
+    }
+
+    public Boolean getBoolean(String key, Boolean defaultValue)
+    {
+        return this.getConfiguration().getBoolean(key, defaultValue);
+    }
+
+    public boolean getBoolean(String key)
+    {
+        return this.getConfiguration().getBoolean(key);
+    }
+
+    public byte getByte(String key, byte defaultValue)
+    {
+        return this.getConfiguration().getByte(key, defaultValue);
+    }
+
+    public Byte getByte(String key, Byte defaultValue)
+    {
+        return this.getConfiguration().getByte(key, defaultValue);
+    }
+
+    public byte getByte(String key)
+    {
+        return this.getConfiguration().getByte(key);
+    }
+
+    public double getDouble(String key, double defaultValue)
+    {
+        return this.getConfiguration().getDouble(key, defaultValue);
+    }
+
+    public Double getDouble(String key, Double defaultValue)
+    {
+        return this.getConfiguration().getDouble(key, defaultValue);
+    }
+
+    public double getDouble(String key)
+    {
+        return this.getConfiguration().getDouble(key);
+    }
+
+    public float getFloat(String key, float defaultValue)
+    {
+        return this.getConfiguration().getFloat(key, defaultValue);
+    }
+
+    public Float getFloat(String key, Float defaultValue)
+    {
+        return this.getConfiguration().getFloat(key, defaultValue);
+    }
+
+    public float getFloat(String key)
+    {
+        return this.getConfiguration().getFloat(key);
+    }
+
+    public int getInt(String key, int defaultValue)
+    {
+        return this.getConfiguration().getInt(key, defaultValue);
+    }
+
+    public int getInt(String key)
+    {
+        return this.getConfiguration().getInt(key);
+    }
+
+    public Integer getInteger(String key, Integer defaultValue)
+    {
+        return this.getConfiguration().getInteger(key, defaultValue);
+    }
+
+    public Iterator getKeys()
+    {
+        return this.getConfiguration().getKeys();
+    }
+
+    public Iterator getKeys(String prefix)
+    {
+        return this.getConfiguration().getKeys(prefix);
+    }
+
+    public List getList(String key, List defaultValue)
+    {
+        return this.getConfiguration().getList(key, defaultValue);
+    }
+
+    public List getList(String key)
+    {
+        return this.getConfiguration().getList(key);
+    }
+
+    public long getLong(String key, long defaultValue)
+    {
+        return this.getConfiguration().getLong(key, defaultValue);
+    }
+
+    public Long getLong(String key, Long defaultValue)
+    {
+        return this.getConfiguration().getLong(key, defaultValue);
+    }
+
+    public long getLong(String key)
+    {
+        return this.getConfiguration().getLong(key);
+    }
+
+    public Properties getProperties(String key)
+    {
+        return this.getConfiguration().getProperties(key);
+    }
+
+    public Object getProperty(String key)
+    {
+        return this.getConfiguration().getProperty(key);
+    }
+
+    public short getShort(String key, short defaultValue)
+    {
+        return this.getConfiguration().getShort(key, defaultValue);
+    }
+
+    public Short getShort(String key, Short defaultValue)
+    {
+        return this.getConfiguration().getShort(key, defaultValue);
+    }
+
+    public short getShort(String key)
+    {
+        return this.getConfiguration().getShort(key);
+    }
+
+    public String getString(String key, String defaultValue)
+    {
+        return this.getConfiguration().getString(key, defaultValue);
+    }
+
+    public String getString(String key)
+    {
+        return this.getConfiguration().getString(key);
+    }
+
+    public String[] getStringArray(String key)
+    {
+        return this.getConfiguration().getStringArray(key);
+    }
+
+    public boolean isEmpty()
+    {
+        return this.getConfiguration().isEmpty();
+    }
+
+    public void setProperty(String key, Object value)
+    {
+        if (init)
+        {
+            this.getConfiguration().setProperty(key, value);
+        }
+    }
+
+    public Configuration subset(String prefix)
+    {
+        return this.getConfiguration().subset(prefix);
+    }
+
+    public Node getRoot()
+    {
+        return this.getConfiguration().getRoot();
+    }
+
+    public void setRoot(Node node)
+    {
+        if (init)
+        {
+            this.getConfiguration().setRoot(node);
+        }
+        else
+        {
+            super.setRoot(node);
+        }
+    }
+
+    public ConfigurationNode getRootNode()
+    {
+        return this.getConfiguration().getRootNode();
+    }
+
+    public void setRootNode(ConfigurationNode rootNode)
+    {
+        if (init)
+        {
+            this.getConfiguration().setRootNode(rootNode);
+        }
+        else
+        {
+            super.setRootNode(rootNode);
+        }
+    }
+
+    public ExpressionEngine getExpressionEngine()
+    {
+        return super.getExpressionEngine();
+    }
+
+    public void setExpressionEngine(ExpressionEngine expressionEngine)
+    {
+        super.setExpressionEngine(expressionEngine);
+    }
+
+    public void addNodes(String key, Collection nodes)
+    {
+        this.getConfiguration().addNodes(key, nodes);
+    }
+
+    public SubnodeConfiguration configurationAt(String key, boolean supportUpdates)
+    {
+        return this.getConfiguration().configurationAt(key, supportUpdates);
+    }
+
+    public SubnodeConfiguration configurationAt(String key)
+    {
+        return this.getConfiguration().configurationAt(key);
+    }
+
+    public List configurationsAt(String key)
+    {
+        return this.getConfiguration().configurationsAt(key);
+    }
+
+    public void clearTree(String key)
+    {
+        this.getConfiguration().clearTree(key);
+    }
+
+    public int getMaxIndex(String key)
+    {
+        return this.getConfiguration().getMaxIndex(key);
+    }
+
+    public Configuration interpolatedConfiguration()
+    {
+        return this.getConfiguration().interpolatedConfiguration();
+    }
+
+    public void addConfigurationListener(ConfigurationListener l)
+    {
+        super.addConfigurationListener(l);
+    }
+
+    public boolean removeConfigurationListener(ConfigurationListener l)
+    {
+        return super.removeConfigurationListener(l);
+    }
+
+    public Collection getConfigurationListeners()
+    {
+        return super.getConfigurationListeners();
+    }
+
+    public void clearConfigurationListeners()
+    {
+        super.clearConfigurationListeners();
+    }
+
+    public void addErrorListener(ConfigurationErrorListener l)
+    {
+        super.addErrorListener(l);
+    }
+
+    public boolean removeErrorListener(ConfigurationErrorListener l)
+    {
+        return super.removeErrorListener(l);
+    }
+
+    public void clearErrorListeners()
+    {
+        super.clearErrorListeners();
+    }
+
+    public Collection getErrorListeners()
+    {
+        return super.getErrorListeners();
+    }
+
+    public void save(Writer writer) throws ConfigurationException
+    {
+        if (init)
+        {
+            this.getConfiguration().save(writer);
+        }
+    }
+
+    public void load(Reader reader) throws ConfigurationException
+    {
+        if (init)
+        {
+            this.getConfiguration().load(reader);
+        }
+    }
+
+    public void load() throws ConfigurationException
+    {
+        this.getConfiguration().load();
+    }
+
+    public void load(String fileName) throws ConfigurationException
+    {
+        this.getConfiguration().load(fileName);
+    }
+
+    public void load(File file) throws ConfigurationException
+    {
+        this.getConfiguration().load(file);
+    }
+
+    public void load(URL url) throws ConfigurationException
+    {
+        this.getConfiguration().load(url);
+    }
+
+    public void load(InputStream in) throws ConfigurationException
+    {
+        this.getConfiguration().load(in);
+    }
+
+    public void load(InputStream in, String encoding) throws ConfigurationException
+    {
+        this.getConfiguration().load(in, encoding);
+    }
+
+    public void save() throws ConfigurationException
+    {
+        this.getConfiguration().save();
+    }
+
+    public void save(String fileName) throws ConfigurationException
+    {
+        this.getConfiguration().save(fileName);
+    }
+
+    public void save(File file) throws ConfigurationException
+    {
+        this.getConfiguration().save(file);
+    }
+
+    public void save(URL url) throws ConfigurationException
+    {
+        this.getConfiguration().save(url);
+    }
+
+    public void save(OutputStream out) throws ConfigurationException
+    {
+        this.getConfiguration().save(out);
+    }
+
+    public void save(OutputStream out, String encoding) throws ConfigurationException
+    {
+        this.getConfiguration().save(out, encoding);
+    }
+
+    public void configurationChanged(ConfigurationEvent event)
+    {
+        if (event.getSource() instanceof XMLConfiguration)
+        {
+            Iterator iter = getConfigurationListeners().iterator();
+            while (iter.hasNext())
+            {
+                ConfigurationListener listener = (ConfigurationListener)iter.next();
+                listener.configurationChanged(event);
+            }
+        }
+    }
+
+    public void configurationError(ConfigurationErrorEvent event)
+    {
+        if (event.getSource() instanceof XMLConfiguration)
+        {
+            Iterator iter = getErrorListeners().iterator();
+            while (iter.hasNext())
+            {
+                ConfigurationErrorListener listener = (ConfigurationErrorListener)iter.next();
+                listener.configurationError(event);
+            }
+        }
+    }
+
+    /*
+     * Don't allow resolveContainerStore to be called recursively.
+     * @param key The key to resolve.
+     * @return The value of the key.
+     */
+    protected Object resolveContainerStore(String key)
+    {
+        if (((Boolean)recursive.get()).booleanValue())
+        {
+            return null;
+        }
+        recursive.set(Boolean.TRUE);
+        try
+        {
+            return super.resolveContainerStore(key);
+        }
+        finally
+        {
+            recursive.set(Boolean.FALSE);
+        }
+    }
+
+    /**
+     * Remove the current Configuration.
+     */
+    public void removeConfiguration()
+    {
+        String path = getSubstitutor().replace(pattern);
+        synchronized(configurationsMap)
+        {
+            configurationsMap.remove(path);
+        }
+    }
+
+    /**
+     * First checks to see if the cache exists, if it does, get the associated Configuration.
+     * If not it will load a new Configuration and save it in the cache.
+     *
+     * @return the Configuration associated with the current value of the path pattern.
+     */
+    private AbstractHierarchicalFileConfiguration getConfiguration()
+    {
+        if (pattern == null)
+        {
+            throw new ConfigurationRuntimeException("File pattern must be defined");
+        }
+        String path = getSubstitutor().replace(pattern);
+        synchronized(configurationsMap)
+        {
+            if (configurationsMap.containsKey(path))
+            {
+                return (AbstractHierarchicalFileConfiguration)configurationsMap.get(path);
+            }
+        }
+
+        if (path.equals(pattern))
+        {
+            XMLConfiguration configuration = new XMLConfiguration()
+            {
+                public void load() throws ConfigurationException
+                {
+                }
+                public void save() throws ConfigurationException
+                {
+                }
+            };
+            synchronized(configurationsMap)
+            {
+                configurationsMap.put(pattern, configuration);
+            }
+            return configuration;
+        }
+
+        XMLConfiguration configuration = new XMLConfiguration();
+        try
+        {
+            URL url = getURL(path);
+            configuration.setURL(url);
+            configuration.load();
+            configuration.setExpressionEngine(getExpressionEngine());
+            configuration.setReloadingStrategy(getReloadingStrategy());
+            configuration.addConfigurationListener(this);
+            configuration.addErrorListener(this);
+            synchronized(configurationsMap)
+            {
+                if (!configurationsMap.containsKey(path))
+                {
+                    configurationsMap.put(path, configuration);
+                }
+            }
+        }
+        catch (ConfigurationException ce)
+        {
+            throw new ConfigurationRuntimeException(ce);
+        }
+        catch (FileNotFoundException fnfe)
+        {
+            throw new ConfigurationRuntimeException(fnfe);
+        }
+
+        return configuration;
+    }
+
+    private URL getURL(String resourceLocation) throws FileNotFoundException
+    {
+        if (resourceLocation == null)
+        {
+            throw new IllegalArgumentException("A path pattern must be configured");
+        }
+        try
+        {
+            // try URL
+            return new URL(resourceLocation);
+        }
+        catch (MalformedURLException ex)
+        {
+            // no URL -> treat as file path
+            try
+            {
+                return new URL(FILE_URL_PREFIX + resourceLocation);
+            }
+            catch (MalformedURLException ex2)
+            {
+                throw new FileNotFoundException("Resource location [" + resourceLocation +
+                        "] is not a URL or a well-formed file path");
+            }
+        }
+    }
+}
