View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration;
18  
19  import java.io.ByteArrayOutputStream;
20  import java.io.PrintStream;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import org.apache.commons.configuration.event.ConfigurationEvent;
30  import org.apache.commons.configuration.event.ConfigurationListener;
31  import org.apache.commons.configuration.tree.ConfigurationNode;
32  import org.apache.commons.configuration.tree.DefaultConfigurationKey;
33  import org.apache.commons.configuration.tree.DefaultConfigurationNode;
34  import org.apache.commons.configuration.tree.DefaultExpressionEngine;
35  import org.apache.commons.configuration.tree.ExpressionEngine;
36  import org.apache.commons.configuration.tree.NodeCombiner;
37  import org.apache.commons.configuration.tree.TreeUtils;
38  import org.apache.commons.configuration.tree.UnionCombiner;
39  import org.apache.commons.configuration.tree.ViewNode;
40  
41  /**
42   * <p>
43   * A hierarchical composite configuration class.
44   * </p>
45   * <p>
46   * This class maintains a list of configuration objects, which can be added
47   * using the divers {@code addConfiguration()} methods. After that the
48   * configurations can be accessed either by name (if one was provided when the
49   * configuration was added) or by index. For the whole set of managed
50   * configurations a logical node structure is constructed. For this purpose a
51   * {@link org.apache.commons.configuration.tree.NodeCombiner NodeCombiner}
52   * object can be set. This makes it possible to specify different algorithms for
53   * the combination process.
54   * </p>
55   * <p>
56   * The big advantage of this class is that it creates a truly hierarchical
57   * structure of all the properties stored in the contained configurations - even
58   * if some of them are no hierarchical configurations per se. So all enhanced
59   * features provided by a hierarchical configuration (e.g. choosing an
60   * expression engine) are applicable.
61   * </p>
62   * <p>
63   * The class works by registering itself as an event listener at all added
64   * configurations. So it gets notified whenever one of these configurations is
65   * changed and can invalidate its internal node structure. The next time a
66   * property is accessed the node structure will be re-constructed using the
67   * current state of the managed configurations. Note that, depending on the used
68   * {@code NodeCombiner}, this may be a complex operation.
69   * </p>
70   * <p>
71   * Because of the way a {@code CombinedConfiguration} is working it has
72   * more or less view character: it provides a logic view on the configurations
73   * it contains. In this constellation not all methods defined for hierarchical
74   * configurations - especially methods that update the stored properties - can
75   * be implemented in a consistent manner. Using such methods (like
76   * {@code addProperty()}, or {@code clearProperty()} on a
77   * {@code CombinedConfiguration} is not strictly forbidden, however,
78   * depending on the current {@link NodeCombiner} and the involved
79   * properties, the results may be different than expected. Some examples may
80   * illustrate this:
81   * </p>
82   * <p>
83   * <ul>
84   * <li>Imagine a {@code CombinedConfiguration} <em>cc</em> containing
85   * two child configurations with the following content:
86   * <dl>
87   * <dt>user.properties</dt>
88   * <dd>
89   *
90   * <pre>
91   * gui.background = blue
92   * gui.position = (10, 10, 400, 200)
93   * </pre>
94   *
95   * </dd>
96   * <dt>default.properties</dt>
97   * <dd>
98   *
99   * <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 }