You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by rg...@apache.org on 2009/01/26 08:05:21 UTC
svn commit: r737641 - in
/commons/proper/configuration/branches/configuration2_experimental:
src/main/java/org/apache/commons/configuration2/
src/test/java/org/apache/commons/configuration2/ src/test/resources/ xdocs/
xdocs/userguide/
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>"database.tables"</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>