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