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