    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()</code> 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     * <code>{@link org.apache.commons.configuration.tree.NodeCombiner NodeCombiner}</code>
    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</code>, this may be a complex operation.
    069     * </p>
    070     * <p>
    071     * Because of the way a <code>CombinedConfiguration</code> 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()</code>, or <code>clearProperty()</code> on a
    077     * <code>CombinedConfiguration</code> is not strictly forbidden, however,
    078     * depending on the current <code>{@link NodeCombiner}</code> 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</code> <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</code> a
    108     * <code>{@link org.apache.commons.configuration.tree.OverrideCombiner OverrideCombiner}</code>
    109     * is used. This combiner will ensure that defined user settings take precedence
    110     * over the default values. If the resulting <code>CombinedConfiguration</code>
    111     * is queried for the background color, <code>blue</code> will be returned
    112     * because this value is defined in <code>user.properties</code>. Now
    113     * consider what happens if the key <code>gui.background</code> is removed
    114     * from the <code>CombinedConfiguration</code>:
    115     *
    116     * <pre>cc.clearProperty("gui.background");</pre>
    117     *
    118     * Will a <code>cc.containsKey("gui.background")</code> now return <b>false</b>?
    119     * No, it won't! The <code>clearProperty()</code> 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</code> to be re-constructed. This
    125     * time the <code>OverrideCombiner</code> cannot find a
    126     * <code>gui.background</code> 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()</code> 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</code> 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");</code> will alter the second
    145     * child configuration - because the prefix <em>home</em> is here already
    146     * present; when the <code>CombinedConfiguration</code> 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</code> becomes
    154     * invalid (either because one of the contained configurations was modified or
    155     * because the <code>invalidate()</code> 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 1158115 2011-08-16 06:04:32Z 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 configurations;
    203
    204        /** Stores a map with the named configurations. */
    205        private Map 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</code> 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</code> 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</code> 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         * performant, 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</code> 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</code> 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</code> will
    374         * be thrown. With the optional <code>at</code> 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>&quot;database.tables&quot;</code>,
    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 = (ConfigData) 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 (Configuration) 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 getConfigurations()
    485        {
    486            List list = new ArrayList();
    487            Iterator iter = configurations.iterator();
    488            while (iter.hasNext())
    489            {
    490                list.add(((ConfigData) iter.next()).getConfiguration());
    491            }
    492            return list;
    493        }
    494
    495        /**
    496         * Returns a List of the names of all the configurations that have been
    497         * added in the order they were added. A NULL value will be present in
    498         * the list for each configuration that was added without a name.
    499         * @return A List of all the configuration names.
    500         * @since 1.7
    501         */
    502        public List getConfigurationNameList()
    503        {
    504            List list = new ArrayList();
    505            Iterator iter = configurations.iterator();
    506            while (iter.hasNext())
    507            {
    508                list.add(((ConfigData) iter.next()).getName());
    509            }
    510            return list;
    511        }
    512
    513        /**
    514         * Removes the specified configuration from this combined configuration.
    515         *
    516         * @param config the configuration to be removed
    517         * @return a flag whether this configuration was found and could be removed
    518         */
    519        public boolean removeConfiguration(Configuration config)
    520        {
    521            for (int index = 0; index < getNumberOfConfigurations(); index++)
    522            {
    523                if (((ConfigData) configurations.get(index)).getConfiguration() == config)
    524                {
    525                    removeConfigurationAt(index);
    526                    return true;
    527                }
    528            }
    529
    530            return false;
    531        }
    532
    533        /**
    534         * Removes the configuration at the specified index.
    535         *
    536         * @param index the index
    537         * @return the removed configuration
    538         */
    539        public Configuration removeConfigurationAt(int index)
    540        {
    541            ConfigData cd = (ConfigData) configurations.remove(index);
    542            if (cd.getName() != null)
    543            {
    544                namedConfigurations.remove(cd.getName());
    545            }
    546            cd.getConfiguration().removeConfigurationListener(this);
    547            invalidate();
    548            return cd.getConfiguration();
    549        }
    550
    551        /**
    552         * Removes the configuration with the specified name.
    553         *
    554         * @param name the name of the configuration to be removed
    555         * @return the removed configuration (<b>null</b> if this configuration
    556         * was not found)
    557         */
    558        public Configuration removeConfiguration(String name)
    559        {
    560            Configuration conf = getConfiguration(name);
    561            if (conf != null)
    562            {
    563                removeConfiguration(conf);
    564            }
    565            return conf;
    566        }
    567
    568        /**
    569         * Returns a set with the names of all configurations contained in this
    570         * combined configuration. Of course here are only these configurations
    571         * listed, for which a name was specified when they were added.
    572         *
    573         * @return a set with the names of the contained configurations (never
    574         * <b>null</b>)
    575         */
    576        public Set getConfigurationNames()
    577        {
    578            return namedConfigurations.keySet();
    579        }
    580
    581        /**
    582         * Invalidates this combined configuration. This means that the next time a
    583         * property is accessed the combined node structure must be re-constructed.
    584         * Invalidation of a combined configuration also means that an event of type
    585         * <code>EVENT_COMBINED_INVALIDATE</code> is fired. Note that while other
    586         * events most times appear twice (once before and once after an update),
    587         * this event is only fired once (after update).
    588         */
    589        public void invalidate()
    590        {
    591            reloadRequired = true;
    592            fireEvent(EVENT_COMBINED_INVALIDATE, null, null, false);
    593        }
    594
    595        /**
    596         * Event listener call back for configuration update events. This method is
    597         * called whenever one of the contained configurations was modified. It
    598         * invalidates this combined configuration.
    599         *
    600         * @param event the update event
    601         */
    602        public void configurationChanged(ConfigurationEvent event)
    603        {
    604            if (event.getType() == AbstractFileConfiguration.EVENT_CONFIG_CHANGED)
    605            {
    606                fireEvent(event.getType(), event.getPropertyName(), event.getPropertyValue(), event.isBeforeUpdate());
    607            }
    608            else if (!event.isBeforeUpdate())
    609            {
    610                invalidate();
    611            }
    612        }
    613
    614        /**
    615         * Returns the configuration root node of this combined configuration. This
    616         * method will construct a combined node structure using the current node
    617         * combiner if necessary.
    618         *
    619         * @return the combined root node
    620         */
    621        public ConfigurationNode getRootNode()
    622        {
    623            synchronized (getReloadLock())
    624            {
    625                if (reloadRequired || combinedRoot == null)
    626                {
    627                    combinedRoot = constructCombinedNode();
    628                    reloadRequired = false;
    629                }
    630                return combinedRoot;
    631            }
    632        }
    633
    634        /**
    635         * Clears this configuration. All contained configurations will be removed.
    636         */
    637        public void clear()
    638        {
    639            fireEvent(EVENT_CLEAR, null, null, true);
    640            configurations = new ArrayList();
    641            namedConfigurations = new HashMap();
    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        public Object clone()
    656        {
    657            CombinedConfiguration copy = (CombinedConfiguration) super.clone();
    658            copy.clear();
    659            for (Iterator it = configurations.iterator(); it.hasNext();)
    660            {
    661                ConfigData cd = (ConfigData) it.next();
    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</code> 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 nodes = fetchNodeList(key);
    702            if (nodes.isEmpty())
    703            {
    704                return null;
    705            }
    706
    707            Iterator it = nodes.iterator();
    708            Configuration source = findSourceConfiguration((ConfigurationNode) it
    709                    .next());
    710            while (it.hasNext())
    711            {
    712                Configuration src = findSourceConfiguration((ConfigurationNode) it
    713                        .next());
    714                if (src != source)
    715                {
    716                    throw new IllegalArgumentException("The key " + key
    717                            + " is defined by multiple sources!");
    718                }
    719            }
    720
    721            return source;
    722        }
    723
    724        /**
    725         * Evaluates the passed in property key and returns a list with the matching
    726         * configuration nodes. This implementation also evaluates the
    727         * <em>force reload check</em> flag. If it is set,
    728         * <code>performReloadCheck()</code> is invoked.
    729         *
    730         * @param key the property key
    731         * @return a list with the matching configuration nodes
    732         */
    733        protected List fetchNodeList(String key)
    734        {
    735            if (isForceReloadCheck())
    736            {
    737                performReloadCheck();
    738            }
    739
    740            return super.fetchNodeList(key);
    741        }
    742
    743        /**
    744         * Triggers the contained configurations to perform a reload check if
    745         * necessary. This method is called when a property of this combined
    746         * configuration is accessed and the <code>forceReloadCheck</code> property
    747         * is set to <b>true</b>.
    748         *
    749         * @see #setForceReloadCheck(boolean)
    750         * @since 1.6
    751         */
    752        protected void performReloadCheck()
    753        {
    754            for (Iterator it = configurations.iterator(); it.hasNext();)
    755            {
    756                try
    757                {
    758                    // simply retrieve a property; this is enough for
    759                    // triggering a reload
    760                    ((ConfigData) it.next()).getConfiguration().getProperty(
    761                            PROP_RELOAD_CHECK);
    762                }
    763                catch (Exception ex)
    764                {
    765                    if (!ignoreReloadExceptions)
    766                    {
    767                        throw new ConfigurationRuntimeException(ex);
    768                    }
    769                }
    770            }
    771        }
    772
    773        /**
    774         * Creates the root node of this combined configuration.
    775         *
    776         * @return the combined root node
    777         */
    778        private ConfigurationNode constructCombinedNode()
    779        {
    780            if (getNumberOfConfigurations() < 1)
    781            {
    782                if (getLogger().isDebugEnabled())
    783                {
    784                    getLogger().debug("No configurations defined for " + this);
    785                }
    786                return new ViewNode();
    787            }
    788
    789            else
    790            {
    791                Iterator it = configurations.iterator();
    792                ConfigurationNode node = ((ConfigData) it.next())
    793                        .getTransformedRoot();
    794                while (it.hasNext())
    795                {
    796                    node = getNodeCombiner().combine(node,
    797                            ((ConfigData) it.next()).getTransformedRoot());
    798                }
    799                if (getLogger().isDebugEnabled())
    800                {
    801                    ByteArrayOutputStream os = new ByteArrayOutputStream();
    802                    PrintStream stream = new PrintStream(os);
    803                    TreeUtils.printTree(stream, node);
    804                    getLogger().debug(os.toString());
    805                }
    806                return node;
    807            }
    808        }
    809
    810        /**
    811         * Determines the configuration that owns the specified node.
    812         *
    813         * @param node the node
    814         * @return the owning configuration
    815         */
    816        private Configuration findSourceConfiguration(ConfigurationNode node)
    817        {
    818            synchronized (getReloadLock())
    819            {
    820                ConfigurationNode root = null;
    821                ConfigurationNode current = node;
    822
    823                // find the root node in this hierarchy
    824                while (current != null)
    825                {
    826                    root = current;
    827                    current = current.getParentNode();
    828                }
    829
    830                // Check with the root nodes of the child configurations
    831                for (Iterator it = configurations.iterator(); it.hasNext();)
    832                {
    833                    ConfigData cd = (ConfigData) it.next();
    834                    if (root == cd.getRootNode())
    835                    {
    836                        return cd.getConfiguration();
    837                    }
    838                }
    839            }
    840
    841            return this;
    842        }
    843
    844        /**
    845         * An internal helper class for storing information about contained
    846         * configurations.
    847         */
    848        class ConfigData
    849        {
    850            /** Stores a reference to the configuration. */
    851            private AbstractConfiguration configuration;
    852
    853            /** Stores the name under which the configuration is stored. */
    854            private String name;
    855
    856            /** Stores the at information as path of nodes. */
    857            private Collection atPath;
    858
    859            /** Stores the at string.*/
    860            private String at;
    861
    862            /** Stores the root node for this child configuration.*/
    863            private ConfigurationNode rootNode;
    864
    865            /**
    866             * Creates a new instance of <code>ConfigData</code> and initializes
    867             * it.
    868             *
    869             * @param config the configuration
    870             * @param n the name
    871             * @param at the at position
    872             */
    873            public ConfigData(AbstractConfiguration config, String n, String at)
    874            {
    875                configuration = config;
    876                name = n;
    877                atPath = parseAt(at);
    878                this.at = at;
    879            }
    880
    881            /**
    882             * Returns the stored configuration.
    883             *
    884             * @return the configuration
    885             */
    886            public AbstractConfiguration getConfiguration()
    887            {
    888                return configuration;
    889            }
    890
    891            /**
    892             * Returns the configuration's name.
    893             *
    894             * @return the name
    895             */
    896            public String getName()
    897            {
    898                return name;
    899            }
    900
    901            /**
    902             * Returns the at position of this configuration.
    903             *
    904             * @return the at position
    905             */
    906            public String getAt()
    907            {
    908                return at;
    909            }
    910
    911            /**
    912             * Returns the root node for this child configuration.
    913             *
    914             * @return the root node of this child configuration
    915             * @since 1.5
    916             */
    917            public ConfigurationNode getRootNode()
    918            {
    919                return rootNode;
    920            }
    921
    922            /**
    923             * Returns the transformed root node of the stored configuration. The
    924             * term &quot;transformed&quot; means that an eventually defined at path
    925             * has been applied.
    926             *
    927             * @return the transformed root node
    928             */
    929            public ConfigurationNode getTransformedRoot()
    930            {
    931                ViewNode result = new ViewNode();
    932                ViewNode atParent = result;
    933
    934                if (atPath != null)
    935                {
    936                    // Build the complete path
    937                    for (Iterator it = atPath.iterator(); it.hasNext();)
    938                    {
    939                        ViewNode node = new ViewNode();
    940                        node.setName((String) it.next());
    941                        atParent.addChild(node);
    942                        atParent = node;
    943                    }
    944                }
    945
    946                // Copy data of the root node to the new path
    947                ConfigurationNode root = ConfigurationUtils
    948                        .convertToHierarchical(getConfiguration(),
    949                                getConversionExpressionEngine()).getRootNode();
    950                atParent.appendChildren(root);
    951                atParent.appendAttributes(root);
    952                rootNode = root;
    953
    954                return result;
    955            }
    956
    957            /**
    958             * Splits the at path into its components.
    959             *
    960             * @param at the at string
    961             * @return a collection with the names of the single components
    962             */
    963            private Collection parseAt(String at)
    964            {
    965                if (at == null)
    966                {
    967                    return null;
    968                }
    969
    970                Collection result = new ArrayList();
    971                DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(
    972                        AT_ENGINE, at).iterator();
    973                while (it.hasNext())
    974                {
    975                    result.add(it.nextKey());
    976                }
    977                return result;
    978            }
    979        }
    980    }