/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.configuration;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.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.configuration.event.ConfigurationErrorListener;
import org.apache.commons.configuration.event.ConfigurationListener;
import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
import org.apache.commons.configuration.tree.ConfigurationNode;
import org.apache.commons.configuration.tree.ExpressionEngine;
import org.apache.commons.configuration.tree.NodeCombiner;
import org.apache.commons.lang.text.StrSubstitutor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* 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 1158121 2011-08-16 06:21:42Z oheger $
*/
public class DynamicCombinedConfiguration extends CombinedConfiguration
{
/**
* Prevent recursion while resolving unprefixed properties.
*/
private static ThreadLocal recursive = new ThreadLocal()
{
protected synchronized Object initialValue()
{
return Boolean.FALSE;
}
};
/** The CombinedConfigurations */
private Map configs = new HashMap();
/** Stores a list with the contained configurations. */
private List configurations = new ArrayList();
/** Stores a map with the named configurations. */
private Map namedConfigurations = new HashMap();
/** The key pattern for the CombinedConfiguration map */
private String keyPattern;
/** Stores the combiner. */
private NodeCombiner nodeCombiner;
/** The name of the logger to use for each CombinedConfiguration */
private String loggerName = DynamicCombinedConfiguration.class.getName();
/** The object for handling variable substitution in key patterns. */
private StrSubstitutor localSubst = new StrSubstitutor(new ConfigurationInterpolator());
/**
* 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);
setIgnoreReloadExceptions(false);
setLogger(LogFactory.getLog(DynamicCombinedConfiguration.class));
}
/**
* Creates a new instance of <code>CombinedConfiguration</code> that uses
* a union combiner.
*
* @see org.apache.commons.configuration.tree.UnionCombiner
*/
public DynamicCombinedConfiguration()
{
super();
setIgnoreReloadExceptions(false);
setLogger(LogFactory.getLog(DynamicCombinedConfiguration.class));
}
public void setKeyPattern(String pattern)
{
this.keyPattern = pattern;
}
public String getKeyPattern()
{
return this.keyPattern;
}
/**
* Set the name of the Logger to use on each CombinedConfiguration.
* @param name The Logger name.
*/
public void setLoggerName(String name)
{
this.loggerName = name;
}
/**
* 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 = (ConfigData) configurations.get(index);
return cd.getConfiguration();
}
/**
* Returns the configuration with the given name. This can be <b>null</b>
* if no such configuration exists.
*
* @param name the name of the configuration
* @return the configuration with this name
*/
public Configuration getConfiguration(String name)
{
return (Configuration) namedConfigurations.get(name);
}
/**
* Returns a set with the names of all configurations contained in this
* combined configuration. Of course here are only these configurations
* listed, for which a name was specified when they were added.
*
* @return a set with the names of the contained configurations (never
* <b>null</b>)
*/
public Set getConfigurationNames()
{
return namedConfigurations.keySet();
}
/**
* Removes the configuration with the specified name.
*
* @param name the name of the configuration to be removed
* @return the removed configuration (<b>null</b> if this configuration
* was not found)
*/
public Configuration removeConfiguration(String name)
{
Configuration conf = getConfiguration(name);
if (conf != null)
{
removeConfiguration(conf);
}
return conf;
}
/**
* Removes the specified configuration from this combined configuration.
*
* @param config the configuration to be removed
* @return a flag whether this configuration was found and could be removed
*/
public boolean removeConfiguration(Configuration config)
{
for (int index = 0; index < getNumberOfConfigurations(); index++)
{
if (((ConfigData) configurations.get(index)).getConfiguration() == config)
{
removeConfigurationAt(index);
}
}
return super.removeConfiguration(config);
}
/**
* Removes the configuration at the specified index.
*
* @param index the index
* @return the removed configuration
*/
public Configuration removeConfigurationAt(int index)
{
ConfigData cd = (ConfigData) configurations.remove(index);
if (cd.getName() != null)
{
namedConfigurations.remove(cd.getName());
}
return super.removeConfigurationAt(index);
}
/**
* Returns the configuration root node of this combined configuration. This
* method will construct a combined node structure using the current node
* combiner if necessary.
*
* @return the combined root node
*/
public ConfigurationNode getRootNode()
{
return getCurrentConfig().getRootNode();
}
public void setRootNode(ConfigurationNode rootNode)
{
if (configs != null)
{
this.getCurrentConfig().setRootNode(rootNode);
}
else
{
super.setRootNode(rootNode);
}
}
public void addProperty(String key, Object value)
{
this.getCurrentConfig().addProperty(key, value);
}
public void clear()
{
if (configs != null)
{
this.getCurrentConfig().clear();
}
}
public void clearProperty(String key)
{
this.getCurrentConfig().clearProperty(key);
}
public boolean containsKey(String key)
{
return this.getCurrentConfig().containsKey(key);
}
public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
{
return this.getCurrentConfig().getBigDecimal(key, defaultValue);
}
public BigDecimal getBigDecimal(String key)
{
return this.getCurrentConfig().getBigDecimal(key);
}
public BigInteger getBigInteger(String key, BigInteger defaultValue)
{
return this.getCurrentConfig().getBigInteger(key, defaultValue);
}
public BigInteger getBigInteger(String key)
{
return this.getCurrentConfig().getBigInteger(key);
}
public boolean getBoolean(String key, boolean defaultValue)
{
return this.getCurrentConfig().getBoolean(key, defaultValue);
}
public Boolean getBoolean(String key, Boolean defaultValue)
{
return this.getCurrentConfig().getBoolean(key, defaultValue);
}
public boolean getBoolean(String key)
{
return this.getCurrentConfig().getBoolean(key);
}
public byte getByte(String key, byte defaultValue)
{
return this.getCurrentConfig().getByte(key, defaultValue);
}
public Byte getByte(String key, Byte defaultValue)
{
return this.getCurrentConfig().getByte(key, defaultValue);
}
public byte getByte(String key)
{
return this.getCurrentConfig().getByte(key);
}
public double getDouble(String key, double defaultValue)
{
return this.getCurrentConfig().getDouble(key, defaultValue);
}
public Double getDouble(String key, Double defaultValue)
{
return this.getCurrentConfig().getDouble(key, defaultValue);
}
public double getDouble(String key)
{
return this.getCurrentConfig().getDouble(key);
}
public float getFloat(String key, float defaultValue)
{
return this.getCurrentConfig().getFloat(key, defaultValue);
}
public Float getFloat(String key, Float defaultValue)
{
return this.getCurrentConfig().getFloat(key, defaultValue);
}
public float getFloat(String key)
{
return this.getCurrentConfig().getFloat(key);
}
public int getInt(String key, int defaultValue)
{
return this.getCurrentConfig().getInt(key, defaultValue);
}
public int getInt(String key)
{
return this.getCurrentConfig().getInt(key);
}
public Integer getInteger(String key, Integer defaultValue)
{
return this.getCurrentConfig().getInteger(key, defaultValue);
}
public Iterator getKeys()
{
return this.getCurrentConfig().getKeys();
}
public Iterator getKeys(String prefix)
{
return this.getCurrentConfig().getKeys(prefix);
}
public List getList(String key, List defaultValue)
{
return this.getCurrentConfig().getList(key, defaultValue);
}
public List getList(String key)
{
return this.getCurrentConfig().getList(key);
}
public long getLong(String key, long defaultValue)
{
return this.getCurrentConfig().getLong(key, defaultValue);
}
public Long getLong(String key, Long defaultValue)
{
return this.getCurrentConfig().getLong(key, defaultValue);
}
public long getLong(String key)
{
return this.getCurrentConfig().getLong(key);
}
public Properties getProperties(String key)
{
return this.getCurrentConfig().getProperties(key);
}
public Object getProperty(String key)
{
return this.getCurrentConfig().getProperty(key);
}
public short getShort(String key, short defaultValue)
{
return this.getCurrentConfig().getShort(key, defaultValue);
}
public Short getShort(String key, Short defaultValue)
{
return this.getCurrentConfig().getShort(key, defaultValue);
}
public short getShort(String key)
{
return this.getCurrentConfig().getShort(key);
}
public String getString(String key, String defaultValue)
{
return this.getCurrentConfig().getString(key, defaultValue);
}
public String getString(String key)
{
return this.getCurrentConfig().getString(key);
}
public String[] getStringArray(String key)
{
return this.getCurrentConfig().getStringArray(key);
}
public boolean isEmpty()
{
return this.getCurrentConfig().isEmpty();
}
public void setProperty(String key, Object value)
{
if (configs != null)
{
this.getCurrentConfig().setProperty(key, value);
}
}
public Configuration subset(String prefix)
{
return this.getCurrentConfig().subset(prefix);
}
public Node getRoot()
{
return this.getCurrentConfig().getRoot();
}
public void setRoot(Node node)
{
if (configs != null)
{
this.getCurrentConfig().setRoot(node);
}
else
{
super.setRoot(node);
}
}
public ExpressionEngine getExpressionEngine()
{
return super.getExpressionEngine();
}
public void setExpressionEngine(ExpressionEngine expressionEngine)
{
super.setExpressionEngine(expressionEngine);
}
public void addNodes(String key, Collection nodes)
{
this.getCurrentConfig().addNodes(key, nodes);
}
public SubnodeConfiguration configurationAt(String key, boolean supportUpdates)
{
return this.getCurrentConfig().configurationAt(key, supportUpdates);
}
public SubnodeConfiguration configurationAt(String key)
{
return this.getCurrentConfig().configurationAt(key);
}
public List configurationsAt(String key)
{
return this.getCurrentConfig().configurationsAt(key);
}
public void clearTree(String key)
{
this.getCurrentConfig().clearTree(key);
}
public int getMaxIndex(String key)
{
return this.getCurrentConfig().getMaxIndex(key);
}
public Configuration interpolatedConfiguration()
{
return this.getCurrentConfig().interpolatedConfiguration();
}
/**
* Returns the configuration source, in which the specified key is defined.
* This method will determine the configuration node that is identified by
* the given key. The following constellations are possible:
* <ul>
* <li>If no node object is found for this key, <b>null</b> is returned.</li>
* <li>If the key maps to multiple nodes belonging to different
* configuration sources, a <code>IllegalArgumentException</code> is
* thrown (in this case no unique source can be determined).</li>
* <li>If exactly one node is found for the key, the (child) configuration
* object, to which the node belongs is determined and returned.</li>
* <li>For keys that have been added directly to this combined
* configuration and that do not belong to the namespaces defined by
* existing child configurations this configuration will be returned.</li>
* </ul>
*
* @param key the key of a configuration property
* @return the configuration, to which this property belongs or <b>null</b>
* if the key cannot be resolved
* @throws IllegalArgumentException if the key maps to multiple properties
* and the source cannot be determined, or if the key is <b>null</b>
*/
public Configuration getSource(String key)
{
if (key == null)
{
throw new IllegalArgumentException("Key must not be null!");
}
return getCurrentConfig().getSource(key);
}
public void addConfigurationListener(ConfigurationListener l)
{
super.addConfigurationListener(l);
Iterator iter = configs.values().iterator();
while (iter.hasNext())
{
CombinedConfiguration config = (CombinedConfiguration) iter.next();
config.addConfigurationListener(l);
}
}
public boolean removeConfigurationListener(ConfigurationListener l)
{
Iterator iter = configs.values().iterator();
while (iter.hasNext())
{
CombinedConfiguration config = (CombinedConfiguration) iter.next();
config.removeConfigurationListener(l);
}
return super.removeConfigurationListener(l);
}
public Collection getConfigurationListeners()
{
return super.getConfigurationListeners();
}
public void clearConfigurationListeners()
{
Iterator iter = configs.values().iterator();
while (iter.hasNext())
{
CombinedConfiguration config = (CombinedConfiguration) iter.next();
config.clearConfigurationListeners();
}
super.clearConfigurationListeners();
}
public void addErrorListener(ConfigurationErrorListener l)
{
Iterator iter = configs.values().iterator();
while (iter.hasNext())
{
CombinedConfiguration config = (CombinedConfiguration) iter.next();
config.addErrorListener(l);
}
super.addErrorListener(l);
}
public boolean removeErrorListener(ConfigurationErrorListener l)
{
Iterator iter = configs.values().iterator();
while (iter.hasNext())
{
CombinedConfiguration config = (CombinedConfiguration) iter.next();
config.removeErrorListener(l);
}
return super.removeErrorListener(l);
}
public void clearErrorListeners()
{
Iterator iter = configs.values().iterator();
while (iter.hasNext())
{
CombinedConfiguration config = (CombinedConfiguration) iter.next();
config.clearErrorListeners();
}
super.clearErrorListeners();
}
public Collection getErrorListeners()
{
return super.getErrorListeners();
}
/**
* Returns a copy of this object. This implementation performs a deep clone,
* i.e. all contained configurations will be cloned, too. For this to work,
* all contained configurations must be cloneable. Registered event
* listeners won't be cloned. The clone will use the same node combiner than
* the original.
*
* @return the copied object
*/
public Object clone()
{
return super.clone();
}
/**
* Invalidates the current combined configuration. This means that the next time a
* property is accessed the combined node structure must be re-constructed.
* Invalidation of a combined configuration also means that an event of type
* <code>EVENT_COMBINED_INVALIDATE</code> is fired. Note that while other
* events most times appear twice (once before and once after an update),
* this event is only fired once (after update).
*/
public void invalidate()
{
getCurrentConfig().invalidate();
}
public void invalidateAll()
{
if (configs == null)
{
return;
}
Iterator iter = configs.values().iterator();
while (iter.hasNext())
{
CombinedConfiguration config = (CombinedConfiguration) iter.next();
config.invalidate();
}
}
/*
* Don't allow resolveContainerStore to be called recursively.
* @param key The key to resolve.
* @return The value of the key.
*/
protected Object resolveContainerStore(String key)
{
if (((Boolean) recursive.get()).booleanValue())
{
return null;
}
recursive.set(Boolean.TRUE);
try
{
return super.resolveContainerStore(key);
}
finally
{
recursive.set(Boolean.FALSE);
}
}
private CombinedConfiguration getCurrentConfig()
{
String key = localSubst.replace(keyPattern);
CombinedConfiguration config;
synchronized (getNodeCombiner())
{
config = (CombinedConfiguration) configs.get(key);
if (config == null)
{
config = new CombinedConfiguration(getNodeCombiner());
if (loggerName != null)
{
Log log = LogFactory.getLog(loggerName);
if (log != null)
{
config.setLogger(log);
}
}
config.setIgnoreReloadExceptions(isIgnoreReloadExceptions());
config.setExpressionEngine(this.getExpressionEngine());
config.setDelimiterParsingDisabled(isDelimiterParsingDisabled());
config.setConversionExpressionEngine(getConversionExpressionEngine());
config.setListDelimiter(getListDelimiter());
Iterator iter = getErrorListeners().iterator();
while (iter.hasNext())
{
ConfigurationErrorListener listener = (ConfigurationErrorListener) iter.next();
config.addErrorListener(listener);
}
iter = 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);
}
}
if (getLogger().isDebugEnabled())
{
getLogger().debug("Returning config for " + 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;
}
}
}
|