    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.io.ByteArrayOutputStream;
    020import java.io.PrintStream;
    021import java.util.ArrayList;
    022import java.util.Collection;
    023import java.util.HashMap;
    024import java.util.HashSet;
    025import java.util.Iterator;
    026import java.util.List;
    027import java.util.Map;
    028import java.util.Set;
    029
    030import org.apache.commons.configuration2.event.ConfigurationEvent;
    031import org.apache.commons.configuration2.event.EventListener;
    032import org.apache.commons.configuration2.event.EventSource;
    033import org.apache.commons.configuration2.event.EventType;
    034import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
    035import org.apache.commons.configuration2.sync.LockMode;
    036import org.apache.commons.configuration2.tree.DefaultConfigurationKey;
    037import org.apache.commons.configuration2.tree.DefaultExpressionEngine;
    038import org.apache.commons.configuration2.tree.ExpressionEngine;
    039import org.apache.commons.configuration2.tree.ImmutableNode;
    040import org.apache.commons.configuration2.tree.NodeCombiner;
    041import org.apache.commons.configuration2.tree.NodeTreeWalker;
    042import org.apache.commons.configuration2.tree.QueryResult;
    043import org.apache.commons.configuration2.tree.TreeUtils;
    044import org.apache.commons.configuration2.tree.UnionCombiner;
    045
    046/**
    047 * <p>
    048 * A hierarchical composite configuration class.
    049 * </p>
    050 * <p>
    051 * This class maintains a list of configuration objects, which can be added
    052 * using the diverse {@code addConfiguration()} methods. After that the
    053 * configurations can be accessed either by name (if one was provided when the
    054 * configuration was added) or by index. For the whole set of managed
    055 * configurations a logical node structure is constructed. For this purpose a
    056 * {@link org.apache.commons.configuration2.tree.NodeCombiner NodeCombiner}
    057 * object can be set. This makes it possible to specify different algorithms for
    058 * the combination process.
    059 * </p>
    060 * <p>
    061 * The big advantage of this class is that it creates a truly hierarchical
    062 * structure of all the properties stored in the contained configurations - even
    063 * if some of them are no hierarchical configurations per se. So all enhanced
    064 * features provided by a hierarchical configuration (e.g. choosing an
    065 * expression engine) are applicable.
    066 * </p>
    067 * <p>
    068 * The class works by registering itself as an event listener at all added
    069 * configurations. So it gets notified whenever one of these configurations is
    070 * changed and can invalidate its internal node structure. The next time a
    071 * property is accessed the node structure will be re-constructed using the
    072 * current state of the managed configurations. Note that, depending on the used
    073 * {@code NodeCombiner}, this may be a complex operation.
    074 * </p>
    075 * <p>
    076 * Because of the way a {@code CombinedConfiguration} is working it has more or
    077 * less view character: it provides a logic view on the configurations it
    078 * contains. In this constellation not all methods defined for hierarchical
    079 * configurations - especially methods that update the stored properties - can
    080 * be implemented in a consistent manner. Using such methods (like
    081 * {@code addProperty()}, or {@code clearProperty()} on a
    082 * {@code CombinedConfiguration} is not strictly forbidden, however, depending
    083 * on the current {@link NodeCombiner} and the involved properties, the results
    084 * may be different than expected. Some examples may illustrate this:
    085 * </p>
    086 * <ul>
    087 * <li>Imagine a {@code CombinedConfiguration} <em>cc</em> containing two child
    088 * configurations with the following content:
    089 * <dl>
    090 * <dt>user.properties</dt>
    091 * <dd>
    092 *
    093 * <pre>
    094 * gui.background = blue
    095 * gui.position = (10, 10, 400, 200)
    096 * </pre>
    097 *
    098 * </dd>
    099 * <dt>default.properties</dt>
    100 * <dd>
    101 *
    102 * <pre>
    103 * gui.background = black
    104 * gui.foreground = white
    105 * home.dir = /data
    106 * </pre>
    107 *
    108 * </dd>
    109 * </dl>
    110 * As a {@code NodeCombiner} a
    111 * {@link org.apache.commons.configuration2.tree.OverrideCombiner
    112 * OverrideCombiner} is used. This combiner will ensure that defined user
    113 * settings take precedence over the default values. If the resulting
    114 * {@code CombinedConfiguration} is queried for the background color,
    115 * {@code blue} will be returned because this value is defined in
    116 * {@code user.properties}. Now consider what happens if the key
    117 * {@code gui.background} is removed from the {@code CombinedConfiguration}:
    118 *
    119 * <pre>
    120 * cc.clearProperty(&quot;gui.background&quot;);
    121 * </pre>
    122 *
    123 * Will a {@code cc.containsKey("gui.background")} now return <b>false</b>? No,
    124 * it won't! The {@code clearProperty()} operation is executed on the node set
    125 * of the combined configuration, which was constructed from the nodes of the
    126 * two child configurations. It causes the value of the <em>background</em> node
    127 * to be cleared, which is also part of the first child configuration. This
    128 * modification of one of its child configurations causes the
    129 * {@code CombinedConfiguration} to be re-constructed. This time the
    130 * {@code OverrideCombiner} cannot find a {@code gui.background} property in the
    131 * first child configuration, but it finds one in the second, and adds it to the
    132 * resulting combined configuration. So the property is still present (with a
    133 * different value now).</li>
    134 * <li>{@code addProperty()} can also be problematic: Most node combiners use
    135 * special view nodes for linking parts of the original configurations' data
    136 * together. If new properties are added to such a special node, they do not
    137 * belong to any of the managed configurations and thus hang in the air. Using
    138 * the same configurations as in the last example, the statement
    139 *
    140 * <pre>
    141 * addProperty(&quot;database.user&quot;, &quot;scott&quot;);
    142 * </pre>
    143 *
    144 * would cause such a hanging property. If now one of the child configurations
    145 * is changed and the {@code CombinedConfiguration} is re-constructed, this
    146 * property will disappear! (Add operations are not problematic if they result
    147 * in a child configuration being updated. For instance an
    148 * {@code addProperty("home.url", "localhost");} will alter the second child
    149 * configuration - because the prefix <em>home</em> is here already present;
    150 * when the {@code CombinedConfiguration} is re-constructed, this change is
    151 * taken into account.)</li>
    152 * </ul>
    153 * <p>
    154 * Because of such problems it is recommended to perform updates only on the
    155 * managed child configurations.
    156 * </p>
    157 * <p>
    158 * Whenever the node structure of a {@code CombinedConfiguration} becomes
    159 * invalid (either because one of the contained configurations was modified or
    160 * because the {@code invalidate()} method was directly called) an event is
    161 * generated. So this can be detected by interested event listeners. This also
    162 * makes it possible to add a combined configuration into another one.
    163 * </p>
    164 * <p>
    165 * Notes about thread-safety: This configuration implementation uses a
    166 * {@code Synchronizer} object to protect instances against concurrent access.
    167 * The concrete {@code Synchronizer} implementation used determines whether an
    168 * instance of this class is thread-safe or not. In contrast to other
    169 * implementations derived from {@link BaseHierarchicalConfiguration},
    170 * thread-safety is an issue here because the nodes structure used by this
    171 * configuration has to be constructed dynamically when a child configuration is
    172 * changed. Therefore, when multiple threads are involved which also manipulate
    173 * one of the child configurations, a proper {@code Synchronizer} object should
    174 * be set. Note that the {@code Synchronizer} objects used by the child
    175 * configurations do not really matter. Because immutable in-memory nodes
    176 * structures are used for them there is no danger that updates on child
    177 * configurations could interfere with read operations on the combined
    178 * configuration.
    179 * </p>
    180 *
    181 * @since 1.3
    182 * @version $Id: CombinedConfiguration.java 1842194 2018-09-27 22:24:23Z ggregory $
    183 */
    184public class CombinedConfiguration extends BaseHierarchicalConfiguration implements
    185        EventListener<ConfigurationEvent>
    186{
    187    /**
    188     * Constant for the event type fired when the internal node structure of a
    189     * combined configuration becomes invalid.
    190     *
    191     * @since 2.0
    192     */
    193    public static final EventType<ConfigurationEvent> COMBINED_INVALIDATE =
    194            new EventType<>(ConfigurationEvent.ANY,
    195                    "COMBINED_INVALIDATE");
    196
    197    /** Constant for the expression engine for parsing the at path. */
    198    private static final DefaultExpressionEngine AT_ENGINE = DefaultExpressionEngine.INSTANCE;
    199
    200    /** Constant for the default node combiner. */
    201    private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner();
    202
    203    /** Constant for a root node for an empty configuration. */
    204    private static final ImmutableNode EMPTY_ROOT = new ImmutableNode.Builder()
    205            .create();
    206
    207    /** Stores the combiner. */
    208    private NodeCombiner nodeCombiner;
    209
    210    /** Stores a list with the contained configurations. */
    211    private List<ConfigData> configurations;
    212
    213    /** Stores a map with the named configurations. */
    214    private Map<String, Configuration> namedConfigurations;
    215
    216    /**
    217     * An expression engine used for converting child configurations to
    218     * hierarchical ones.
    219     */
    220    private ExpressionEngine conversionExpressionEngine;
    221
    222    /** A flag whether this configuration is up-to-date. */
    223    private boolean upToDate;
    224
    225    /**
    226     * Creates a new instance of {@code CombinedConfiguration} and
    227     * initializes the combiner to be used.
    228     *
    229     * @param comb the node combiner (can be <b>null</b>, then a union combiner
    230     * is used as default)
    231     */
    232    public CombinedConfiguration(final NodeCombiner comb)
    233    {
    234        nodeCombiner = (comb != null) ? comb : DEFAULT_COMBINER;
    235        initChildCollections();
    236    }
    237
    238    /**
    239     * Creates a new instance of {@code CombinedConfiguration} that uses
    240     * a union combiner.
    241     *
    242     * @see org.apache.commons.configuration2.tree.UnionCombiner
    243     */
    244    public CombinedConfiguration()
    245    {
    246        this(null);
    247    }
    248
    249    /**
    250     * Returns the node combiner that is used for creating the combined node
    251     * structure.
    252     *
    253     * @return the node combiner
    254     */
    255    public NodeCombiner getNodeCombiner()
    256    {
    257        beginRead(true);
    258        try
    259        {
    260            return nodeCombiner;
    261        }
    262        finally
    263        {
    264            endRead();
    265        }
    266    }
    267
    268    /**
    269     * Sets the node combiner. This object will be used when the combined node
    270     * structure is to be constructed. It must not be <b>null</b>, otherwise an
    271     * {@code IllegalArgumentException} exception is thrown. Changing the
    272     * node combiner causes an invalidation of this combined configuration, so
    273     * that the new combiner immediately takes effect.
    274     *
    275     * @param nodeCombiner the node combiner
    276     */
    277    public void setNodeCombiner(final NodeCombiner nodeCombiner)
    278    {
    279        if (nodeCombiner == null)
    280        {
    281            throw new IllegalArgumentException(
    282                    "Node combiner must not be null!");
    283        }
    284
    285        beginWrite(true);
    286        try
    287        {
    288            this.nodeCombiner = nodeCombiner;
    289            invalidateInternal();
    290        }
    291        finally
    292        {
    293            endWrite();
    294        }
    295    }
    296
    297    /**
    298     * Returns the {@code ExpressionEngine} for converting flat child
    299     * configurations to hierarchical ones.
    300     *
    301     * @return the conversion expression engine
    302     * @since 1.6
    303     */
    304    public ExpressionEngine getConversionExpressionEngine()
    305    {
    306        beginRead(true);
    307        try
    308        {
    309            return conversionExpressionEngine;
    310        }
    311        finally
    312        {
    313            endRead();
    314        }
    315    }
    316
    317    /**
    318     * Sets the {@code ExpressionEngine} for converting flat child
    319     * configurations to hierarchical ones. When constructing the root node for
    320     * this combined configuration the properties of all child configurations
    321     * must be combined to a single hierarchical node structure. In this
    322     * process, non hierarchical configurations are converted to hierarchical
    323     * ones first. This can be problematic if a child configuration contains
    324     * keys that are no compatible with the default expression engine used by
    325     * hierarchical configurations. Therefore it is possible to specify a
    326     * specific expression engine to be used for this purpose.
    327     *
    328     * @param conversionExpressionEngine the conversion expression engine
    329     * @see ConfigurationUtils#convertToHierarchical(Configuration, ExpressionEngine)
    330     * @since 1.6
    331     */
    332    public void setConversionExpressionEngine(
    333            final ExpressionEngine conversionExpressionEngine)
    334    {
    335        beginWrite(true);
    336        try
    337        {
    338            this.conversionExpressionEngine = conversionExpressionEngine;
    339        }
    340        finally
    341        {
    342            endWrite();
    343        }
    344    }
    345
    346    /**
    347     * Adds a new configuration to this combined configuration. It is possible
    348     * (but not mandatory) to give the new configuration a name. This name must
    349     * be unique, otherwise a {@code ConfigurationRuntimeException} will
    350     * be thrown. With the optional {@code at} argument you can specify
    351     * where in the resulting node structure the content of the added
    352     * configuration should appear. This is a string that uses dots as property
    353     * delimiters (independent on the current expression engine). For instance
    354     * if you pass in the string {@code "database.tables"},
    355     * all properties of the added configuration will occur in this branch.
    356     *
    357     * @param config the configuration to add (must not be <b>null</b>)
    358     * @param name the name of this configuration (can be <b>null</b>)
    359     * @param at the position of this configuration in the combined tree (can be
    360     * <b>null</b>)
    361     */
    362    public void addConfiguration(final Configuration config, final String name,
    363            final String at)
    364    {
    365        if (config == null)
    366        {
    367            throw new IllegalArgumentException(
    368                    "Added configuration must not be null!");
    369        }
    370
    371        beginWrite(true);
    372        try
    373        {
    374            if (name != null && namedConfigurations.containsKey(name))
    375            {
    376                throw new ConfigurationRuntimeException(
    377                        "A configuration with the name '"
    378                                + name
    379                                + "' already exists in this combined configuration!");
    380            }
    381
    382            final ConfigData cd = new ConfigData(config, name, at);
    383            if (getLogger().isDebugEnabled())
    384            {
    385                getLogger()
    386                        .debug("Adding configuration " + config + " with name "
    387                                + name);
    388            }
    389            configurations.add(cd);
    390            if (name != null)
    391            {
    392                namedConfigurations.put(name, config);
    393            }
    394
    395            invalidateInternal();
    396        }
    397        finally
    398        {
    399            endWrite();
    400        }
    401        registerListenerAt(config);
    402    }
    403
    404    /**
    405     * Adds a new configuration to this combined configuration with an optional
    406     * name. The new configuration's properties will be added under the root of
    407     * the combined node structure.
    408     *
    409     * @param config the configuration to add (must not be <b>null</b>)
    410     * @param name the name of this configuration (can be <b>null</b>)
    411     */
    412    public void addConfiguration(final Configuration config, final String name)
    413    {
    414        addConfiguration(config, name, null);
    415    }
    416
    417    /**
    418     * Adds a new configuration to this combined configuration. The new
    419     * configuration is not given a name. Its properties will be added under the
    420     * root of the combined node structure.
    421     *
    422     * @param config the configuration to add (must not be <b>null</b>)
    423     */
    424    public void addConfiguration(final Configuration config)
    425    {
    426        addConfiguration(config, null, null);
    427    }
    428
    429    /**
    430     * Returns the number of configurations that are contained in this combined
    431     * configuration.
    432     *
    433     * @return the number of contained configurations
    434     */
    435    public int getNumberOfConfigurations()
    436    {
    437        beginRead(true);
    438        try
    439        {
    440            return getNumberOfConfigurationsInternal();
    441        }
    442        finally
    443        {
    444            endRead();
    445        }
    446    }
    447
    448    /**
    449     * Returns the configuration at the specified index. The contained
    450     * configurations are numbered in the order they were added to this combined
    451     * configuration. The index of the first configuration is 0.
    452     *
    453     * @param index the index
    454     * @return the configuration at this index
    455     */
    456    public Configuration getConfiguration(final int index)
    457    {
    458        beginRead(true);
    459        try
    460        {
    461            final ConfigData cd = configurations.get(index);
    462            return cd.getConfiguration();
    463        }
    464        finally
    465        {
    466            endRead();
    467        }
    468    }
    469
    470    /**
    471     * Returns the configuration with the given name. This can be <b>null</b>
    472     * if no such configuration exists.
    473     *
    474     * @param name the name of the configuration
    475     * @return the configuration with this name
    476     */
    477    public Configuration getConfiguration(final String name)
    478    {
    479        beginRead(true);
    480        try
    481        {
    482            return namedConfigurations.get(name);
    483        }
    484        finally
    485        {
    486            endRead();
    487        }
    488    }
    489
    490    /**
    491     * Returns a List of all the configurations that have been added.
    492     * @return A List of all the configurations.
    493     * @since 1.7
    494     */
    495    public List<Configuration> getConfigurations()
    496    {
    497        beginRead(true);
    498        try
    499        {
    500            final List<Configuration> list =
    501                    new ArrayList<>(getNumberOfConfigurationsInternal());
    502            for (final ConfigData cd : configurations)
    503            {
    504                list.add(cd.getConfiguration());
    505            }
    506            return list;
    507        }
    508        finally
    509        {
    510            endRead();
    511        }
    512    }
    513
    514    /**
    515     * Returns a List of the names of all the configurations that have been
    516     * added in the order they were added. A NULL value will be present in
    517     * the list for each configuration that was added without a name.
    518     * @return A List of all the configuration names.
    519     * @since 1.7
    520     */
    521    public List<String> getConfigurationNameList()
    522    {
    523        beginRead(true);
    524        try
    525        {
    526            final List<String> list = new ArrayList<>(getNumberOfConfigurationsInternal());
    527            for (final ConfigData cd : configurations)
    528            {
    529                list.add(cd.getName());
    530            }
    531            return list;
    532        }
    533        finally
    534        {
    535            endRead();
    536        }
    537    }
    538
    539    /**
    540     * Removes the specified configuration from this combined configuration.
    541     *
    542     * @param config the configuration to be removed
    543     * @return a flag whether this configuration was found and could be removed
    544     */
    545    public boolean removeConfiguration(final Configuration config)
    546    {
    547        for (int index = 0; index < getNumberOfConfigurations(); index++)
    548        {
    549            if (configurations.get(index).getConfiguration() == config)
    550            {
    551                removeConfigurationAt(index);
    552                return true;
    553            }
    554        }
    555
    556        return false;
    557    }
    558
    559    /**
    560     * Removes the configuration at the specified index.
    561     *
    562     * @param index the index
    563     * @return the removed configuration
    564     */
    565    public Configuration removeConfigurationAt(final int index)
    566    {
    567        final ConfigData cd = configurations.remove(index);
    568        if (cd.getName() != null)
    569        {
    570            namedConfigurations.remove(cd.getName());
    571        }
    572        unregisterListenerAt(cd.getConfiguration());
    573        invalidateInternal();
    574        return cd.getConfiguration();
    575    }
    576
    577    /**
    578     * Removes the configuration with the specified name.
    579     *
    580     * @param name the name of the configuration to be removed
    581     * @return the removed configuration (<b>null</b> if this configuration
    582     * was not found)
    583     */
    584    public Configuration removeConfiguration(final String name)
    585    {
    586        final Configuration conf = getConfiguration(name);
    587        if (conf != null)
    588        {
    589            removeConfiguration(conf);
    590        }
    591        return conf;
    592    }
    593
    594    /**
    595     * Returns a set with the names of all configurations contained in this
    596     * combined configuration. Of course here are only these configurations
    597     * listed, for which a name was specified when they were added.
    598     *
    599     * @return a set with the names of the contained configurations (never
    600     * <b>null</b>)
    601     */
    602    public Set<String> getConfigurationNames()
    603    {
    604        beginRead(true);
    605        try
    606        {
    607            return namedConfigurations.keySet();
    608        }
    609        finally
    610        {
    611            endRead();
    612        }
    613    }
    614
    615    /**
    616     * Invalidates this combined configuration. This means that the next time a
    617     * property is accessed the combined node structure must be re-constructed.
    618     * Invalidation of a combined configuration also means that an event of type
    619     * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other
    620     * events most times appear twice (once before and once after an update),
    621     * this event is only fired once (after update).
    622     */
    623    public void invalidate()
    624    {
    625        beginWrite(true);
    626        try
    627        {
    628            invalidateInternal();
    629        }
    630        finally
    631        {
    632            endWrite();
    633        }
    634    }
    635
    636    /**
    637     * Event listener call back for configuration update events. This method is
    638     * called whenever one of the contained configurations was modified. It
    639     * invalidates this combined configuration.
    640     *
    641     * @param event the update event
    642     */
    643    @Override
    644    public void onEvent(final ConfigurationEvent event)
    645    {
    646        if (event.isBeforeUpdate())
    647        {
    648            invalidate();
    649        }
    650    }
    651
    652    /**
    653     * Clears this configuration. All contained configurations will be removed.
    654     */
    655    @Override
    656    protected void clearInternal()
    657    {
    658        unregisterListenerAtChildren();
    659        initChildCollections();
    660        invalidateInternal();
    661    }
    662
    663    /**
    664     * Returns a copy of this object. This implementation performs a deep clone,
    665     * i.e. all contained configurations will be cloned, too. For this to work,
    666     * all contained configurations must be cloneable. Registered event
    667     * listeners won't be cloned. The clone will use the same node combiner than
    668     * the original.
    669     *
    670     * @return the copied object
    671     */
    672    @Override
    673    public Object clone()
    674    {
    675        beginRead(false);
    676        try
    677        {
    678            final CombinedConfiguration copy = (CombinedConfiguration) super.clone();
    679            copy.initChildCollections();
    680            for (final ConfigData cd : configurations)
    681            {
    682                copy.addConfiguration(ConfigurationUtils.cloneConfiguration(cd
    683                        .getConfiguration()), cd.getName(), cd.getAt());
    684            }
    685
    686            return copy;
    687        }
    688        finally
    689        {
    690            endRead();
    691        }
    692    }
    693
    694    /**
    695     * Returns the configuration source, in which the specified key is defined.
    696     * This method will determine the configuration node that is identified by
    697     * the given key. The following constellations are possible:
    698     * <ul>
    699     * <li>If no node object is found for this key, <b>null</b> is returned.</li>
    700     * <li>If the key maps to multiple nodes belonging to different
    701     * configuration sources, a {@code IllegalArgumentException} is
    702     * thrown (in this case no unique source can be determined).</li>
    703     * <li>If exactly one node is found for the key, the (child) configuration
    704     * object, to which the node belongs is determined and returned.</li>
    705     * <li>For keys that have been added directly to this combined
    706     * configuration and that do not belong to the namespaces defined by
    707     * existing child configurations this configuration will be returned.</li>
    708     * </ul>
    709     *
    710     * @param key the key of a configuration property
    711     * @return the configuration, to which this property belongs or <b>null</b>
    712     * if the key cannot be resolved
    713     * @throws IllegalArgumentException if the key maps to multiple properties
    714     * and the source cannot be determined, or if the key is <b>null</b>
    715     * @since 1.5
    716     */
    717    public Configuration getSource(final String key)
    718    {
    719        if (key == null)
    720        {
    721            throw new IllegalArgumentException("Key must not be null!");
    722        }
    723
    724        final Set<Configuration> sources = getSources(key);
    725        if (sources.isEmpty())
    726        {
    727            return null;
    728        }
    729        final Iterator<Configuration> iterator = sources.iterator();
    730        final Configuration source = iterator.next();
    731        if (iterator.hasNext())
    732        {
    733            throw new IllegalArgumentException("The key " + key
    734                    + " is defined by multiple sources!");
    735        }
    736        return source;
    737    }
    738
    739    /**
    740     * Returns a set with the configuration sources, in which the specified key
    741     * is defined. This method determines the configuration nodes that are
    742     * identified by the given key. It then determines the configuration sources
    743     * to which these nodes belong and adds them to the result set. Note the
    744     * following points:
    745     * <ul>
    746     * <li>If no node object is found for this key, an empty set is returned.</li>
    747     * <li>For keys that have been added directly to this combined configuration
    748     * and that do not belong to the namespaces defined by existing child
    749     * configurations this combined configuration is contained in the result
    750     * set.</li>
    751     * </ul>
    752     *
    753     * @param key the key of a configuration property
    754     * @return a set with the configuration sources, which contain this property
    755     * @since 2.0
    756     */
    757    public Set<Configuration> getSources(final String key)
    758    {
    759        beginRead(false);
    760        try
    761        {
    762            final List<QueryResult<ImmutableNode>> results = fetchNodeList(key);
    763            final Set<Configuration> sources = new HashSet<>();
    764
    765            for (final QueryResult<ImmutableNode> result : results)
    766            {
    767                final Set<Configuration> resultSources =
    768                        findSourceConfigurations(result.getNode());
    769                if (resultSources.isEmpty())
    770                {
    771                    // key must be defined in combined configuration
    772                    sources.add(this);
    773                }
    774                else
    775                {
    776                    sources.addAll(resultSources);
    777                }
    778            }
    779
    780            return sources;
    781        }
    782        finally
    783        {
    784            endRead();
    785        }
    786    }
    787
    788    /**
    789     * {@inheritDoc} This implementation checks whether a combined root node
    790     * is available. If not, it is constructed by requesting a write lock.
    791     */
    792    @Override
    793    protected void beginRead(final boolean optimize)
    794    {
    795        if (optimize)
    796        {
    797            // just need a lock, don't construct configuration
    798            super.beginRead(true);
    799            return;
    800        }
    801
    802        boolean lockObtained = false;
    803        do
    804        {
    805            super.beginRead(false);
    806            if (isUpToDate())
    807            {
    808                lockObtained = true;
    809            }
    810            else
    811            {
    812                // release read lock and try to obtain a write lock
    813                endRead();
    814                beginWrite(false); // this constructs the root node
    815                endWrite();
    816            }
    817        } while (!lockObtained);
    818    }
    819
    820    /**
    821     * {@inheritDoc} This implementation checks whether a combined root node
    822     * is available. If not, it is constructed now.
    823     */
    824    @Override
    825    protected void beginWrite(final boolean optimize)
    826    {
    827        super.beginWrite(true);
    828        if (optimize)
    829        {
    830            // just need a lock, don't construct configuration
    831            return;
    832        }
    833
    834        try
    835        {
    836            if (!isUpToDate())
    837            {
    838                getSubConfigurationParentModel().replaceRoot(
    839                        constructCombinedNode(), this);
    840                upToDate = true;
    841            }
    842        }
    843        catch (final RuntimeException rex)
    844        {
    845            endWrite();
    846            throw rex;
    847        }
    848    }
    849
    850    /**
    851     * Returns a flag whether this configuration has been invalidated. This
    852     * means that the combined nodes structure has to be rebuilt before the
    853     * configuration can be accessed.
    854     *
    855     * @return a flag whether this configuration is invalid
    856     */
    857    private boolean isUpToDate()
    858    {
    859        return upToDate;
    860    }
    861
    862    /**
    863     * Marks this configuration as invalid. This means that the next access
    864     * re-creates the root node. An invalidate event is also fired. Note:
    865     * This implementation expects that an exclusive (write) lock is held on
    866     * this instance.
    867     */
    868    private void invalidateInternal()
    869    {
    870        upToDate = false;
    871        fireEvent(COMBINED_INVALIDATE, null, null, false);
    872    }
    873
    874    /**
    875     * Initializes internal data structures for storing information about
    876     * child configurations.
    877     */
    878    private void initChildCollections()
    879    {
    880        configurations = new ArrayList<>();
    881        namedConfigurations = new HashMap<>();
    882    }
    883
    884    /**
    885     * Creates the root node of this combined configuration.
    886     *
    887     * @return the combined root node
    888     */
    889    private ImmutableNode constructCombinedNode()
    890    {
    891        if (getNumberOfConfigurationsInternal() < 1)
    892        {
    893            if (getLogger().isDebugEnabled())
    894            {
    895                getLogger().debug("No configurations defined for " + this);
    896            }
    897            return EMPTY_ROOT;
    898        }
    899        final Iterator<ConfigData> it = configurations.iterator();
    900        ImmutableNode node = it.next().getTransformedRoot();
    901        while (it.hasNext())
    902        {
    903            node = nodeCombiner.combine(node,
    904                    it.next().getTransformedRoot());
    905        }
    906        if (getLogger().isDebugEnabled())
    907        {
    908            final ByteArrayOutputStream os = new ByteArrayOutputStream();
    909            final PrintStream stream = new PrintStream(os);
    910            TreeUtils.printTree(stream, node);
    911            getLogger().debug(os.toString());
    912        }
    913        return node;
    914    }
    915
    916    /**
    917     * Determines the configurations to which the specified node belongs. This
    918     * is done by inspecting the nodes structures of all child configurations.
    919     *
    920     * @param node the node
    921     * @return a set with the owning configurations
    922     */
    923    private Set<Configuration> findSourceConfigurations(final ImmutableNode node)
    924    {
    925        final Set<Configuration> result = new HashSet<>();
    926        final FindNodeVisitor<ImmutableNode> visitor =
    927                new FindNodeVisitor<>(node);
    928
    929        for (final ConfigData cd : configurations)
    930        {
    931            NodeTreeWalker.INSTANCE.walkBFS(cd.getRootNode(), visitor,
    932                    getModel().getNodeHandler());
    933            if (visitor.isFound())
    934            {
    935                result.add(cd.getConfiguration());
    936                visitor.reset();
    937            }
    938        }
    939
    940        return result;
    941    }
    942
    943    /**
    944     * Registers this combined configuration as listener at the given child
    945     * configuration.
    946     *
    947     * @param configuration the child configuration
    948     */
    949    private void registerListenerAt(final Configuration configuration)
    950    {
    951        if (configuration instanceof EventSource)
    952        {
    953            ((EventSource) configuration).addEventListener(
    954                    ConfigurationEvent.ANY, this);
    955        }
    956    }
    957
    958    /**
    959     * Removes this combined configuration as listener from the given child
    960     * configuration.
    961     *
    962     * @param configuration the child configuration
    963     */
    964    private void unregisterListenerAt(final Configuration configuration)
    965    {
    966        if (configuration instanceof EventSource)
    967        {
    968            ((EventSource) configuration).removeEventListener(
    969                    ConfigurationEvent.ANY, this);
    970        }
    971    }
    972
    973    /**
    974     * Removes this combined configuration as listener from all child
    975     * configurations. This method is called on a clear() operation.
    976     */
    977    private void unregisterListenerAtChildren()
    978    {
    979        if (configurations != null)
    980        {
    981            for (final ConfigData child : configurations)
    982            {
    983                unregisterListenerAt(child.getConfiguration());
    984            }
    985        }
    986    }
    987
    988    /**
    989     * Returns the number of child configurations in this combined
    990     * configuration. The internal list of child configurations is accessed
    991     * without synchronization.
    992     *
    993     * @return the number of child configurations
    994     */
    995    private int getNumberOfConfigurationsInternal()
    996    {
    997        return configurations.size();
    998    }
    999
    1000    /**
    1001     * An internal helper class for storing information about contained
    1002     * configurations.
    1003     */
    1004    private class ConfigData
    1005    {
    1006        /** Stores a reference to the configuration. */
    1007        private final Configuration configuration;
    1008
    1009        /** Stores the name under which the configuration is stored. */
    1010        private final String name;
    1011
    1012        /** Stores the at information as path of nodes. */
    1013        private final Collection<String> atPath;
    1014
    1015        /** Stores the at string.*/
    1016        private final String at;
    1017
    1018        /** Stores the root node for this child configuration.*/
    1019        private ImmutableNode rootNode;
    1020
    1021        /**
    1022         * Creates a new instance of {@code ConfigData} and initializes
    1023         * it.
    1024         *
    1025         * @param config the configuration
    1026         * @param n the name
    1027         * @param at the at position
    1028         */
    1029        public ConfigData(final Configuration config, final String n, final String at)
    1030        {
    1031            configuration = config;
    1032            name = n;
    1033            atPath = parseAt(at);
    1034            this.at = at;
    1035        }
    1036
    1037        /**
    1038         * Returns the stored configuration.
    1039         *
    1040         * @return the configuration
    1041         */
    1042        public Configuration getConfiguration()
    1043        {
    1044            return configuration;
    1045        }
    1046
    1047        /**
    1048         * Returns the configuration's name.
    1049         *
    1050         * @return the name
    1051         */
    1052        public String getName()
    1053        {
    1054            return name;
    1055        }
    1056
    1057        /**
    1058         * Returns the at position of this configuration.
    1059         *
    1060         * @return the at position
    1061         */
    1062        public String getAt()
    1063        {
    1064            return at;
    1065        }
    1066
    1067        /**
    1068         * Returns the root node for this child configuration.
    1069         *
    1070         * @return the root node of this child configuration
    1071         * @since 1.5
    1072         */
    1073        public ImmutableNode getRootNode()
    1074        {
    1075            return rootNode;
    1076        }
    1077
    1078        /**
    1079         * Returns the transformed root node of the stored configuration. The
    1080         * term &quot;transformed&quot; means that an eventually defined at path
    1081         * has been applied.
    1082         *
    1083         * @return the transformed root node
    1084         */
    1085        public ImmutableNode getTransformedRoot()
    1086        {
    1087            final ImmutableNode configRoot = getRootNodeOfConfiguration();
    1088            return (atPath == null) ? configRoot : prependAtPath(configRoot);
    1089        }
    1090
    1091        /**
    1092         * Prepends the at path to the given node.
    1093         *
    1094         * @param node the root node of the represented configuration
    1095         * @return the new root node including the at path
    1096         */
    1097        private ImmutableNode prependAtPath(final ImmutableNode node)
    1098        {
    1099            final ImmutableNode.Builder pathBuilder = new ImmutableNode.Builder();
    1100            final Iterator<String> pathIterator = atPath.iterator();
    1101            prependAtPathComponent(pathBuilder, pathIterator.next(),
    1102                    pathIterator, node);
    1103            return new ImmutableNode.Builder(1).addChild(pathBuilder.create())
    1104                    .create();
    1105        }
    1106
    1107        /**
    1108         * Handles a single component of the at path. A corresponding node is
    1109         * created and added to the hierarchical path to the original root node
    1110         * of the configuration.
    1111         *
    1112         * @param builder the current node builder object
    1113         * @param currentComponent the name of the current path component
    1114         * @param components an iterator with all components of the at path
    1115         * @param orgRoot the original root node of the wrapped configuration
    1116         */
    1117        private void prependAtPathComponent(final ImmutableNode.Builder builder,
    1118                final String currentComponent, final Iterator<String> components,
    1119                final ImmutableNode orgRoot)
    1120        {
    1121            builder.name(currentComponent);
    1122            if (components.hasNext())
    1123            {
    1124                final ImmutableNode.Builder childBuilder =
    1125                        new ImmutableNode.Builder();
    1126                prependAtPathComponent(childBuilder, components.next(),
    1127                        components, orgRoot);
    1128                builder.addChild(childBuilder.create());
    1129            }
    1130            else
    1131            {
    1132                builder.addChildren(orgRoot.getChildren());
    1133                builder.addAttributes(orgRoot.getAttributes());
    1134                builder.value(orgRoot.getValue());
    1135            }
    1136        }
    1137
    1138        /**
    1139         * Obtains the root node of the wrapped configuration. If necessary, a
    1140         * hierarchical representation of the configuration has to be created
    1141         * first.
    1142         *
    1143         * @return the root node of the associated configuration
    1144         */
    1145        private ImmutableNode getRootNodeOfConfiguration()
    1146        {
    1147            getConfiguration().lock(LockMode.READ);
    1148            try
    1149            {
    1150                final ImmutableNode root =
    1151                        ConfigurationUtils
    1152                                .convertToHierarchical(getConfiguration(),
    1153                                        conversionExpressionEngine).getNodeModel()
    1154                                .getInMemoryRepresentation();
    1155                rootNode = root;
    1156                return root;
    1157            }
    1158            finally
    1159            {
    1160                getConfiguration().unlock(LockMode.READ);
    1161            }
    1162        }
    1163
    1164        /**
    1165         * Splits the at path into its components.
    1166         *
    1167         * @param at the at string
    1168         * @return a collection with the names of the single components
    1169         */
    1170        private Collection<String> parseAt(final String at)
    1171        {
    1172            if (at == null)
    1173            {
    1174                return null;
    1175            }
    1176
    1177            final Collection<String> result = new ArrayList<>();
    1178            final DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
    1179                    AT_ENGINE, at).iterator();
    1180            while (it.hasNext())
    1181            {
    1182                result.add(it.nextKey());
    1183            }
    1184            return result;
    1185        }
    1186    }
    1187}