    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     */
    017    package org.apache.commons.configuration;
    018
    019    import java.io.ByteArrayOutputStream;
    020    import java.io.PrintStream;
    021    import java.util.ArrayList;
    022    import java.util.Collection;
    023    import java.util.HashMap;
    024    import java.util.Iterator;
    025    import java.util.List;
    026    import java.util.Map;
    027    import java.util.Set;
    028
    029    import org.apache.commons.configuration.event.ConfigurationEvent;
    030    import org.apache.commons.configuration.event.ConfigurationListener;
    031    import org.apache.commons.configuration.tree.ConfigurationNode;
    032    import org.apache.commons.configuration.tree.DefaultConfigurationKey;
    033    import org.apache.commons.configuration.tree.DefaultConfigurationNode;
    034    import org.apache.commons.configuration.tree.DefaultExpressionEngine;
    035    import org.apache.commons.configuration.tree.ExpressionEngine;
    036    import org.apache.commons.configuration.tree.NodeCombiner;
    037    import org.apache.commons.configuration.tree.TreeUtils;
    038    import org.apache.commons.configuration.tree.UnionCombiner;
    039    import org.apache.commons.configuration.tree.ViewNode;
    040
    041    /**
    042     * <p>
    043     * A hierarchical composite configuration class.
    044     * </p>
    045     * <p>
    046     * This class maintains a list of configuration objects, which can be added
    047     * using the divers {@code addConfiguration()} methods. After that the
    048     * configurations can be accessed either by name (if one was provided when the
    049     * configuration was added) or by index. For the whole set of managed
    050     * configurations a logical node structure is constructed. For this purpose a
    051     * {@link org.apache.commons.configuration.tree.NodeCombiner NodeCombiner}
    052     * object can be set. This makes it possible to specify different algorithms for
    053     * the combination process.
    054     * </p>
    055     * <p>
    056     * The big advantage of this class is that it creates a truly hierarchical
    057     * structure of all the properties stored in the contained configurations - even
    058     * if some of them are no hierarchical configurations per se. So all enhanced
    059     * features provided by a hierarchical configuration (e.g. choosing an
    060     * expression engine) are applicable.
    061     * </p>
    062     * <p>
    063     * The class works by registering itself as an event listener at all added
    064     * configurations. So it gets notified whenever one of these configurations is
    065     * changed and can invalidate its internal node structure. The next time a
    066     * property is accessed the node structure will be re-constructed using the
    067     * current state of the managed configurations. Note that, depending on the used
    068     * {@code NodeCombiner}, this may be a complex operation.
    069     * </p>
    070     * <p>
    071     * Because of the way a {@code CombinedConfiguration} is working it has
    072     * more or less view character: it provides a logic view on the configurations
    073     * it contains. In this constellation not all methods defined for hierarchical
    074     * configurations - especially methods that update the stored properties - can
    075     * be implemented in a consistent manner. Using such methods (like
    076     * {@code addProperty()}, or {@code clearProperty()} on a
    077     * {@code CombinedConfiguration} is not strictly forbidden, however,
    078     * depending on the current {@link NodeCombiner} and the involved
    079     * properties, the results may be different than expected. Some examples may
    080     * illustrate this:
    081     * </p>
    082     * <p>
    083     * <ul>
    084     * <li>Imagine a {@code CombinedConfiguration} <em>cc</em> containing
    085     * two child configurations with the following content:
    086     * <dl>
    087     * <dt>user.properties</dt>
    088     * <dd>
    089     *
    090     * <pre>
    091     * gui.background = blue
    092     * gui.position = (10, 10, 400, 200)
    093     * </pre>
    094     *
    095     * </dd>
    096     * <dt>default.properties</dt>
    097     * <dd>
    098     *
    099     * <pre>
    100     * gui.background = black
    101     * gui.foreground = white
    102     * home.dir = /data
    103     * </pre>
    104     *
    105     * </dd>
    106     * </dl>
    107     * As a {@code NodeCombiner} a
    108     * {@link org.apache.commons.configuration.tree.OverrideCombiner OverrideCombiner}
    109     * is used. This combiner will ensure that defined user settings take precedence
    110     * over the default values. If the resulting {@code CombinedConfiguration}
    111     * is queried for the background color, {@code blue} will be returned
    112     * because this value is defined in {@code user.properties}. Now
    113     * consider what happens if the key {@code gui.background} is removed
    114     * from the {@code CombinedConfiguration}:
    115     *
    116     * <pre>cc.clearProperty("gui.background");</pre>
    117     *
    118     * Will a {@code cc.containsKey("gui.background")} now return <b>false</b>?
    119     * No, it won't! The {@code clearProperty()} operation is executed on the
    120     * node set of the combined configuration, which was constructed from the nodes
    121     * of the two child configurations. It causes the value of the
    122     * <em>background</em> node to be cleared, which is also part of the first
    123     * child configuration. This modification of one of its child configurations
    124     * causes the {@code CombinedConfiguration} to be re-constructed. This
    125     * time the {@code OverrideCombiner} cannot find a
    126     * {@code gui.background} property in the first child configuration, but
    127     * it finds one in the second, and adds it to the resulting combined
    128     * configuration. So the property is still present (with a different value now).</li>
    129     * <li>{@code addProperty()} can also be problematic: Most node
    130     * combiners use special view nodes for linking parts of the original
    131     * configurations' data together. If new properties are added to such a special
    132     * node, they do not belong to any of the managed configurations and thus hang
    133     * in the air. Using the same configurations as in the last example, the
    134     * statement
    135     *
    136     * <pre>
    137     * addProperty("database.user", "scott");
    138     * </pre>
    139     *
    140     * would cause such a hanging property. If now one of the child configurations
    141     * is changed and the {@code CombinedConfiguration} is re-constructed,
    142     * this property will disappear! (Add operations are not problematic if they
    143     * result in a child configuration being updated. For instance an
    144     * {@code addProperty("home.url", "localhost");} will alter the second
    145     * child configuration - because the prefix <em>home</em> is here already
    146     * present; when the {@code CombinedConfiguration} is re-constructed,
    147     * this change is taken into account.)</li>
    148     * </ul>
    149     * Because of such problems it is recommended to perform updates only on the
    150     * managed child configurations.
    151     * </p>
    152     * <p>
    153     * Whenever the node structure of a {@code CombinedConfiguration} becomes
    154     * invalid (either because one of the contained configurations was modified or
    155     * because the {@code invalidate()} method was directly called) an event
    156     * is generated. So this can be detected by interested event listeners. This
    157     * also makes it possible to add a combined configuration into another one.
    158     * </p>
    159     * <p>
    160     * Implementation note: Adding and removing configurations to and from a
    161     * combined configuration is not thread-safe. If a combined configuration is
    162     * manipulated by multiple threads, the developer has to take care about
    163     * properly synchronization.
    164     * </p>
    165     *
    166     * @author <a
    167     * href="http://commons.apache.org/configuration/team-list.html">Commons
    168     * Configuration team</a>
    169     * @since 1.3
    170     * @version $Id: CombinedConfiguration.java 1234985 2012-01-23 21:09:09Z oheger $
    171     */
    172    public class CombinedConfiguration extends HierarchicalReloadableConfiguration implements
    173            ConfigurationListener, Cloneable
    174    {
    175        /**
    176         * Constant for the invalidate event that is fired when the internal node
    177         * structure becomes invalid.
    178         */
    179        public static final int EVENT_COMBINED_INVALIDATE = 40;
    180
    181        /**
    182         * The serial version ID.
    183         */
    184        private static final long serialVersionUID = 8338574525528692307L;
    185
    186        /** Constant for the expression engine for parsing the at path. */
    187        private static final DefaultExpressionEngine AT_ENGINE = new DefaultExpressionEngine();
    188
    189        /** Constant for the default node combiner. */
    190        private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner();
    191
    192        /** Constant for the name of the property used for the reload check.*/
    193        private static final String PROP_RELOAD_CHECK = "CombinedConfigurationReloadCheck";
    194
    195        /** Stores the combiner. */
    196        private NodeCombiner nodeCombiner;
    197
    198        /** Stores the combined root node. */
    199        private volatile ConfigurationNode combinedRoot;
    200
    201        /** Stores a list with the contained configurations. */
    202        private List<ConfigData> configurations;
    203
    204        /** Stores a map with the named configurations. */
    205        private Map<String, AbstractConfiguration> namedConfigurations;
    206
    207        /** The default behavior is to ignore exceptions that occur during reload */
    208        private boolean ignoreReloadExceptions = true;
    209
    210        /** Set to true when the backing file has changed */
    211        private boolean reloadRequired;
    212
    213        /**
    214         * An expression engine used for converting child configurations to
    215         * hierarchical ones.
    216         */
    217        private ExpressionEngine conversionExpressionEngine;
    218
    219        /** A flag whether an enhanced reload check is to be performed.*/
    220        private boolean forceReloadCheck;
    221
    222        /**
    223         * Creates a new instance of {@code CombinedConfiguration} and
    224         * initializes the combiner to be used.
    225         *
    226         * @param comb the node combiner (can be <b>null</b>, then a union combiner
    227         * is used as default)
    228         */
    229        public CombinedConfiguration(NodeCombiner comb)
    230        {
    231            setNodeCombiner((comb != null) ? comb : DEFAULT_COMBINER);
    232            clear();
    233        }
    234
    235        public CombinedConfiguration(NodeCombiner comb, Lock lock)
    236        {
    237            super(lock);
    238            setNodeCombiner((comb != null) ? comb : DEFAULT_COMBINER);
    239            clear();
    240        }
    241
    242        public CombinedConfiguration(Lock lock)
    243        {
    244            this(null, lock);
    245        }
    246
    247        /**
    248         * Creates a new instance of {@code CombinedConfiguration} that uses
    249         * a union combiner.
    250         *
    251         * @see org.apache.commons.configuration.tree.UnionCombiner
    252         */
    253        public CombinedConfiguration()
    254        {
    255            this(null, null);
    256        }
    257
    258        /**
    259         * Returns the node combiner that is used for creating the combined node
    260         * structure.
    261         *
    262         * @return the node combiner
    263         */
    264        public NodeCombiner getNodeCombiner()
    265        {
    266            return nodeCombiner;
    267        }
    268
    269        /**
    270         * Sets the node combiner. This object will be used when the combined node
    271         * structure is to be constructed. It must not be <b>null</b>, otherwise an
    272         * {@code IllegalArgumentException} exception is thrown. Changing the
    273         * node combiner causes an invalidation of this combined configuration, so
    274         * that the new combiner immediately takes effect.
    275         *
    276         * @param nodeCombiner the node combiner
    277         */
    278        public void setNodeCombiner(NodeCombiner nodeCombiner)
    279        {
    280            if (nodeCombiner == null)
    281            {
    282                throw new IllegalArgumentException(
    283                        "Node combiner must not be null!");
    284            }
    285            this.nodeCombiner = nodeCombiner;
    286            invalidate();
    287        }
    288
    289        /**
    290         * Returns a flag whether an enhanced reload check must be performed.
    291         *
    292         * @return the force reload check flag
    293         * @since 1.4
    294         */
    295        public boolean isForceReloadCheck()
    296        {
    297            return forceReloadCheck;
    298        }
    299
    300        /**
    301         * Sets the force reload check flag. If this flag is set, each property
    302         * access on this configuration will cause a reload check on the contained
    303         * configurations. This is a workaround for a problem with some reload
    304         * implementations that only check if a reload is required when they are
    305         * triggered. Per default this mode is disabled. If the force reload check
    306         * flag is set to <b>true</b>, accessing properties will be less
    307         * efficient, but reloads on contained configurations will be detected.
    308         *
    309         * @param forceReloadCheck the value of the flag
    310         * @since 1.4
    311         */
    312        public void setForceReloadCheck(boolean forceReloadCheck)
    313        {
    314            this.forceReloadCheck = forceReloadCheck;
    315        }
    316
    317        /**
    318         * Returns the {@code ExpressionEngine} for converting flat child
    319         * configurations to hierarchical ones.
    320         *
    321         * @return the conversion expression engine
    322         * @since 1.6
    323         */
    324        public ExpressionEngine getConversionExpressionEngine()
    325        {
    326            return conversionExpressionEngine;
    327        }
    328
    329        /**
    330         * Sets the {@code ExpressionEngine} for converting flat child
    331         * configurations to hierarchical ones. When constructing the root node for
    332         * this combined configuration the properties of all child configurations
    333         * must be combined to a single hierarchical node structure. In this
    334         * process, non hierarchical configurations are converted to hierarchical
    335         * ones first. This can be problematic if a child configuration contains
    336         * keys that are no compatible with the default expression engine used by
    337         * hierarchical configurations. Therefore it is possible to specify a
    338         * specific expression engine to be used for this purpose.
    339         *
    340         * @param conversionExpressionEngine the conversion expression engine
    341         * @see ConfigurationUtils#convertToHierarchical(Configuration, ExpressionEngine)
    342         * @since 1.6
    343         */
    344        public void setConversionExpressionEngine(
    345                ExpressionEngine conversionExpressionEngine)
    346        {
    347            this.conversionExpressionEngine = conversionExpressionEngine;
    348        }
    349
    350        /**
    351         * Retrieves the value of the ignoreReloadExceptions flag.
    352         * @return true if exceptions are ignored, false otherwise.
    353         */
    354        public boolean isIgnoreReloadExceptions()
    355        {
    356            return ignoreReloadExceptions;
    357        }
    358
    359        /**
    360         * If set to true then exceptions that occur during reloading will be
    361         * ignored. If false then the exceptions will be allowed to be thrown
    362         * back to the caller.
    363         * @param ignoreReloadExceptions true if exceptions should be ignored.
    364         */
    365        public void setIgnoreReloadExceptions(boolean ignoreReloadExceptions)
    366        {
    367            this.ignoreReloadExceptions = ignoreReloadExceptions;
    368        }
    369
    370        /**
    371         * Adds a new configuration to this combined configuration. It is possible
    372         * (but not mandatory) to give the new configuration a name. This name must
    373         * be unique, otherwise a {@code ConfigurationRuntimeException} will
    374         * be thrown. With the optional {@code at} argument you can specify
    375         * where in the resulting node structure the content of the added
    376         * configuration should appear. This is a string that uses dots as property
    377         * delimiters (independent on the current expression engine). For instance
    378         * if you pass in the string {@code "database.tables"},
    379         * all properties of the added configuration will occur in this branch.
    380         *
    381         * @param config the configuration to add (must not be <b>null</b>)
    382         * @param name the name of this configuration (can be <b>null</b>)
    383         * @param at the position of this configuration in the combined tree (can be
    384         * <b>null</b>)
    385         */
    386        public void addConfiguration(AbstractConfiguration config, String name,
    387                String at)
    388        {
    389            if (config == null)
    390            {
    391                throw new IllegalArgumentException(
    392                        "Added configuration must not be null!");
    393            }
    394            if (name != null && namedConfigurations.containsKey(name))
    395            {
    396                throw new ConfigurationRuntimeException(
    397                        "A configuration with the name '"
    398                                + name
    399                                + "' already exists in this combined configuration!");
    400            }
    401
    402            ConfigData cd = new ConfigData(config, name, at);
    403            if (getLogger().isDebugEnabled())
    404            {
    405                getLogger().debug("Adding configuration " + config + " with name " + name);
    406            }
    407            configurations.add(cd);
    408            if (name != null)
    409            {
    410                namedConfigurations.put(name, config);
    411            }
    412
    413            config.addConfigurationListener(this);
    414            invalidate();
    415        }
    416
    417        /**
    418         * Adds a new configuration to this combined configuration with an optional
    419         * name. The new configuration's properties will be added under the root of
    420         * the combined node structure.
    421         *
    422         * @param config the configuration to add (must not be <b>null</b>)
    423         * @param name the name of this configuration (can be <b>null</b>)
    424         */
    425        public void addConfiguration(AbstractConfiguration config, String name)
    426        {
    427            addConfiguration(config, name, null);
    428        }
    429
    430        /**
    431         * Adds a new configuration to this combined configuration. The new
    432         * configuration is not given a name. Its properties will be added under the
    433         * root of the combined node structure.
    434         *
    435         * @param config the configuration to add (must not be <b>null</b>)
    436         */
    437        public void addConfiguration(AbstractConfiguration config)
    438        {
    439            addConfiguration(config, null, null);
    440        }
    441
    442        /**
    443         * Returns the number of configurations that are contained in this combined
    444         * configuration.
    445         *
    446         * @return the number of contained configurations
    447         */
    448        public int getNumberOfConfigurations()
    449        {
    450            return configurations.size();
    451        }
    452
    453        /**
    454         * Returns the configuration at the specified index. The contained
    455         * configurations are numbered in the order they were added to this combined
    456         * configuration. The index of the first configuration is 0.
    457         *
    458         * @param index the index
    459         * @return the configuration at this index
    460         */
    461        public Configuration getConfiguration(int index)
    462        {
    463            ConfigData cd = configurations.get(index);
    464            return cd.getConfiguration();
    465        }
    466
    467        /**
    468         * Returns the configuration with the given name. This can be <b>null</b>
    469         * if no such configuration exists.
    470         *
    471         * @param name the name of the configuration
    472         * @return the configuration with this name
    473         */
    474        public Configuration getConfiguration(String name)
    475        {
    476            return namedConfigurations.get(name);
    477        }
    478
    479        /**
    480         * Returns a List of all the configurations that have been added.
    481         * @return A List of all the configurations.
    482         * @since 1.7
    483         */
    484        public List<AbstractConfiguration> getConfigurations()
    485        {
    486            List<AbstractConfiguration> list = new ArrayList<AbstractConfiguration>(configurations.size());
    487            for (ConfigData cd : configurations)
    488            {
    489                list.add(cd.getConfiguration());
    490            }
    491            return list;
    492        }
    493
    494        /**
    495         * Returns a List of the names of all the configurations that have been
    496         * added in the order they were added. A NULL value will be present in
    497         * the list for each configuration that was added without a name.
    498         * @return A List of all the configuration names.
    499         * @since 1.7
    500         */
    501        public List<String> getConfigurationNameList()
    502        {
    503            List<String> list = new ArrayList<String>(configurations.size());
    504            for (ConfigData cd : configurations)
    505            {
    506                list.add(cd.getName());
    507            }
    508            return list;
    509        }
    510
    511        /**
    512         * Removes the specified configuration from this combined configuration.
    513         *
    514         * @param config the configuration to be removed
    515         * @return a flag whether this configuration was found and could be removed
    516         */
    517        public boolean removeConfiguration(Configuration config)
    518        {
    519            for (int index = 0; index < getNumberOfConfigurations(); index++)
    520            {
    521                if (configurations.get(index).getConfiguration() == config)
    522                {
    523                    removeConfigurationAt(index);
    524                    return true;
    525                }
    526            }
    527
    528            return false;
    529        }
    530
    531        /**
    532         * Removes the configuration at the specified index.
    533         *
    534         * @param index the index
    535         * @return the removed configuration
    536         */
    537        public Configuration removeConfigurationAt(int index)
    538        {
    539            ConfigData cd = configurations.remove(index);
    540            if (cd.getName() != null)
    541            {
    542                namedConfigurations.remove(cd.getName());
    543            }
    544            cd.getConfiguration().removeConfigurationListener(this);
    545            invalidate();
    546            return cd.getConfiguration();
    547        }
    548
    549        /**
    550         * Removes the configuration with the specified name.
    551         *
    552         * @param name the name of the configuration to be removed
    553         * @return the removed configuration (<b>null</b> if this configuration
    554         * was not found)
    555         */
    556        public Configuration removeConfiguration(String name)
    557        {
    558            Configuration conf = getConfiguration(name);
    559            if (conf != null)
    560            {
    561                removeConfiguration(conf);
    562            }
    563            return conf;
    564        }
    565
    566        /**
    567         * Returns a set with the names of all configurations contained in this
    568         * combined configuration. Of course here are only these configurations
    569         * listed, for which a name was specified when they were added.
    570         *
    571         * @return a set with the names of the contained configurations (never
    572         * <b>null</b>)
    573         */
    574        public Set<String> getConfigurationNames()
    575        {
    576            return namedConfigurations.keySet();
    577        }
    578
    579        /**
    580         * Invalidates this combined configuration. This means that the next time a
    581         * property is accessed the combined node structure must be re-constructed.
    582         * Invalidation of a combined configuration also means that an event of type
    583         * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other
    584         * events most times appear twice (once before and once after an update),
    585         * this event is only fired once (after update).
    586         */
    587        public void invalidate()
    588        {
    589            reloadRequired = true;
    590            fireEvent(EVENT_COMBINED_INVALIDATE, null, null, false);
    591        }
    592
    593        /**
    594         * Event listener call back for configuration update events. This method is
    595         * called whenever one of the contained configurations was modified. It
    596         * invalidates this combined configuration.
    597         *
    598         * @param event the update event
    599         */
    600        public void configurationChanged(ConfigurationEvent event)
    601        {
    602            if (event.getType() == AbstractFileConfiguration.EVENT_CONFIG_CHANGED)
    603            {
    604                fireEvent(event.getType(), event.getPropertyName(), event.getPropertyValue(), event.isBeforeUpdate());
    605            }
    606            else if (!event.isBeforeUpdate())
    607            {
    608                invalidate();
    609            }
    610        }
    611
    612        /**
    613         * Returns the configuration root node of this combined configuration. This
    614         * method will construct a combined node structure using the current node
    615         * combiner if necessary.
    616         *
    617         * @return the combined root node
    618         */
    619        @Override
    620        public ConfigurationNode getRootNode()
    621        {
    622            synchronized (getReloadLock())
    623            {
    624                if (reloadRequired || combinedRoot == null)
    625                {
    626                    combinedRoot = constructCombinedNode();
    627                    reloadRequired = false;
    628                }
    629                return combinedRoot;
    630            }
    631        }
    632
    633        /**
    634         * Clears this configuration. All contained configurations will be removed.
    635         */
    636        @Override
    637        public void clear()
    638        {
    639            fireEvent(EVENT_CLEAR, null, null, true);
    640            configurations = new ArrayList<ConfigData>();
    641            namedConfigurations = new HashMap<String, AbstractConfiguration>();
    642            fireEvent(EVENT_CLEAR, null, null, false);
    643            invalidate();
    644        }
    645
    646        /**
    647         * Returns a copy of this object. This implementation performs a deep clone,
    648         * i.e. all contained configurations will be cloned, too. For this to work,
    649         * all contained configurations must be cloneable. Registered event
    650         * listeners won't be cloned. The clone will use the same node combiner than
    651         * the original.
    652         *
    653         * @return the copied object
    654         */
    655        @Override
    656        public Object clone()
    657        {
    658            CombinedConfiguration copy = (CombinedConfiguration) super.clone();
    659            copy.clear();
    660            for (ConfigData cd : configurations)
    661            {
    662                copy.addConfiguration((AbstractConfiguration) ConfigurationUtils
    663                        .cloneConfiguration(cd.getConfiguration()), cd.getName(),
    664                        cd.getAt());
    665            }
    666
    667            copy.setRootNode(new DefaultConfigurationNode());
    668            return copy;
    669        }
    670
    671        /**
    672         * Returns the configuration source, in which the specified key is defined.
    673         * This method will determine the configuration node that is identified by
    674         * the given key. The following constellations are possible:
    675         * <ul>
    676         * <li>If no node object is found for this key, <b>null</b> is returned.</li>
    677         * <li>If the key maps to multiple nodes belonging to different
    678         * configuration sources, a {@code IllegalArgumentException} is
    679         * thrown (in this case no unique source can be determined).</li>
    680         * <li>If exactly one node is found for the key, the (child) configuration
    681         * object, to which the node belongs is determined and returned.</li>
    682         * <li>For keys that have been added directly to this combined
    683         * configuration and that do not belong to the namespaces defined by
    684         * existing child configurations this configuration will be returned.</li>
    685         * </ul>
    686         *
    687         * @param key the key of a configuration property
    688         * @return the configuration, to which this property belongs or <b>null</b>
    689         * if the key cannot be resolved
    690         * @throws IllegalArgumentException if the key maps to multiple properties
    691         * and the source cannot be determined, or if the key is <b>null</b>
    692         * @since 1.5
    693         */
    694        public Configuration getSource(String key)
    695        {
    696            if (key == null)
    697            {
    698                throw new IllegalArgumentException("Key must not be null!");
    699            }
    700
    701            List<ConfigurationNode> nodes = fetchNodeList(key);
    702            if (nodes.isEmpty())
    703            {
    704                return null;
    705            }
    706
    707            Iterator<ConfigurationNode> it = nodes.iterator();
    708            Configuration source = findSourceConfiguration(it.next());
    709            while (it.hasNext())
    710            {
    711                Configuration src = findSourceConfiguration(it.next());
    712                if (src != source)
    713                {
    714                    throw new IllegalArgumentException("The key " + key
    715                            + " is defined by multiple sources!");
    716                }
    717            }
    718
    719            return source;
    720        }
    721
    722        /**
    723         * Evaluates the passed in property key and returns a list with the matching
    724         * configuration nodes. This implementation also evaluates the
    725         * <em>force reload check</em> flag. If it is set,
    726         * {@code performReloadCheck()} is invoked.
    727         *
    728         * @param key the property key
    729         * @return a list with the matching configuration nodes
    730         */
    731        @Override
    732        protected List<ConfigurationNode> fetchNodeList(String key)
    733        {
    734            if (isForceReloadCheck())
    735            {
    736                performReloadCheck();
    737            }
    738
    739            return super.fetchNodeList(key);
    740        }
    741
    742        /**
    743         * Triggers the contained configurations to perform a reload check if
    744         * necessary. This method is called when a property of this combined
    745         * configuration is accessed and the {@code forceReloadCheck} property
    746         * is set to <b>true</b>.
    747         *
    748         * @see #setForceReloadCheck(boolean)
    749         * @since 1.6
    750         */
    751        protected void performReloadCheck()
    752        {
    753            for (ConfigData cd : configurations)
    754            {
    755                try
    756                {
    757                    // simply retrieve a property; this is enough for
    758                    // triggering a reload
    759                    cd.getConfiguration().getProperty(PROP_RELOAD_CHECK);
    760                }
    761                catch (Exception ex)
    762                {
    763                    if (!ignoreReloadExceptions)
    764                    {
    765                        throw new ConfigurationRuntimeException(ex);
    766                    }
    767                }
    768            }
    769        }
    770
    771        /**
    772         * Creates the root node of this combined configuration.
    773         *
    774         * @return the combined root node
    775         */
    776        private ConfigurationNode constructCombinedNode()
    777        {
    778            if (getNumberOfConfigurations() < 1)
    779            {
    780                if (getLogger().isDebugEnabled())
    781                {
    782                    getLogger().debug("No configurations defined for " + this);
    783                }
    784                return new ViewNode();
    785            }
    786
    787            else
    788            {
    789                Iterator<ConfigData> it = configurations.iterator();
    790                ConfigurationNode node = it.next().getTransformedRoot();
    791                while (it.hasNext())
    792                {
    793                    node = getNodeCombiner().combine(node,
    794                            it.next().getTransformedRoot());
    795                }
    796                if (getLogger().isDebugEnabled())
    797                {
    798                    ByteArrayOutputStream os = new ByteArrayOutputStream();
    799                    PrintStream stream = new PrintStream(os);
    800                    TreeUtils.printTree(stream, node);
    801                    getLogger().debug(os.toString());
    802                }
    803                return node;
    804            }
    805        }
    806
    807        /**
    808         * Determines the configuration that owns the specified node.
    809         *
    810         * @param node the node
    811         * @return the owning configuration
    812         */
    813        private Configuration findSourceConfiguration(ConfigurationNode node)
    814        {
    815            synchronized (getReloadLock())
    816            {
    817                ConfigurationNode root = null;
    818                ConfigurationNode current = node;
    819
    820                // find the root node in this hierarchy
    821                while (current != null)
    822                {
    823                    root = current;
    824                    current = current.getParentNode();
    825                }
    826
    827                // Check with the root nodes of the child configurations
    828                for (ConfigData cd : configurations)
    829                {
    830                    if (root == cd.getRootNode())
    831                    {
    832                        return cd.getConfiguration();
    833                    }
    834                }
    835            }
    836
    837            return this;
    838        }
    839
    840        /**
    841         * An internal helper class for storing information about contained
    842         * configurations.
    843         */
    844        class ConfigData
    845        {
    846            /** Stores a reference to the configuration. */
    847            private AbstractConfiguration configuration;
    848
    849            /** Stores the name under which the configuration is stored. */
    850            private String name;
    851
    852            /** Stores the at information as path of nodes. */
    853            private Collection<String> atPath;
    854
    855            /** Stores the at string.*/
    856            private String at;
    857
    858            /** Stores the root node for this child configuration.*/
    859            private ConfigurationNode rootNode;
    860
    861            /**
    862             * Creates a new instance of {@code ConfigData} and initializes
    863             * it.
    864             *
    865             * @param config the configuration
    866             * @param n the name
    867             * @param at the at position
    868             */
    869            public ConfigData(AbstractConfiguration config, String n, String at)
    870            {
    871                configuration = config;
    872                name = n;
    873                atPath = parseAt(at);
    874                this.at = at;
    875            }
    876
    877            /**
    878             * Returns the stored configuration.
    879             *
    880             * @return the configuration
    881             */
    882            public AbstractConfiguration getConfiguration()
    883            {
    884                return configuration;
    885            }
    886
    887            /**
    888             * Returns the configuration's name.
    889             *
    890             * @return the name
    891             */
    892            public String getName()
    893            {
    894                return name;
    895            }
    896
    897            /**
    898             * Returns the at position of this configuration.
    899             *
    900             * @return the at position
    901             */
    902            public String getAt()
    903            {
    904                return at;
    905            }
    906
    907            /**
    908             * Returns the root node for this child configuration.
    909             *
    910             * @return the root node of this child configuration
    911             * @since 1.5
    912             */
    913            public ConfigurationNode getRootNode()
    914            {
    915                return rootNode;
    916            }
    917
    918            /**
    919             * Returns the transformed root node of the stored configuration. The
    920             * term &quot;transformed&quot; means that an eventually defined at path
    921             * has been applied.
    922             *
    923             * @return the transformed root node
    924             */
    925            public ConfigurationNode getTransformedRoot()
    926            {
    927                ViewNode result = new ViewNode();
    928                ViewNode atParent = result;
    929
    930                if (atPath != null)
    931                {
    932                    // Build the complete path
    933                    for (String p : atPath)
    934                    {
    935                        ViewNode node = new ViewNode();
    936                        node.setName(p);
    937                        atParent.addChild(node);
    938                        atParent = node;
    939                    }
    940                }
    941
    942                // Copy data of the root node to the new path
    943                ConfigurationNode root = ConfigurationUtils
    944                        .convertToHierarchical(getConfiguration(),
    945                                getConversionExpressionEngine()).getRootNode();
    946                atParent.appendChildren(root);
    947                atParent.appendAttributes(root);
    948                rootNode = root;
    949
    950                return result;
    951            }
    952
    953            /**
    954             * Splits the at path into its components.
    955             *
    956             * @param at the at string
    957             * @return a collection with the names of the single components
    958             */
    959            private Collection<String> parseAt(String at)
    960            {
    961                if (at == null)
    962                {
    963                    return null;
    964                }
    965
    966                Collection<String> result = new ArrayList<String>();
    967                DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
    968                        AT_ENGINE, at).iterator();
    969                while (it.hasNext())
    970                {
    971                    result.add(it.nextKey());
    972                }
    973                return result;
    974            }
    975        }
    976    }