All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.configuration2.DynamicCombinedConfiguration Maven / Gradle / Ivy

Go to download

Tools to assist in the reading of configuration/preferences files in various formats

The newest version!
/*
 * 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.Collections;
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 java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.apache.commons.configuration2.event.BaseEventSource;
import org.apache.commons.configuration2.event.Event;
import org.apache.commons.configuration2.event.EventListener;
import org.apache.commons.configuration2.event.EventType;
import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
import org.apache.commons.configuration2.interpol.Lookup;
import org.apache.commons.configuration2.io.ConfigurationLogger;
import org.apache.commons.configuration2.tree.ImmutableNode;
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. *

*

* This Configuration implementation uses the configured {@code Synchronizer} to guard itself against concurrent access. * If there are multiple threads accessing an instance concurrently, a fully functional {@code Synchronizer} * implementation (e.g. {@code ReadWriteSynchronizer}) has to be used to ensure consistency and to avoid exceptions. The * {@code Synchronizer} assigned to an instance is also passed to child configuration objects when they are created. *

* * @since 1.6 */ public class DynamicCombinedConfiguration extends CombinedConfiguration { /** * Internal class that identifies each Configuration. */ static class ConfigData { /** Stores a reference to the configuration. */ private final Configuration configuration; /** Stores the name under which the configuration is stored. */ private final String name; /** Stores the at string. */ private final String at; /** * Creates a new instance of {@code ConfigData} and initializes it. * * @param config the configuration * @param n the name * @param at the at position */ public ConfigData(final Configuration config, final String n, final String at) { configuration = config; name = n; this.at = at; } /** * Gets the at position of this configuration. * * @return the at position */ public String getAt() { return at; } /** * Gets the stored configuration. * * @return the configuration */ public Configuration getConfiguration() { return configuration; } /** * Gets the configuration's name. * * @return the name */ public String getName() { return name; } } /** * A simple data class holding information about the current configuration while an operation for a thread is processed. */ private static final class CurrentConfigHolder { /** Stores the current configuration of the current thread. */ private CombinedConfiguration currentConfiguration; /** * Stores the key of the configuration evaluated for the current thread at the beginning of an operation. */ private final String key; /** A counter for reentrant locks. */ private int lockCount; /** * Creates a new instance of {@code CurrentConfigHolder} and initializes it with the key for the current configuration. * * @param curKey the current key */ public CurrentConfigHolder(final String curKey) { key = curKey; } /** * Decrements the lock counter and checks whether it has reached 0. In this cause, the operation is complete, and the * lock can be released. * * @return true if the lock count reaches 0, false otherwise */ public boolean decrementLockCountAndCheckRelease() { return --lockCount == 0; } /** * Gets the current configuration. * * @return the current configuration */ public CombinedConfiguration getCurrentConfiguration() { return currentConfiguration; } /** * Gets the current key. * * @return the current key */ public String getKey() { return key; } /** * Increments the lock counter. */ public void incrementLockCount() { lockCount++; } /** * Sets the current configuration. * * @param currentConfiguration the current configuration */ public void setCurrentConfiguration(final CombinedConfiguration currentConfiguration) { this.currentConfiguration = currentConfiguration; } } /** * Stores the current configuration for each involved thread. This value is set at the beginning of an operation and * removed at the end. */ private static final ThreadLocal CURRENT_CONFIG = new ThreadLocal<>(); /** The CombinedConfigurations */ private final ConcurrentMap configs = new ConcurrentHashMap<>(); /** Stores a list with the contained configurations. */ private final List configurations = new ArrayList<>(); /** Stores a map with the named configurations. */ private final 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 final ConfigurationInterpolator localSubst; /** * Creates a new instance of {@code DynamicCombinedConfiguration} that uses a union combiner. * * @see org.apache.commons.configuration2.tree.UnionCombiner */ public DynamicCombinedConfiguration() { initLogger(new ConfigurationLogger(DynamicCombinedConfiguration.class)); localSubst = initLocalInterpolator(); } /** * Creates a new instance of {@code DynamicCombinedConfiguration} and initializes the combiner to be used. * * @param comb the node combiner (can be null, then a union combiner is used as default) */ public DynamicCombinedConfiguration(final NodeCombiner comb) { setNodeCombiner(comb); initLogger(new ConfigurationLogger(DynamicCombinedConfiguration.class)); localSubst = initLocalInterpolator(); } /** * 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} will be thrown. * With the optional {@code at} 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"}, all properties of the added * configuration will occur in this branch. * * @param config the configuration to add (must not be null) * @param name the name of this configuration (can be null) * @param at the position of this configuration in the combined tree (can be null) */ @Override public void addConfiguration(final Configuration config, final String name, final String at) { beginWrite(true); try { final ConfigData cd = new ConfigData(config, name, at); configurations.add(cd); if (name != null) { namedConfigurations.put(name, config); } // clear cache of all child configurations configs.clear(); } finally { endWrite(); } } @Override public void addEventListener(final EventType eventType, final EventListener listener) { configs.values().forEach(cc -> cc.addEventListener(eventType, listener)); super.addEventListener(eventType, listener); } @Override protected void addNodesInternal(final String key, final Collection nodes) { getCurrentConfig().addNodes(key, nodes); } @Override protected void addPropertyInternal(final String key, final Object value) { getCurrentConfig().addProperty(key, value); } /** * {@inheritDoc} This implementation ensures that the current configuration is initialized. The lock counter is * increased. */ @Override protected void beginRead(final boolean optimize) { final CurrentConfigHolder cch = ensureCurrentConfiguration(); cch.incrementLockCount(); if (!optimize && cch.getCurrentConfiguration() == null) { // delegate to beginWrite() which creates the child configuration beginWrite(false); endWrite(); } // This actually uses our own synchronizer cch.getCurrentConfiguration().beginRead(optimize); } /** * {@inheritDoc} This implementation ensures that the current configuration is initialized. If necessary, a new child * configuration instance is created. */ @Override protected void beginWrite(final boolean optimize) { final CurrentConfigHolder cch = ensureCurrentConfiguration(); cch.incrementLockCount(); super.beginWrite(optimize); if (!optimize && cch.getCurrentConfiguration() == null) { cch.setCurrentConfiguration(createChildConfiguration()); configs.put(cch.getKey(), cch.getCurrentConfiguration()); initChildConfiguration(cch.getCurrentConfiguration()); } } @Override public void clearErrorListeners() { configs.values().forEach(BaseEventSource::clearErrorListeners); super.clearErrorListeners(); } @Override public void clearEventListeners() { configs.values().forEach(CombinedConfiguration::clearEventListeners); super.clearEventListeners(); } @Override protected void clearInternal() { getCurrentConfig().clear(); } @Override protected void clearPropertyDirect(final String key) { getCurrentConfig().clearProperty(key); } @Override protected Object clearTreeInternal(final String key) { getCurrentConfig().clearTree(key); return Collections.emptyList(); } @Override public HierarchicalConfiguration configurationAt(final String key) { return getCurrentConfig().configurationAt(key); } @Override public HierarchicalConfiguration configurationAt(final String key, final boolean supportUpdates) { return getCurrentConfig().configurationAt(key, supportUpdates); } @Override public List> configurationsAt(final String key) { return getCurrentConfig().configurationsAt(key); } @Override protected boolean containsKeyInternal(final String key) { return getCurrentConfig().containsKey(key); } /** * Tests whether this configuration contains one or more matches to this value. This operation stops at first * match but may be more expensive than the containsKey method. * @since 2.11.0 */ @Override protected boolean containsValueInternal(final Object value) { return getCurrentConfig().contains(getKeys(), value); } /** * Creates a new, uninitialized child configuration. * * @return the new child configuration */ private CombinedConfiguration createChildConfiguration() { return new CombinedConfiguration(getNodeCombiner()); } /** * {@inheritDoc} This implementation clears the current configuration if necessary. */ @Override protected void endRead() { CURRENT_CONFIG.get().getCurrentConfiguration().endRead(); releaseLock(); } /** * {@inheritDoc} This implementation clears the current configuration if necessary. */ @Override protected void endWrite() { super.endWrite(); releaseLock(); } /** * Checks whether the current configuration is set. If not, a {@code CurrentConfigHolder} is now created and * initialized, and associated with the current thread. The member for the current configuration is undefined if for the * current key no configuration exists yet. * * @return the {@code CurrentConfigHolder} instance for the current thread */ private CurrentConfigHolder ensureCurrentConfiguration() { CurrentConfigHolder cch = CURRENT_CONFIG.get(); if (cch == null) { final String key = String.valueOf(localSubst.interpolate(keyPattern)); cch = new CurrentConfigHolder(key); cch.setCurrentConfiguration(configs.get(key)); CURRENT_CONFIG.set(cch); } return cch; } @Override public BigDecimal getBigDecimal(final String key) { return getCurrentConfig().getBigDecimal(key); } @Override public BigDecimal getBigDecimal(final String key, final BigDecimal defaultValue) { return getCurrentConfig().getBigDecimal(key, defaultValue); } @Override public BigInteger getBigInteger(final String key) { return getCurrentConfig().getBigInteger(key); } @Override public BigInteger getBigInteger(final String key, final BigInteger defaultValue) { return getCurrentConfig().getBigInteger(key, defaultValue); } @Override public boolean getBoolean(final String key) { return getCurrentConfig().getBoolean(key); } @Override public boolean getBoolean(final String key, final boolean defaultValue) { return getCurrentConfig().getBoolean(key, defaultValue); } @Override public Boolean getBoolean(final String key, final Boolean defaultValue) { return getCurrentConfig().getBoolean(key, defaultValue); } @Override public byte getByte(final String key) { return getCurrentConfig().getByte(key); } @Override public byte getByte(final String key, final byte defaultValue) { return getCurrentConfig().getByte(key, defaultValue); } @Override public Byte getByte(final String key, final Byte defaultValue) { return getCurrentConfig().getByte(key, defaultValue); } /** * Gets 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 */ @Override public Configuration getConfiguration(final int index) { beginRead(false); try { final ConfigData cd = configurations.get(index); return cd.getConfiguration(); } finally { endRead(); } } /** * Gets the configuration with the given name. This can be null if no such configuration exists. * * @param name the name of the configuration * @return the configuration with this name */ @Override public Configuration getConfiguration(final String name) { beginRead(false); try { return namedConfigurations.get(name); } finally { endRead(); } } /** * Gets 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 null) */ @Override public Set getConfigurationNames() { beginRead(false); try { return namedConfigurations.keySet(); } finally { endRead(); } } /** * Gets the current configuration. This configuration was initialized at the beginning of an operation and stored in * a thread-local variable. Some methods of this class call this method directly without requesting a lock before. To * deal with this, we always request an additional read lock. * * @return the current configuration */ private CombinedConfiguration getCurrentConfig() { CombinedConfiguration config; String key; beginRead(false); try { config = CURRENT_CONFIG.get().getCurrentConfiguration(); key = CURRENT_CONFIG.get().getKey(); } finally { endRead(); } if (getLogger().isDebugEnabled()) { getLogger().debug("Returning config for " + key + ": " + config); } return config; } @Override public double getDouble(final String key) { return getCurrentConfig().getDouble(key); } @Override public double getDouble(final String key, final double defaultValue) { return getCurrentConfig().getDouble(key, defaultValue); } @Override public Double getDouble(final String key, final Double defaultValue) { return getCurrentConfig().getDouble(key, defaultValue); } @Override public float getFloat(final String key) { return getCurrentConfig().getFloat(key); } @Override public float getFloat(final String key, final float defaultValue) { return getCurrentConfig().getFloat(key, defaultValue); } @Override public Float getFloat(final String key, final Float defaultValue) { return getCurrentConfig().getFloat(key, defaultValue); } @Override public int getInt(final String key) { return getCurrentConfig().getInt(key); } @Override public int getInt(final String key, final int defaultValue) { return getCurrentConfig().getInt(key, defaultValue); } @Override public Integer getInteger(final String key, final Integer defaultValue) { return getCurrentConfig().getInteger(key, defaultValue); } public String getKeyPattern() { return this.keyPattern; } @Override protected Iterator getKeysInternal() { return getCurrentConfig().getKeys(); } @Override protected Iterator getKeysInternal(final String prefix) { return getCurrentConfig().getKeys(prefix); } @Override public List getList(final String key) { return getCurrentConfig().getList(key); } @Override public List getList(final String key, final List defaultValue) { return getCurrentConfig().getList(key, defaultValue); } @Override public long getLong(final String key) { return getCurrentConfig().getLong(key); } @Override public long getLong(final String key, final long defaultValue) { return getCurrentConfig().getLong(key, defaultValue); } @Override public Long getLong(final String key, final Long defaultValue) { return getCurrentConfig().getLong(key, defaultValue); } @Override protected int getMaxIndexInternal(final String key) { return getCurrentConfig().getMaxIndex(key); } /** * Gets the node combiner that is used for creating the combined node structure. * * @return the node combiner */ @Override public NodeCombiner getNodeCombiner() { return nodeCombiner; } /** * Gets the number of configurations that are contained in this combined configuration. * * @return the number of contained configurations */ @Override public int getNumberOfConfigurations() { beginRead(false); try { return configurations.size(); } finally { endRead(); } } @Override public Properties getProperties(final String key) { return getCurrentConfig().getProperties(key); } @Override protected Object getPropertyInternal(final String key) { return getCurrentConfig().getProperty(key); } @Override public short getShort(final String key) { return getCurrentConfig().getShort(key); } @Override public short getShort(final String key, final short defaultValue) { return getCurrentConfig().getShort(key, defaultValue); } @Override public Short getShort(final String key, final Short defaultValue) { return getCurrentConfig().getShort(key, defaultValue); } /** * Gets 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: *
    *
  • If no node object is found for this key, null is returned.
  • *
  • If the key maps to multiple nodes belonging to different configuration sources, a * {@code IllegalArgumentException} is thrown (in this case no unique source can be determined).
  • *
  • If exactly one node is found for the key, the (child) configuration object, to which the node belongs is * determined and returned.
  • *
  • 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.
  • *
* * @param key the key of a configuration property * @return the configuration, to which this property belongs or null 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 null */ @Override public Configuration getSource(final String key) { if (key == null) { throw new IllegalArgumentException("Key must not be null!"); } return getCurrentConfig().getSource(key); } @Override public String getString(final String key) { return getCurrentConfig().getString(key); } @Override public String getString(final String key, final String defaultValue) { return getCurrentConfig().getString(key, defaultValue); } @Override public String[] getStringArray(final String key) { return getCurrentConfig().getStringArray(key); } /** * Initializes a newly created child configuration. This method copies a bunch of settings from this instance to the * child configuration. * * @param config the child configuration to be initialized */ private void initChildConfiguration(final CombinedConfiguration config) { if (loggerName != null) { config.setLogger(new ConfigurationLogger(loggerName)); } config.setExpressionEngine(getExpressionEngine()); config.setConversionExpressionEngine(getConversionExpressionEngine()); config.setListDelimiterHandler(getListDelimiterHandler()); copyEventListeners(config); configurations.forEach(data -> config.addConfiguration(data.getConfiguration(), data.getName(), data.getAt())); config.setSynchronizer(getSynchronizer()); } /** * Creates a {@code ConfigurationInterpolator} instance for performing local variable substitutions. This implementation * returns an object which shares the prefix lookups from this configuration's {@code ConfigurationInterpolator}, but * does not define any other lookups. * * @return the {@code ConfigurationInterpolator} */ private ConfigurationInterpolator initLocalInterpolator() { return new ConfigurationInterpolator() { @Override protected Lookup fetchLookupForPrefix(final String prefix) { return nullSafeLookup(getInterpolator().getLookups().get(prefix)); } }; } @Override public Configuration interpolatedConfiguration() { return getCurrentConfig().interpolatedConfiguration(); } /** * 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} 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). */ @Override public void invalidate() { getCurrentConfig().invalidate(); } public void invalidateAll() { configs.values().forEach(CombinedConfiguration::invalidate); } @Override protected boolean isEmptyInternal() { return getCurrentConfig().isEmpty(); } /** * Decrements the lock count of the current configuration holder. If it reaches 0, the current configuration is removed. * (It is then reevaluated when the next operation starts.) */ private void releaseLock() { final CurrentConfigHolder cch = CURRENT_CONFIG.get(); assert cch != null : "No current configuration!"; if (cch.decrementLockCountAndCheckRelease()) { CURRENT_CONFIG.remove(); } } /** * 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 */ @Override public boolean removeConfiguration(final Configuration config) { beginWrite(false); try { for (int index = 0; index < getNumberOfConfigurations(); index++) { if (configurations.get(index).getConfiguration() == config) { removeConfigurationAt(index); return true; } } return false; } finally { endWrite(); } } /** * Removes the configuration with the specified name. * * @param name the name of the configuration to be removed * @return the removed configuration (null if this configuration was not found) */ @Override public Configuration removeConfiguration(final String name) { final Configuration conf = getConfiguration(name); if (conf != null) { removeConfiguration(conf); } return conf; } /** * Removes the configuration at the specified index. * * @param index the index * @return the removed configuration */ @Override public Configuration removeConfigurationAt(final int index) { beginWrite(false); try { final ConfigData cd = configurations.remove(index); if (cd.getName() != null) { namedConfigurations.remove(cd.getName()); } return cd.getConfiguration(); } finally { endWrite(); } } @Override public boolean removeEventListener(final EventType eventType, final EventListener listener) { configs.values().forEach(cc -> cc.removeEventListener(eventType, listener)); return super.removeEventListener(eventType, listener); } public void setKeyPattern(final String pattern) { this.keyPattern = pattern; } /** * Sets the name of the Logger to use on each CombinedConfiguration. * * @param name The Logger name. */ public void setLoggerName(final String name) { this.loggerName = name; } /** * Sets the node combiner. This object will be used when the combined node structure is to be constructed. It must not * be null, otherwise an {@code IllegalArgumentException} 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 */ @Override public void setNodeCombiner(final NodeCombiner nodeCombiner) { if (nodeCombiner == null) { throw new IllegalArgumentException("Node combiner must not be null!"); } this.nodeCombiner = nodeCombiner; invalidateAll(); } @Override protected void setPropertyInternal(final String key, final Object value) { getCurrentConfig().setProperty(key, value); } @Override protected int sizeInternal() { return getCurrentConfig().size(); } @Override public Configuration subset(final String prefix) { return getCurrentConfig().subset(prefix); } }