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>