Author: rgoers
Date: Mon Jan 26 07:05:21 2009
New Revision: 737641

URL: http://svn.apache.org/viewvc?rev=737641&view=rev
Log:
CONFIGURATION 361 - use basepath in MultiFileHierarchicalConfiguration. Also 
added DynamicCombinedConfiguration test to TestDefaultConfigurationBuilder

Added:
    
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/DynamicCombinedConfiguration.java
    
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDynamicCombinedConfiguration.java
    
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiTenentConfigurationBuilder.xml
Modified:
    
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/MultiFileHierarchicalConfiguration.java
    
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PatternSubtreeConfigurationWrapper.java
    
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDefaultConfigurationBuilder.java
    
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiConfiguration_1001.xml
    
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiConfiguration_1002.xml
    
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiConfiguration_1003.xml
    
commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml
    
commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_multitenant.xml

Added: 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/DynamicCombinedConfiguration.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/DynamicCombinedConfiguration.java?rev=737641&view=auto
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/DynamicCombinedConfiguration.java
 (added)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/DynamicCombinedConfiguration.java
 Mon Jan 26 07:05:21 2009
@@ -0,0 +1,812 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration2;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.commons.configuration2.event.ConfigurationErrorListener;
+import org.apache.commons.configuration2.event.ConfigurationListener;
+import org.apache.commons.configuration2.tree.ConfigurationNode;
+import org.apache.commons.configuration2.tree.ExpressionEngine;
+import org.apache.commons.configuration2.tree.NodeCombiner;
+
+/**
+ * DynamicCombinedConfiguration allows a set of CombinedConfigurations to be 
used. Each CombinedConfiguration
+ * is referenced by a key that is dynamically constructed from a key pattern 
on each call. The key pattern
+ * will be resolved using the configured ConfigurationInterpolator.
+ * @since 1.6
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html";>Commons
+ * Configuration team</a>
+ * @version $Id: DynamicCombinedConfiguration.java 727955 2008-12-19 07:06:16Z 
oheger $
+ */
+public class DynamicCombinedConfiguration extends CombinedConfiguration
+{
+    /**
+     * Prevent recursion while resolving unprefixed properties.
+     */
+    private static ThreadLocal<Boolean> recursive = new ThreadLocal<Boolean>()
+    {
+        protected synchronized Boolean initialValue()
+        {
+            return Boolean.FALSE;
+        }
+    };
+
+    /** The CombinedConfigurations */
+    private Map<String, CombinedConfiguration> configs = new HashMap<String, 
CombinedConfiguration>();
+
+    /** Stores a list with the contained configurations. */
+    private List<ConfigData> configurations = new ArrayList<ConfigData>();
+
+    /** Stores a map with the named configurations. */
+    private Map<String, AbstractConfiguration> namedConfigurations =
+            new HashMap<String, AbstractConfiguration>();
+
+    /** The key pattern for the CombinedConfiguration map */
+    private String keyPattern;
+
+    /** Stores the combiner. */
+    private NodeCombiner nodeCombiner;
+
+    /**
+     * 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.configuration2.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 = 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 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<String> 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 (( 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 = 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<String> getKeys()
+    {
+        return this.getCurrentConfig().getKeys();
+    }
+
+    public Iterator<String> getKeys(String prefix)
+    {
+        return this.getCurrentConfig().getKeys(prefix);
+    }
+
+    @Override
+    public <T> List<T> getList(String key, List<T> defaultValue)
+    {
+        return this.getCurrentConfig().getList(key, defaultValue);
+    }
+
+    @Override
+    public <T> List<T> 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 ExpressionEngine getExpressionEngine()
+    {
+        return super.getExpressionEngine();
+    }
+
+    public void setExpressionEngine(ExpressionEngine expressionEngine)
+    {
+        super.setExpressionEngine(expressionEngine);
+    }
+
+    public void addNodes(String key, Collection<? extends ConfigurationNode> 
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<HierarchicalConfiguration> 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);
+
+        for (CombinedConfiguration config : configs.values())
+        {
+            config.addConfigurationListener(l);
+        }
+    }
+
+    public boolean removeConfigurationListener(ConfigurationListener l)
+    {
+        for (CombinedConfiguration config : configs.values())
+        {
+            config.removeConfigurationListener(l);
+        }
+        return super.removeConfigurationListener(l);
+    }
+
+    public Collection getConfigurationListeners()
+    {
+        return super.getConfigurationListeners();
+    }
+
+    public void clearConfigurationListeners()
+    {
+        for (CombinedConfiguration config : configs.values())
+        {
+            config.clearConfigurationListeners();
+        }
+        super.clearConfigurationListeners();
+    }
+
+    public void addErrorListener(ConfigurationErrorListener l)
+    {
+        for (CombinedConfiguration config : configs.values())
+        {
+            config.addErrorListener(l);
+        }
+        super.addErrorListener(l);
+    }
+
+    public boolean removeErrorListener(ConfigurationErrorListener l)
+    {
+        for (CombinedConfiguration config : configs.values())
+        {
+            config.removeErrorListener(l);
+        }
+        return super.removeErrorListener(l);
+    }
+
+    public void clearErrorListeners()
+    {
+        for (CombinedConfiguration config : configs.values())
+        {
+            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;
+        }
+        for (CombinedConfiguration config : configs.values())
+        {
+           config.invalidate();
+        }
+    }
+
+    /*
+     * Don't allow resolveContainerStore to be called recursively. This happens
+     * when the key pattern does not resolve and the ConfigurationInterpolator
+     * calls resolveContainerStore, which in turn calls getProperty, which then
+     * calls getConfiguration. GetConfiguration then calls the interpoloator
+     * which starts it all over again.
+     * @param key The key to resolve.
+     * @return The value of the key.
+     */
+    protected Object resolveContainerStore(String key)
+    {
+        if (recursive.get())
+        {
+            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 = 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;
+    }
+
+    /**
+     * Internal class that identifies each Configuration.
+     */
+    static 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;
+        }
+
+    }
+}

Modified: 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/MultiFileHierarchicalConfiguration.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/MultiFileHierarchicalConfiguration.java?rev=737641&r1=737640&r2=737641&view=diff
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/MultiFileHierarchicalConfiguration.java
 (original)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/MultiFileHierarchicalConfiguration.java
 Mon Jan 26 07:05:21 2009
@@ -58,6 +58,17 @@
     /** FILE URL prefix */
     private static final String FILE_URL_PREFIX = "file:";
 
+    /**
+     * Prevent recursion while resolving unprefixed properties.
+     */
+    private static ThreadLocal<Boolean> recursive = new ThreadLocal<Boolean>()
+    {
+        protected synchronized Boolean initialValue()
+        {
+            return Boolean.FALSE;
+        }
+    };
+
     /** Map of configurations */
     private ConcurrentMap<String, XMLConfiguration> configurationsMap =
             new ConcurrentHashMap<String, XMLConfiguration>();
@@ -68,6 +79,9 @@
     /** True if the constructor has finished */
     private boolean init;
 
+    /** Return an empty configuration if loading fails */
+    private boolean ignoreException = true;
+
     /**
      * Default Constructor
      */
@@ -97,6 +111,29 @@
         this.pattern = pathPattern;
     }
 
+    /**
+     * Set to true if an empty Configuration should be returned when loading 
fails. If
+     * false an exception will be thrown.
+     * @param ignoreException The ignore value.
+     */
+    public void setIgnoreException(boolean ignoreException)
+    {
+        this.ignoreException = ignoreException;
+    }
+
+    /**
+     * 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)
     {
@@ -546,6 +583,42 @@
         }
     }
 
+    /*
+     * Don't allow resolveContainerStore to be called recursively. This happens
+     * when the file pattern does not resolve and the ConfigurationInterpolator
+     * calls resolveContainerStore, which in turn calls getProperty, which then
+     * calls getConfiguration. GetConfiguration then calls the interpoloator
+     * which starts it all over again.
+     * @param key The key to resolve.
+     * @return The value of the key.
+     */
+    protected Object resolveContainerStore(String key)
+    {
+        if (recursive.get())
+        {
+            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);
+        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.
@@ -597,11 +670,17 @@
         }
         catch (ConfigurationException ce)
         {
-            throw new ConfigurationRuntimeException(ce);
+            if (!ignoreException)
+            {
+                throw new ConfigurationRuntimeException(ce);
+            }
         }
         catch (FileNotFoundException fnfe)
         {
-            throw new ConfigurationRuntimeException(fnfe);
+            if (!ignoreException)
+            {
+                 throw new ConfigurationRuntimeException(fnfe);
+            }                
         }
 
         return configuration;
@@ -616,7 +695,7 @@
         try
         {
             // try URL
-            return new URL(resourceLocation);
+            return ConfigurationUtils.getURL(getBasePath(), resourceLocation);
         }
         catch (MalformedURLException ex)
         {

Modified: 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PatternSubtreeConfigurationWrapper.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PatternSubtreeConfigurationWrapper.java?rev=737641&r1=737640&r2=737641&view=diff
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PatternSubtreeConfigurationWrapper.java
 (original)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PatternSubtreeConfigurationWrapper.java
 Mon Jan 26 07:05:21 2009
@@ -43,6 +43,17 @@
  */
 public class PatternSubtreeConfigurationWrapper extends 
AbstractHierarchicalFileConfiguration
 {
+    /**
+     * Prevent recursion while resolving unprefixed properties.
+     */
+    private static ThreadLocal<Boolean> recursive = new ThreadLocal<Boolean>()
+    {
+        protected synchronized Boolean initialValue()
+        {
+            return Boolean.FALSE;
+        }
+    };
+
     /** The wrapped configuration */
     private final AbstractHierarchicalFileConfiguration config;
 
@@ -410,6 +421,32 @@
         return getConfig().getErrorListeners();
     }
 
+    /*
+     * Don't allow resolveContainerStore to be called recursively. This happens
+     * when the path pattern does not resolve and the ConfigurationInterpolator
+     * calls resolveContainerStore, which in turn calls getProperty, which then
+     * calls getConfiguration. GetConfiguration then calls the interpoloator
+     * which starts it all over again.
+     * @param key The key to resolve.
+     * @return The value of the key.
+     */
+    protected Object resolveContainerStore(String key)
+    {
+        if (recursive.get())
+        {
+            return null;
+        }
+        recursive.set(Boolean.TRUE);
+        try
+        {
+            return super.resolveContainerStore(key);
+        }
+        finally
+        {
+            recursive.set(Boolean.FALSE);
+        }
+    }
+
     private SubConfiguration<ConfigurationNode> getConfig()
     {
         return config.configurationAt(makePath());

Modified: 
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDefaultConfigurationBuilder.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDefaultConfigurationBuilder.java?rev=737641&r1=737640&r2=737641&view=diff
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDefaultConfigurationBuilder.java
 (original)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDefaultConfigurationBuilder.java
 Mon Jan 26 07:05:21 2009
@@ -74,6 +74,9 @@
     private static final File VALIDATION_FILE = ConfigurationAssert
             .getTestFile("testValidation.xml");
 
+    private static final File MULTI_TENENT_FILE = ConfigurationAssert
+            .getTestFile("testMultiTenentConfigurationBuilder.xml");
+
     /** Constant for the name of an optional configuration.*/
     private static final String OPTIONAL_NAME = "optionalConfig";
 
@@ -829,6 +832,43 @@
         assertEquals("Incorrect value retrieved","value1",value);
     }
 
+    public void testMultiTenentConfiguration() throws Exception
+    {
+        factory.setFile(MULTI_TENENT_FILE);
+        System.clearProperty("Id");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertTrue("Incorrect configuration", config instanceof 
DynamicCombinedConfiguration);
+
+        verify("1001", config, 15);
+        verify("1002", config, 25);
+        verify("1003", config, 35);
+        verify("1004", config, 50);
+        verify("1005", config, 50);
+    }
+
+    public void testMultiTenentConfiguration2() throws Exception
+    {
+        factory.setFile(MULTI_TENENT_FILE);
+        System.setProperty("Id", "1004");
+
+        CombinedConfiguration config = factory.getConfiguration(true);
+        assertTrue("Incorrect configuration", config instanceof 
DynamicCombinedConfiguration);
+
+        verify("1001", config, 15);
+        verify("1002", config, 25);
+        verify("1003", config, 35);
+        verify("1004", config, 50);
+        verify("1005", config, 50);
+    }
+
+    private void verify(String key, CombinedConfiguration config, int rows)
+    {
+        System.setProperty("Id", key);
+        int actual = config.getInt("rowsPerPage");
+        assertTrue("expected: " + rows + " actual: " + actual, actual == rows);
+    }
+
 
     /**
      * A specialized combined configuration implementation used for testing

Added: 
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDynamicCombinedConfiguration.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDynamicCombinedConfiguration.java?rev=737641&view=auto
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDynamicCombinedConfiguration.java
 (added)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDynamicCombinedConfiguration.java
 Mon Jan 26 07:05:21 2009
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration2;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ *
+ */
+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);
+    }
+}

Modified: 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiConfiguration_1001.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiConfiguration_1001.xml?rev=737641&r1=737640&r2=737641&view=diff
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiConfiguration_1001.xml
 (original)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiConfiguration_1001.xml
 Mon Jan 26 07:05:21 2009
@@ -1,15 +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>
+<?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>

Modified: 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiConfiguration_1002.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiConfiguration_1002.xml?rev=737641&r1=737640&r2=737641&view=diff
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiConfiguration_1002.xml
 (original)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiConfiguration_1002.xml
 Mon Jan 26 07:05:21 2009
@@ -1,15 +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>
+<?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>

Modified: 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiConfiguration_1003.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiConfiguration_1003.xml?rev=737641&r1=737640&r2=737641&view=diff
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiConfiguration_1003.xml
 (original)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiConfiguration_1003.xml
 Mon Jan 26 07:05:21 2009
@@ -1,15 +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>
+<?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>

Added: 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiTenentConfigurationBuilder.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiTenentConfigurationBuilder.xml?rev=737641&view=auto
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiTenentConfigurationBuilder.xml
 (added)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiTenentConfigurationBuilder.xml
 Mon Jan 26 07:05:21 2009
@@ -0,0 +1,23 @@
+<?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.configuration2.DynamicCombinedConfiguration"
+            keyPattern="$${sys:Id}">
+      <expressionEngine
+          
config-class="org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine"/>
+    </result>
+    <providers>
+      <provider config-tag="multifile"
+         
config-class="org.apache.commons.configuration2.DefaultConfigurationBuilder$FileConfigurationProvider"
+         
configurationClassName="org.apache.commons.configuration2.MultiFileHierarchicalConfiguration"/>
+    </providers>
+  </header>
+  <override>
+    <multifile filePattern="testMultiConfiguration_$$${sys:Id}.xml"
+               config-name="clientConfig"/>
+    <xml fileName="testMultiConfiguration_default.xml"
+         config-name="defaultConfig"/>
+  </override>
+</configuration>
\ No newline at end of file

Modified: 
commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml?rev=737641&r1=737640&r2=737641&view=diff
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml
 (original)
+++ 
commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml
 Mon Jan 26 07:05:21 2009
@@ -85,6 +85,12 @@
     </release>
 
     <release version="1.7" date="in SVN" description="">
+      <action dev="rgoers" type="fix" issue="CONFIGURATION-361">
+        MultiFileHierarchicalConfiguration was not using basepath to
+        construct the file url. It also threw an exception if the
+        file pattern resolved to a non-existant file. This is now
+        configurable.
+      </action>
       <action dev="oheger" type="fix" issue="CONFIGURATION-359">
         Fixed broken links to the API documentation in the user's guide.
       </action>

Modified: 
commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_multitenant.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_multitenant.xml?rev=737641&r1=737640&r2=737641&view=diff
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_multitenant.xml
 (original)
+++ 
commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_multitenant.xml
 Mon Jan 26 07:05:21 2009
@@ -77,7 +77,7 @@
   <header>
     <result delimiterParsingDisabled="true" forceReloadCheck="true"
             
config-class="org.apache.commons.configuration.DynamicCombinedConfiguration"
-            keyPattern="${sys:Id}">
+            keyPattern="$${sys:Id}">
       <expressionEngine
           
config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
     </result>
@@ -88,11 +88,26 @@
     </providers>
   </header>
   <override>
-    <multifile filePattern="/opt/configs/${sys:Id}/config.xml" 
config-name="clientConfig"/>
+    <multifile filePattern="/opt/configs/$$${sys:Id}/config.xml" 
config-name="clientConfig"/>
     <xml fileName="/opt/configs/default/config.xml" 
config-name="defaultConfig"/>
   </override>
 </configuration>
 ]]></source>
+        <p>
+          Note how the variables have multiple '$'. This is how variables are 
escaped and
+          is necessary because the variables will be interpolated multiple 
times. Each
+          attempt will remove the leading '$'. When there is only a single '$' 
in front
+          of the '{' the interpolator will then resolve the variable. The 
first extra '$'
+          is necessary because DefaultConfigurationBuilder will interpolate 
any variables
+          in the configuration. In the case of the multifile configuration 
item two
+          leading '$' characters are necessary before the variable because it 
will be
+          interpolated by both DefaultConfigurationBuilder and 
DynamicCombinedConfiguration
+          before MultiFileHierarchicalConfiguration gets the chance to 
evaluate it.
+          Although in this example one would not expect system properties to 
change
+          at runtime, other types of lookups such as the MDCStrLookup provided 
with
+          SLF4J require that the variables be evaluated as the configuration 
is being
+          accessed instead of when the configuration file is processed to 
behave as desired.
+        </p>
       </subsection>
       <subsection name="PatternSubtreeConfigurationWrapper">
         <p>


Reply via email to