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 }