    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.configuration2;
    018
    019import java.math.BigDecimal;
    020import java.math.BigInteger;
    021import java.util.ArrayList;
    022import java.util.Collection;
    023import java.util.Collections;
    024import java.util.HashMap;
    025import java.util.Iterator;
    026import java.util.List;
    027import java.util.Map;
    028import java.util.Properties;
    029import java.util.Set;
    030import java.util.concurrent.ConcurrentHashMap;
    031import java.util.concurrent.ConcurrentMap;
    032
    033import org.apache.commons.configuration2.event.Event;
    034import org.apache.commons.configuration2.event.EventListener;
    035import org.apache.commons.configuration2.event.EventType;
    036import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
    037import org.apache.commons.configuration2.interpol.Lookup;
    038import org.apache.commons.configuration2.io.ConfigurationLogger;
    039import org.apache.commons.configuration2.tree.ExpressionEngine;
    040import org.apache.commons.configuration2.tree.ImmutableNode;
    041import org.apache.commons.configuration2.tree.NodeCombiner;
    042
    043/**
    044 * <p>
    045 * DynamicCombinedConfiguration allows a set of CombinedConfigurations to be
    046 * used.
    047 * </p>
    048 * <p>
    049 * Each CombinedConfiguration is referenced by a key that is dynamically
    050 * constructed from a key pattern on each call. The key pattern will be resolved
    051 * using the configured ConfigurationInterpolator.
    052 * </p>
    053 * <p>
    054 * This Configuration implementation uses the configured {@code Synchronizer} to
    055 * guard itself against concurrent access. If there are multiple threads
    056 * accessing an instance concurrently, a fully functional {@code Synchronizer}
    057 * implementation (e.g. {@code ReadWriteSynchronizer}) has to be used to ensure
    058 * consistency and to avoid exceptions. The {@code Synchronizer} assigned to an
    059 * instance is also passed to child configuration objects when they are created.
    060 * </p>
    061 *
    062 * @since 1.6
    063 * @version $Id: DynamicCombinedConfiguration.java 1842194 2018-09-27 22:24:23Z ggregory $
    064 */
    065public class DynamicCombinedConfiguration extends CombinedConfiguration
    066{
    067    /**
    068     * Stores the current configuration for each involved thread. This value is
    069     * set at the beginning of an operation and removed at the end.
    070     */
    071    private static final ThreadLocal<CurrentConfigHolder> CURRENT_CONFIG =
    072            new ThreadLocal<>();
    073
    074    /** The CombinedConfigurations */
    075    private final ConcurrentMap<String, CombinedConfiguration> configs =
    076            new ConcurrentHashMap<>();
    077
    078    /** Stores a list with the contained configurations. */
    079    private final List<ConfigData> configurations = new ArrayList<>();
    080
    081    /** Stores a map with the named configurations. */
    082    private final Map<String, Configuration> namedConfigurations =
    083            new HashMap<>();
    084
    085    /** The key pattern for the CombinedConfiguration map */
    086    private String keyPattern;
    087
    088    /** Stores the combiner. */
    089    private NodeCombiner nodeCombiner;
    090
    091    /** The name of the logger to use for each CombinedConfiguration */
    092    private String loggerName = DynamicCombinedConfiguration.class.getName();
    093
    094    /** The object for handling variable substitution in key patterns. */
    095    private final ConfigurationInterpolator localSubst;
    096
    097    /**
    098     * Creates a new instance of {@code DynamicCombinedConfiguration} and
    099     * initializes the combiner to be used.
    100     *
    101     * @param comb the node combiner (can be <b>null</b>, then a union combiner
    102     * is used as default)
    103     */
    104    public DynamicCombinedConfiguration(final NodeCombiner comb)
    105    {
    106        super();
    107        setNodeCombiner(comb);
    108        initLogger(new ConfigurationLogger(DynamicCombinedConfiguration.class));
    109        localSubst = initLocalInterpolator();
    110    }
    111
    112    /**
    113     * Creates a new instance of {@code DynamicCombinedConfiguration} that uses
    114     * a union combiner.
    115     *
    116     * @see org.apache.commons.configuration2.tree.UnionCombiner
    117     */
    118    public DynamicCombinedConfiguration()
    119    {
    120        super();
    121        initLogger(new ConfigurationLogger(DynamicCombinedConfiguration.class));
    122        localSubst = initLocalInterpolator();
    123    }
    124
    125    public void setKeyPattern(final String pattern)
    126    {
    127        this.keyPattern = pattern;
    128    }
    129
    130    public String getKeyPattern()
    131    {
    132        return this.keyPattern;
    133    }
    134
    135    /**
    136     * Set the name of the Logger to use on each CombinedConfiguration.
    137     * @param name The Logger name.
    138     */
    139    public void setLoggerName(final String name)
    140    {
    141        this.loggerName = name;
    142    }
    143
    144    /**
    145     * Returns the node combiner that is used for creating the combined node
    146     * structure.
    147     *
    148     * @return the node combiner
    149     */
    150    @Override
    151    public NodeCombiner getNodeCombiner()
    152    {
    153        return nodeCombiner;
    154    }
    155
    156    /**
    157     * Sets the node combiner. This object will be used when the combined node
    158     * structure is to be constructed. It must not be <b>null</b>, otherwise an
    159     * {@code IllegalArgumentException} exception is thrown. Changing the
    160     * node combiner causes an invalidation of this combined configuration, so
    161     * that the new combiner immediately takes effect.
    162     *
    163     * @param nodeCombiner the node combiner
    164     */
    165    @Override
    166    public void setNodeCombiner(final NodeCombiner nodeCombiner)
    167    {
    168        if (nodeCombiner == null)
    169        {
    170            throw new IllegalArgumentException(
    171                    "Node combiner must not be null!");
    172        }
    173        this.nodeCombiner = nodeCombiner;
    174        invalidateAll();
    175    }
    176    /**
    177     * Adds a new configuration to this combined configuration. It is possible
    178     * (but not mandatory) to give the new configuration a name. This name must
    179     * be unique, otherwise a {@code ConfigurationRuntimeException} will
    180     * be thrown. With the optional {@code at} argument you can specify
    181     * where in the resulting node structure the content of the added
    182     * configuration should appear. This is a string that uses dots as property
    183     * delimiters (independent on the current expression engine). For instance
    184     * if you pass in the string {@code "database.tables"},
    185     * all properties of the added configuration will occur in this branch.
    186     *
    187     * @param config the configuration to add (must not be <b>null</b>)
    188     * @param name the name of this configuration (can be <b>null</b>)
    189     * @param at the position of this configuration in the combined tree (can be
    190     * <b>null</b>)
    191     */
    192    @Override
    193    public void addConfiguration(final Configuration config, final String name,
    194            final String at)
    195    {
    196        beginWrite(true);
    197        try
    198        {
    199            final ConfigData cd = new ConfigData(config, name, at);
    200            configurations.add(cd);
    201            if (name != null)
    202            {
    203                namedConfigurations.put(name, config);
    204            }
    205
    206            // clear cache of all child configurations
    207            configs.clear();
    208        }
    209        finally
    210        {
    211            endWrite();
    212        }
    213    }
    214       /**
    215     * Returns the number of configurations that are contained in this combined
    216     * configuration.
    217     *
    218     * @return the number of contained configurations
    219     */
    220    @Override
    221    public int getNumberOfConfigurations()
    222    {
    223        beginRead(false);
    224        try
    225        {
    226            return configurations.size();
    227        }
    228        finally
    229        {
    230            endRead();
    231        }
    232    }
    233
    234    /**
    235     * Returns the configuration at the specified index. The contained
    236     * configurations are numbered in the order they were added to this combined
    237     * configuration. The index of the first configuration is 0.
    238     *
    239     * @param index the index
    240     * @return the configuration at this index
    241     */
    242    @Override
    243    public Configuration getConfiguration(final int index)
    244    {
    245        beginRead(false);
    246        try
    247        {
    248            final ConfigData cd = configurations.get(index);
    249            return cd.getConfiguration();
    250        }
    251        finally
    252        {
    253            endRead();
    254        }
    255    }
    256
    257    /**
    258     * Returns the configuration with the given name. This can be <b>null</b>
    259     * if no such configuration exists.
    260     *
    261     * @param name the name of the configuration
    262     * @return the configuration with this name
    263     */
    264    @Override
    265    public Configuration getConfiguration(final String name)
    266    {
    267        beginRead(false);
    268        try
    269        {
    270            return namedConfigurations.get(name);
    271        }
    272        finally
    273        {
    274            endRead();
    275        }
    276    }
    277
    278    /**
    279     * Returns a set with the names of all configurations contained in this
    280     * combined configuration. Of course here are only these configurations
    281     * listed, for which a name was specified when they were added.
    282     *
    283     * @return a set with the names of the contained configurations (never
    284     * <b>null</b>)
    285     */
    286    @Override
    287    public Set<String> getConfigurationNames()
    288    {
    289        beginRead(false);
    290        try
    291        {
    292            return namedConfigurations.keySet();
    293        }
    294        finally
    295        {
    296            endRead();
    297        }
    298    }
    299
    300    /**
    301     * Removes the configuration with the specified name.
    302     *
    303     * @param name the name of the configuration to be removed
    304     * @return the removed configuration (<b>null</b> if this configuration
    305     * was not found)
    306     */
    307    @Override
    308    public Configuration removeConfiguration(final String name)
    309    {
    310        final Configuration conf = getConfiguration(name);
    311        if (conf != null)
    312        {
    313            removeConfiguration(conf);
    314        }
    315        return conf;
    316    }
    317
    318    /**
    319     * Removes the specified configuration from this combined configuration.
    320     *
    321     * @param config the configuration to be removed
    322     * @return a flag whether this configuration was found and could be removed
    323     */
    324    @Override
    325    public boolean removeConfiguration(final Configuration config)
    326    {
    327        beginWrite(false);
    328        try
    329        {
    330            for (int index = 0; index < getNumberOfConfigurations(); index++)
    331            {
    332                if (configurations.get(index).getConfiguration() == config)
    333                {
    334                    removeConfigurationAt(index);
    335                    return true;
    336                }
    337            }
    338
    339            return false;
    340        }
    341        finally
    342        {
    343            endWrite();
    344        }
    345    }
    346
    347    /**
    348     * Removes the configuration at the specified index.
    349     *
    350     * @param index the index
    351     * @return the removed configuration
    352     */
    353    @Override
    354    public Configuration removeConfigurationAt(final int index)
    355    {
    356        beginWrite(false);
    357        try
    358        {
    359            final ConfigData cd = configurations.remove(index);
    360            if (cd.getName() != null)
    361            {
    362                namedConfigurations.remove(cd.getName());
    363            }
    364            return cd.getConfiguration();
    365        }
    366        finally
    367        {
    368            endWrite();
    369        }
    370    }
    371
    372    @Override
    373    protected void addPropertyInternal(final String key, final Object value)
    374    {
    375        this.getCurrentConfig().addProperty(key, value);
    376    }
    377
    378    @Override
    379    protected void clearInternal()
    380    {
    381        if (configs != null)
    382        {
    383            this.getCurrentConfig().clear();
    384        }
    385    }
    386
    387    @Override
    388    protected void clearPropertyDirect(final String key)
    389    {
    390        this.getCurrentConfig().clearProperty(key);
    391    }
    392
    393    @Override
    394    protected boolean containsKeyInternal(final String key)
    395    {
    396        return this.getCurrentConfig().containsKey(key);
    397    }
    398
    399    @Override
    400    public BigDecimal getBigDecimal(final String key, final BigDecimal defaultValue)
    401    {
    402        return this.getCurrentConfig().getBigDecimal(key, defaultValue);
    403    }
    404
    405    @Override
    406    public BigDecimal getBigDecimal(final String key)
    407    {
    408        return this.getCurrentConfig().getBigDecimal(key);
    409    }
    410
    411    @Override
    412    public BigInteger getBigInteger(final String key, final BigInteger defaultValue)
    413    {
    414        return this.getCurrentConfig().getBigInteger(key, defaultValue);
    415    }
    416
    417    @Override
    418    public BigInteger getBigInteger(final String key)
    419    {
    420        return this.getCurrentConfig().getBigInteger(key);
    421    }
    422
    423    @Override
    424    public boolean getBoolean(final String key, final boolean defaultValue)
    425    {
    426        return this.getCurrentConfig().getBoolean(key, defaultValue);
    427    }
    428
    429    @Override
    430    public Boolean getBoolean(final String key, final Boolean defaultValue)
    431    {
    432        return this.getCurrentConfig().getBoolean(key, defaultValue);
    433    }
    434
    435    @Override
    436    public boolean getBoolean(final String key)
    437    {
    438        return this.getCurrentConfig().getBoolean(key);
    439    }
    440
    441    @Override
    442    public byte getByte(final String key, final byte defaultValue)
    443    {
    444        return this.getCurrentConfig().getByte(key, defaultValue);
    445    }
    446
    447    @Override
    448    public Byte getByte(final String key, final Byte defaultValue)
    449    {
    450        return this.getCurrentConfig().getByte(key, defaultValue);
    451    }
    452
    453    @Override
    454    public byte getByte(final String key)
    455    {
    456        return this.getCurrentConfig().getByte(key);
    457    }
    458
    459    @Override
    460    public double getDouble(final String key, final double defaultValue)
    461    {
    462        return this.getCurrentConfig().getDouble(key, defaultValue);
    463    }
    464
    465    @Override
    466    public Double getDouble(final String key, final Double defaultValue)
    467    {
    468        return this.getCurrentConfig().getDouble(key, defaultValue);
    469    }
    470
    471    @Override
    472    public double getDouble(final String key)
    473    {
    474        return this.getCurrentConfig().getDouble(key);
    475    }
    476
    477    @Override
    478    public float getFloat(final String key, final float defaultValue)
    479    {
    480        return this.getCurrentConfig().getFloat(key, defaultValue);
    481    }
    482
    483    @Override
    484    public Float getFloat(final String key, final Float defaultValue)
    485    {
    486        return this.getCurrentConfig().getFloat(key, defaultValue);
    487    }
    488
    489    @Override
    490    public float getFloat(final String key)
    491    {
    492        return this.getCurrentConfig().getFloat(key);
    493    }
    494
    495    @Override
    496    public int getInt(final String key, final int defaultValue)
    497    {
    498        return this.getCurrentConfig().getInt(key, defaultValue);
    499    }
    500
    501    @Override
    502    public int getInt(final String key)
    503    {
    504        return this.getCurrentConfig().getInt(key);
    505    }
    506
    507    @Override
    508    public Integer getInteger(final String key, final Integer defaultValue)
    509    {
    510        return this.getCurrentConfig().getInteger(key, defaultValue);
    511    }
    512
    513    @Override
    514    protected Iterator<String> getKeysInternal()
    515    {
    516        return this.getCurrentConfig().getKeys();
    517    }
    518
    519    @Override
    520    protected Iterator<String> getKeysInternal(final String prefix)
    521    {
    522        return this.getCurrentConfig().getKeys(prefix);
    523    }
    524
    525    @Override
    526    public List<Object> getList(final String key, final List<?> defaultValue)
    527    {
    528        return this.getCurrentConfig().getList(key, defaultValue);
    529    }
    530
    531    @Override
    532    public List<Object> getList(final String key)
    533    {
    534        return this.getCurrentConfig().getList(key);
    535    }
    536
    537    @Override
    538    public long getLong(final String key, final long defaultValue)
    539    {
    540        return this.getCurrentConfig().getLong(key, defaultValue);
    541    }
    542
    543    @Override
    544    public Long getLong(final String key, final Long defaultValue)
    545    {
    546        return this.getCurrentConfig().getLong(key, defaultValue);
    547    }
    548
    549    @Override
    550    public long getLong(final String key)
    551    {
    552        return this.getCurrentConfig().getLong(key);
    553    }
    554
    555    @Override
    556    public Properties getProperties(final String key)
    557    {
    558        return this.getCurrentConfig().getProperties(key);
    559    }
    560
    561    @Override
    562    protected Object getPropertyInternal(final String key)
    563    {
    564        return this.getCurrentConfig().getProperty(key);
    565    }
    566
    567    @Override
    568    public short getShort(final String key, final short defaultValue)
    569    {
    570        return this.getCurrentConfig().getShort(key, defaultValue);
    571    }
    572
    573    @Override
    574    public Short getShort(final String key, final Short defaultValue)
    575    {
    576        return this.getCurrentConfig().getShort(key, defaultValue);
    577    }
    578
    579    @Override
    580    public short getShort(final String key)
    581    {
    582        return this.getCurrentConfig().getShort(key);
    583    }
    584
    585    @Override
    586    public String getString(final String key, final String defaultValue)
    587    {
    588        return this.getCurrentConfig().getString(key, defaultValue);
    589    }
    590
    591    @Override
    592    public String getString(final String key)
    593    {
    594        return this.getCurrentConfig().getString(key);
    595    }
    596
    597    @Override
    598    public String[] getStringArray(final String key)
    599    {
    600        return this.getCurrentConfig().getStringArray(key);
    601    }
    602
    603    @Override
    604    protected boolean isEmptyInternal()
    605    {
    606        return this.getCurrentConfig().isEmpty();
    607    }
    608
    609    @Override
    610    protected int sizeInternal()
    611    {
    612        return this.getCurrentConfig().size();
    613    }
    614
    615    @Override
    616    protected void setPropertyInternal(final String key, final Object value)
    617    {
    618        if (configs != null)
    619        {
    620            this.getCurrentConfig().setProperty(key, value);
    621        }
    622    }
    623
    624    @Override
    625    public Configuration subset(final String prefix)
    626    {
    627        return this.getCurrentConfig().subset(prefix);
    628    }
    629
    630    @Override
    631    public ExpressionEngine getExpressionEngine()
    632    {
    633        return super.getExpressionEngine();
    634    }
    635
    636    @Override
    637    public void setExpressionEngine(final ExpressionEngine expressionEngine)
    638    {
    639        super.setExpressionEngine(expressionEngine);
    640    }
    641
    642    @Override
    643    protected void addNodesInternal(final String key, final Collection<? extends ImmutableNode> nodes)
    644    {
    645        this.getCurrentConfig().addNodes(key, nodes);
    646    }
    647
    648    @Override
    649    public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key, final boolean supportUpdates)
    650    {
    651        return this.getCurrentConfig().configurationAt(key, supportUpdates);
    652    }
    653
    654    @Override
    655    public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key)
    656    {
    657        return this.getCurrentConfig().configurationAt(key);
    658    }
    659
    660    @Override
    661    public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(final String key)
    662    {
    663        return this.getCurrentConfig().configurationsAt(key);
    664    }
    665
    666    @Override
    667    protected Object clearTreeInternal(final String key)
    668    {
    669        this.getCurrentConfig().clearTree(key);
    670        return Collections.emptyList();
    671    }
    672
    673    @Override
    674    protected int getMaxIndexInternal(final String key)
    675    {
    676        return this.getCurrentConfig().getMaxIndex(key);
    677    }
    678
    679    @Override
    680    public Configuration interpolatedConfiguration()
    681    {
    682        return this.getCurrentConfig().interpolatedConfiguration();
    683    }
    684
    685
    686    /**
    687     * Returns the configuration source, in which the specified key is defined.
    688     * This method will determine the configuration node that is identified by
    689     * the given key. The following constellations are possible:
    690     * <ul>
    691     * <li>If no node object is found for this key, <b>null</b> is returned.</li>
    692     * <li>If the key maps to multiple nodes belonging to different
    693     * configuration sources, a {@code IllegalArgumentException} is
    694     * thrown (in this case no unique source can be determined).</li>
    695     * <li>If exactly one node is found for the key, the (child) configuration
    696     * object, to which the node belongs is determined and returned.</li>
    697     * <li>For keys that have been added directly to this combined
    698     * configuration and that do not belong to the namespaces defined by
    699     * existing child configurations this configuration will be returned.</li>
    700     * </ul>
    701     *
    702     * @param key the key of a configuration property
    703     * @return the configuration, to which this property belongs or <b>null</b>
    704     * if the key cannot be resolved
    705     * @throws IllegalArgumentException if the key maps to multiple properties
    706     * and the source cannot be determined, or if the key is <b>null</b>
    707     */
    708    @Override
    709    public Configuration getSource(final String key)
    710    {
    711        if (key == null)
    712        {
    713            throw new IllegalArgumentException("Key must not be null!");
    714        }
    715        return getCurrentConfig().getSource(key);
    716    }
    717
    718    @Override
    719    public void clearEventListeners()
    720    {
    721        for (final CombinedConfiguration cc : configs.values())
    722        {
    723            cc.clearEventListeners();
    724        }
    725        super.clearEventListeners();
    726    }
    727
    728    @Override
    729    public <T extends Event> void addEventListener(final EventType<T> eventType,
    730            final EventListener<? super T> listener)
    731    {
    732        for (final CombinedConfiguration cc : configs.values())
    733        {
    734            cc.addEventListener(eventType, listener);
    735        }
    736        super.addEventListener(eventType, listener);
    737    }
    738
    739    @Override
    740    public <T extends Event> boolean removeEventListener(
    741            final EventType<T> eventType, final EventListener<? super T> listener)
    742    {
    743        for (final CombinedConfiguration cc : configs.values())
    744        {
    745            cc.removeEventListener(eventType, listener);
    746        }
    747        return super.removeEventListener(eventType, listener);
    748    }
    749
    750    @Override
    751    public void clearErrorListeners()
    752    {
    753        for (final CombinedConfiguration cc : configs.values())
    754        {
    755            cc.clearErrorListeners();
    756        }
    757        super.clearErrorListeners();
    758    }
    759
    760    /**
    761     * Returns a copy of this object. This implementation performs a deep clone,
    762     * i.e. all contained configurations will be cloned, too. For this to work,
    763     * all contained configurations must be cloneable. Registered event
    764     * listeners won't be cloned. The clone will use the same node combiner than
    765     * the original.
    766     *
    767     * @return the copied object
    768     */
    769    @Override
    770    public Object clone()
    771    {
    772        return super.clone();
    773    }
    774
    775    /**
    776     * Invalidates the current combined configuration. This means that the next time a
    777     * property is accessed the combined node structure must be re-constructed.
    778     * Invalidation of a combined configuration also means that an event of type
    779     * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other
    780     * events most times appear twice (once before and once after an update),
    781     * this event is only fired once (after update).
    782     */
    783    @Override
    784    public void invalidate()
    785    {
    786        getCurrentConfig().invalidate();
    787    }
    788
    789    public void invalidateAll()
    790    {
    791        for (final CombinedConfiguration cc : configs.values())
    792        {
    793            cc.invalidate();
    794        }
    795    }
    796
    797    /**
    798     * {@inheritDoc} This implementation ensures that the current configuration
    799     * is initialized. The lock counter is increased.
    800     */
    801    @Override
    802    protected void beginRead(final boolean optimize)
    803    {
    804        final CurrentConfigHolder cch = ensureCurrentConfiguration();
    805        cch.incrementLockCount();
    806        if (!optimize && cch.getCurrentConfiguration() == null)
    807        {
    808            // delegate to beginWrite() which creates the child configuration
    809            beginWrite(false);
    810            endWrite();
    811        }
    812
    813        // This actually uses our own synchronizer
    814        cch.getCurrentConfiguration().beginRead(optimize);
    815    }
    816
    817    /**
    818     * {@inheritDoc} This implementation ensures that the current configuration
    819     * is initialized. If necessary, a new child configuration instance is
    820     * created.
    821     */
    822    @Override
    823    protected void beginWrite(final boolean optimize)
    824    {
    825        final CurrentConfigHolder cch = ensureCurrentConfiguration();
    826        cch.incrementLockCount();
    827
    828        super.beginWrite(optimize);
    829        if (!optimize && cch.getCurrentConfiguration() == null)
    830        {
    831            cch.setCurrentConfiguration(createChildConfiguration());
    832            configs.put(cch.getKey(), cch.getCurrentConfiguration());
    833            initChildConfiguration(cch.getCurrentConfiguration());
    834        }
    835    }
    836
    837    /**
    838     * {@inheritDoc} This implementation clears the current configuration if
    839     * necessary.
    840     */
    841    @Override
    842    protected void endRead()
    843    {
    844        CURRENT_CONFIG.get().getCurrentConfiguration().endRead();
    845        releaseLock();
    846    }
    847
    848    /**
    849     * {@inheritDoc} This implementation clears the current configuration if
    850     * necessary.
    851     */
    852    @Override
    853    protected void endWrite()
    854    {
    855        super.endWrite();
    856        releaseLock();
    857    }
    858
    859    /**
    860     * Decrements the lock count of the current configuration holder. If it
    861     * reaches 0, the current configuration is removed. (It is then reevaluated
    862     * when the next operation starts.)
    863     */
    864    private void releaseLock()
    865    {
    866        final CurrentConfigHolder cch = CURRENT_CONFIG.get();
    867        assert cch != null : "No current configuration!";
    868        if (cch.decrementLockCountAndCheckRelease())
    869        {
    870            CURRENT_CONFIG.remove();
    871        }
    872    }
    873
    874    /**
    875     * Returns the current configuration. This configuration was initialized at
    876     * the beginning of an operation and stored in a thread-local variable. Some
    877     * methods of this class call this method directly without requesting a lock
    878     * before. To deal with this, we always request an additional read lock.
    879     *
    880     * @return the current configuration
    881     */
    882    private CombinedConfiguration getCurrentConfig()
    883    {
    884        CombinedConfiguration config;
    885        String key;
    886        beginRead(false);
    887        try
    888        {
    889            config = CURRENT_CONFIG.get().getCurrentConfiguration();
    890            key = CURRENT_CONFIG.get().getKey();
    891        }
    892        finally
    893        {
    894            endRead();
    895        }
    896
    897        if (getLogger().isDebugEnabled())
    898        {
    899            getLogger().debug("Returning config for " + key + ": " + config);
    900        }
    901        return config;
    902    }
    903
    904    /**
    905     * Creates a new, uninitialized child configuration.
    906     *
    907     * @return the new child configuration
    908     */
    909    private CombinedConfiguration createChildConfiguration()
    910    {
    911        return new CombinedConfiguration(getNodeCombiner());
    912    }
    913
    914    /**
    915     * Initializes a newly created child configuration. This method copies a
    916     * bunch of settings from this instance to the child configuration.
    917     *
    918     * @param config the child configuration to be initialized
    919     */
    920    private void initChildConfiguration(final CombinedConfiguration config)
    921    {
    922        if (loggerName != null)
    923        {
    924            config.setLogger(new ConfigurationLogger(loggerName));
    925        }
    926        config.setExpressionEngine(this.getExpressionEngine());
    927        config.setConversionExpressionEngine(getConversionExpressionEngine());
    928        config.setListDelimiterHandler(getListDelimiterHandler());
    929        copyEventListeners(config);
    930        for (final ConfigData data : configurations)
    931        {
    932            config.addConfiguration(data.getConfiguration(), data.getName(),
    933                    data.getAt());
    934        }
    935        config.setSynchronizer(getSynchronizer());
    936    }
    937
    938    /**
    939     * Creates a {@code ConfigurationInterpolator} instance for performing local
    940     * variable substitutions. This implementation returns an object which
    941     * shares the prefix lookups from this configuration's
    942     * {@code ConfigurationInterpolator}, but does not define any other lookups.
    943     *
    944     * @return the {@code ConfigurationInterpolator}
    945     */
    946    private ConfigurationInterpolator initLocalInterpolator()
    947    {
    948        return new ConfigurationInterpolator()
    949        {
    950            @Override
    951            protected Lookup fetchLookupForPrefix(final String prefix)
    952            {
    953                return ConfigurationInterpolator
    954                        .nullSafeLookup(getInterpolator().getLookups().get(
    955                                prefix));
    956            }
    957        };
    958    }
    959
    960    /**
    961     * Checks whether the current configuration is set. If not, a
    962     * {@code CurrentConfigHolder} is now created and initialized, and
    963     * associated with the current thread. The member for the current
    964     * configuration is undefined if for the current key no configuration exists
    965     * yet.
    966     *
    967     * @return the {@code CurrentConfigHolder} instance for the current thread
    968     */
    969    private CurrentConfigHolder ensureCurrentConfiguration()
    970    {
    971        CurrentConfigHolder cch = CURRENT_CONFIG.get();
    972        if (cch == null)
    973        {
    974            final String key = String.valueOf(localSubst.interpolate(keyPattern));
    975            cch = new CurrentConfigHolder(key);
    976            cch.setCurrentConfiguration(configs.get(key));
    977            CURRENT_CONFIG.set(cch);
    978        }
    979        return cch;
    980    }
    981
    982    /**
    983     * Internal class that identifies each Configuration.
    984     */
    985    static class ConfigData
    986    {
    987        /** Stores a reference to the configuration. */
    988        private final Configuration configuration;
    989
    990        /** Stores the name under which the configuration is stored. */
    991        private final String name;
    992
    993        /** Stores the at string.*/
    994        private final String at;
    995
    996        /**
    997         * Creates a new instance of {@code ConfigData} and initializes
    998         * it.
    999         *
    1000         * @param config the configuration
    1001         * @param n the name
    1002         * @param at the at position
    1003         */
    1004        public ConfigData(final Configuration config, final String n, final String at)
    1005        {
    1006            configuration = config;
    1007            name = n;
    1008            this.at = at;
    1009        }
    1010
    1011        /**
    1012         * Returns the stored configuration.
    1013         *
    1014         * @return the configuration
    1015         */
    1016        public Configuration getConfiguration()
    1017        {
    1018            return configuration;
    1019        }
    1020
    1021        /**
    1022         * Returns the configuration's name.
    1023         *
    1024         * @return the name
    1025         */
    1026        public String getName()
    1027        {
    1028            return name;
    1029        }
    1030
    1031        /**
    1032         * Returns the at position of this configuration.
    1033         *
    1034         * @return the at position
    1035         */
    1036        public String getAt()
    1037        {
    1038            return at;
    1039        }
    1040
    1041    }
    1042
    1043    /**
    1044     * A simple data class holding information about the current configuration
    1045     * while an operation for a thread is processed.
    1046     */
    1047    private static class CurrentConfigHolder
    1048    {
    1049        /** Stores the current configuration of the current thread. */
    1050        private CombinedConfiguration currentConfiguration;
    1051
    1052        /**
    1053         * Stores the key of the configuration evaluated for the current thread
    1054         * at the beginning of an operation.
    1055         */
    1056        private final String key;
    1057
    1058        /** A counter for reentrant locks. */
    1059        private int lockCount;
    1060
    1061        /**
    1062         * Creates a new instance of {@code CurrentConfigHolder} and initializes
    1063         * it with the key for the current configuration.
    1064         *
    1065         * @param curKey the current key
    1066         */
    1067        public CurrentConfigHolder(final String curKey)
    1068        {
    1069            key = curKey;
    1070        }
    1071
    1072        /**
    1073         * Returns the current configuration.
    1074         *
    1075         * @return the current configuration
    1076         */
    1077        public CombinedConfiguration getCurrentConfiguration()
    1078        {
    1079            return currentConfiguration;
    1080        }
    1081
    1082        /**
    1083         * Sets the current configuration.
    1084         *
    1085         * @param currentConfiguration the current configuration
    1086         */
    1087        public void setCurrentConfiguration(
    1088                final CombinedConfiguration currentConfiguration)
    1089        {
    1090            this.currentConfiguration = currentConfiguration;
    1091        }
    1092
    1093        /**
    1094         * Returns the current key.
    1095         *
    1096         * @return the current key
    1097         */
    1098        public String getKey()
    1099        {
    1100            return key;
    1101        }
    1102
    1103        /**
    1104         * Increments the lock counter.
    1105         */
    1106        public void incrementLockCount()
    1107        {
    1108            lockCount++;
    1109        }
    1110
    1111        /**
    1112         * Decrements the lock counter and checks whether it has reached 0. In
    1113         * this cause, the operation is complete, and the lock can be released.
    1114         *
    1115         * @return <b>true</b> if the lock count reaches 0, <b>false</b>
    1116         *         otherwise
    1117         */
    1118        public boolean decrementLockCountAndCheckRelease()
    1119        {
    1120            return --lockCount == 0;
    1121        }
    1122    }
    1123}