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