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.configuration;
018
019import java.io.ByteArrayOutputStream;
020import java.io.PrintStream;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028
029import org.apache.commons.configuration.event.ConfigurationEvent;
030import org.apache.commons.configuration.event.ConfigurationListener;
031import org.apache.commons.configuration.tree.ConfigurationNode;
032import org.apache.commons.configuration.tree.DefaultConfigurationKey;
033import org.apache.commons.configuration.tree.DefaultConfigurationNode;
034import org.apache.commons.configuration.tree.DefaultExpressionEngine;
035import org.apache.commons.configuration.tree.ExpressionEngine;
036import org.apache.commons.configuration.tree.NodeCombiner;
037import org.apache.commons.configuration.tree.TreeUtils;
038import org.apache.commons.configuration.tree.UnionCombiner;
039import 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 */
172public 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}