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    }