001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.configuration2; 018 019import java.io.ByteArrayOutputStream; 020import java.io.PrintStream; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.Iterator; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029 030import org.apache.commons.configuration2.event.ConfigurationEvent; 031import org.apache.commons.configuration2.event.EventListener; 032import org.apache.commons.configuration2.event.EventSource; 033import org.apache.commons.configuration2.event.EventType; 034import org.apache.commons.configuration2.ex.ConfigurationRuntimeException; 035import org.apache.commons.configuration2.sync.LockMode; 036import org.apache.commons.configuration2.tree.DefaultConfigurationKey; 037import org.apache.commons.configuration2.tree.DefaultExpressionEngine; 038import org.apache.commons.configuration2.tree.ExpressionEngine; 039import org.apache.commons.configuration2.tree.ImmutableNode; 040import org.apache.commons.configuration2.tree.NodeCombiner; 041import org.apache.commons.configuration2.tree.NodeTreeWalker; 042import org.apache.commons.configuration2.tree.QueryResult; 043import org.apache.commons.configuration2.tree.TreeUtils; 044import org.apache.commons.configuration2.tree.UnionCombiner; 045 046/** 047 * <p> 048 * A hierarchical composite configuration class. 049 * </p> 050 * <p> 051 * This class maintains a list of configuration objects, which can be added 052 * using the diverse {@code addConfiguration()} methods. After that the 053 * configurations can be accessed either by name (if one was provided when the 054 * configuration was added) or by index. For the whole set of managed 055 * configurations a logical node structure is constructed. For this purpose a 056 * {@link org.apache.commons.configuration2.tree.NodeCombiner NodeCombiner} 057 * object can be set. This makes it possible to specify different algorithms for 058 * the combination process. 059 * </p> 060 * <p> 061 * The big advantage of this class is that it creates a truly hierarchical 062 * structure of all the properties stored in the contained configurations - even 063 * if some of them are no hierarchical configurations per se. So all enhanced 064 * features provided by a hierarchical configuration (e.g. choosing an 065 * expression engine) are applicable. 066 * </p> 067 * <p> 068 * The class works by registering itself as an event listener at all added 069 * configurations. So it gets notified whenever one of these configurations is 070 * changed and can invalidate its internal node structure. The next time a 071 * property is accessed the node structure will be re-constructed using the 072 * current state of the managed configurations. Note that, depending on the used 073 * {@code NodeCombiner}, this may be a complex operation. 074 * </p> 075 * <p> 076 * Because of the way a {@code CombinedConfiguration} is working it has more or 077 * less view character: it provides a logic view on the configurations it 078 * contains. In this constellation not all methods defined for hierarchical 079 * configurations - especially methods that update the stored properties - can 080 * be implemented in a consistent manner. Using such methods (like 081 * {@code addProperty()}, or {@code clearProperty()} on a 082 * {@code CombinedConfiguration} is not strictly forbidden, however, depending 083 * on the current {@link NodeCombiner} and the involved properties, the results 084 * may be different than expected. Some examples may illustrate this: 085 * </p> 086 * <ul> 087 * <li>Imagine a {@code CombinedConfiguration} <em>cc</em> containing two child 088 * configurations with the following content: 089 * <dl> 090 * <dt>user.properties</dt> 091 * <dd> 092 * 093 * <pre> 094 * gui.background = blue 095 * gui.position = (10, 10, 400, 200) 096 * </pre> 097 * 098 * </dd> 099 * <dt>default.properties</dt> 100 * <dd> 101 * 102 * <pre> 103 * gui.background = black 104 * gui.foreground = white 105 * home.dir = /data 106 * </pre> 107 * 108 * </dd> 109 * </dl> 110 * As a {@code NodeCombiner} a 111 * {@link org.apache.commons.configuration2.tree.OverrideCombiner 112 * OverrideCombiner} is used. This combiner will ensure that defined user 113 * settings take precedence over the default values. If the resulting 114 * {@code CombinedConfiguration} is queried for the background color, 115 * {@code blue} will be returned because this value is defined in 116 * {@code user.properties}. Now consider what happens if the key 117 * {@code gui.background} is removed from the {@code CombinedConfiguration}: 118 * 119 * <pre> 120 * cc.clearProperty("gui.background"); 121 * </pre> 122 * 123 * Will a {@code cc.containsKey("gui.background")} now return <b>false</b>? No, 124 * it won't! The {@code clearProperty()} operation is executed on the node set 125 * of the combined configuration, which was constructed from the nodes of the 126 * two child configurations. It causes the value of the <em>background</em> node 127 * to be cleared, which is also part of the first child configuration. This 128 * modification of one of its child configurations causes the 129 * {@code CombinedConfiguration} to be re-constructed. This time the 130 * {@code OverrideCombiner} cannot find a {@code gui.background} property in the 131 * first child configuration, but it finds one in the second, and adds it to the 132 * resulting combined configuration. So the property is still present (with a 133 * different value now).</li> 134 * <li>{@code addProperty()} can also be problematic: Most node combiners use 135 * special view nodes for linking parts of the original configurations' data 136 * together. If new properties are added to such a special node, they do not 137 * belong to any of the managed configurations and thus hang in the air. Using 138 * the same configurations as in the last example, the statement 139 * 140 * <pre> 141 * addProperty("database.user", "scott"); 142 * </pre> 143 * 144 * would cause such a hanging property. If now one of the child configurations 145 * is changed and the {@code CombinedConfiguration} is re-constructed, this 146 * property will disappear! (Add operations are not problematic if they result 147 * in a child configuration being updated. For instance an 148 * {@code addProperty("home.url", "localhost");} will alter the second child 149 * configuration - because the prefix <em>home</em> is here already present; 150 * when the {@code CombinedConfiguration} is re-constructed, this change is 151 * taken into account.)</li> 152 * </ul> 153 * <p> 154 * Because of such problems it is recommended to perform updates only on the 155 * managed child configurations. 156 * </p> 157 * <p> 158 * Whenever the node structure of a {@code CombinedConfiguration} becomes 159 * invalid (either because one of the contained configurations was modified or 160 * because the {@code invalidate()} method was directly called) an event is 161 * generated. So this can be detected by interested event listeners. This also 162 * makes it possible to add a combined configuration into another one. 163 * </p> 164 * <p> 165 * Notes about thread-safety: This configuration implementation uses a 166 * {@code Synchronizer} object to protect instances against concurrent access. 167 * The concrete {@code Synchronizer} implementation used determines whether an 168 * instance of this class is thread-safe or not. In contrast to other 169 * implementations derived from {@link BaseHierarchicalConfiguration}, 170 * thread-safety is an issue here because the nodes structure used by this 171 * configuration has to be constructed dynamically when a child configuration is 172 * changed. Therefore, when multiple threads are involved which also manipulate 173 * one of the child configurations, a proper {@code Synchronizer} object should 174 * be set. Note that the {@code Synchronizer} objects used by the child 175 * configurations do not really matter. Because immutable in-memory nodes 176 * structures are used for them there is no danger that updates on child 177 * configurations could interfere with read operations on the combined 178 * configuration. 179 * </p> 180 * 181 * @since 1.3 182 */ 183public class CombinedConfiguration extends BaseHierarchicalConfiguration implements 184 EventListener<ConfigurationEvent> 185{ 186 /** 187 * Constant for the event type fired when the internal node structure of a 188 * combined configuration becomes invalid. 189 * 190 * @since 2.0 191 */ 192 public static final EventType<ConfigurationEvent> COMBINED_INVALIDATE = 193 new EventType<>(ConfigurationEvent.ANY, 194 "COMBINED_INVALIDATE"); 195 196 /** Constant for the expression engine for parsing the at path. */ 197 private static final DefaultExpressionEngine AT_ENGINE = DefaultExpressionEngine.INSTANCE; 198 199 /** Constant for the default node combiner. */ 200 private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner(); 201 202 /** Constant for a root node for an empty configuration. */ 203 private static final ImmutableNode EMPTY_ROOT = new ImmutableNode.Builder() 204 .create(); 205 206 /** Stores the combiner. */ 207 private NodeCombiner nodeCombiner; 208 209 /** Stores a list with the contained configurations. */ 210 private List<ConfigData> configurations; 211 212 /** Stores a map with the named configurations. */ 213 private Map<String, Configuration> namedConfigurations; 214 215 /** 216 * An expression engine used for converting child configurations to 217 * hierarchical ones. 218 */ 219 private ExpressionEngine conversionExpressionEngine; 220 221 /** A flag whether this configuration is up-to-date. */ 222 private boolean upToDate; 223 224 /** 225 * Creates a new instance of {@code CombinedConfiguration} and 226 * initializes the combiner to be used. 227 * 228 * @param comb the node combiner (can be <b>null</b>, then a union combiner 229 * is used as default) 230 */ 231 public CombinedConfiguration(final NodeCombiner comb) 232 { 233 nodeCombiner = comb != null ? comb : DEFAULT_COMBINER; 234 initChildCollections(); 235 } 236 237 /** 238 * Creates a new instance of {@code CombinedConfiguration} that uses 239 * a union combiner. 240 * 241 * @see org.apache.commons.configuration2.tree.UnionCombiner 242 */ 243 public CombinedConfiguration() 244 { 245 this(null); 246 } 247 248 /** 249 * Returns the node combiner that is used for creating the combined node 250 * structure. 251 * 252 * @return the node combiner 253 */ 254 public NodeCombiner getNodeCombiner() 255 { 256 beginRead(true); 257 try 258 { 259 return nodeCombiner; 260 } 261 finally 262 { 263 endRead(); 264 } 265 } 266 267 /** 268 * Sets the node combiner. This object will be used when the combined node 269 * structure is to be constructed. It must not be <b>null</b>, otherwise an 270 * {@code IllegalArgumentException} exception is thrown. Changing the 271 * node combiner causes an invalidation of this combined configuration, so 272 * that the new combiner immediately takes effect. 273 * 274 * @param nodeCombiner the node combiner 275 */ 276 public void setNodeCombiner(final NodeCombiner nodeCombiner) 277 { 278 if (nodeCombiner == null) 279 { 280 throw new IllegalArgumentException( 281 "Node combiner must not be null!"); 282 } 283 284 beginWrite(true); 285 try 286 { 287 this.nodeCombiner = nodeCombiner; 288 invalidateInternal(); 289 } 290 finally 291 { 292 endWrite(); 293 } 294 } 295 296 /** 297 * Returns the {@code ExpressionEngine} for converting flat child 298 * configurations to hierarchical ones. 299 * 300 * @return the conversion expression engine 301 * @since 1.6 302 */ 303 public ExpressionEngine getConversionExpressionEngine() 304 { 305 beginRead(true); 306 try 307 { 308 return conversionExpressionEngine; 309 } 310 finally 311 { 312 endRead(); 313 } 314 } 315 316 /** 317 * Sets the {@code ExpressionEngine} for converting flat child 318 * configurations to hierarchical ones. When constructing the root node for 319 * this combined configuration the properties of all child configurations 320 * must be combined to a single hierarchical node structure. In this 321 * process, non hierarchical configurations are converted to hierarchical 322 * ones first. This can be problematic if a child configuration contains 323 * keys that are no compatible with the default expression engine used by 324 * hierarchical configurations. Therefore it is possible to specify a 325 * specific expression engine to be used for this purpose. 326 * 327 * @param conversionExpressionEngine the conversion expression engine 328 * @see ConfigurationUtils#convertToHierarchical(Configuration, ExpressionEngine) 329 * @since 1.6 330 */ 331 public void setConversionExpressionEngine( 332 final ExpressionEngine conversionExpressionEngine) 333 { 334 beginWrite(true); 335 try 336 { 337 this.conversionExpressionEngine = conversionExpressionEngine; 338 } 339 finally 340 { 341 endWrite(); 342 } 343 } 344 345 /** 346 * Adds a new configuration to this combined configuration. It is possible 347 * (but not mandatory) to give the new configuration a name. This name must 348 * be unique, otherwise a {@code ConfigurationRuntimeException} will 349 * be thrown. With the optional {@code at} argument you can specify 350 * where in the resulting node structure the content of the added 351 * configuration should appear. This is a string that uses dots as property 352 * delimiters (independent on the current expression engine). For instance 353 * if you pass in the string {@code "database.tables"}, 354 * all properties of the added configuration will occur in this branch. 355 * 356 * @param config the configuration to add (must not be <b>null</b>) 357 * @param name the name of this configuration (can be <b>null</b>) 358 * @param at the position of this configuration in the combined tree (can be 359 * <b>null</b>) 360 */ 361 public void addConfiguration(final Configuration config, final String name, 362 final String at) 363 { 364 if (config == null) 365 { 366 throw new IllegalArgumentException( 367 "Added configuration must not be null!"); 368 } 369 370 beginWrite(true); 371 try 372 { 373 if (name != null && namedConfigurations.containsKey(name)) 374 { 375 throw new ConfigurationRuntimeException( 376 "A configuration with the name '" 377 + name 378 + "' already exists in this combined configuration!"); 379 } 380 381 final ConfigData cd = new ConfigData(config, name, at); 382 if (getLogger().isDebugEnabled()) 383 { 384 getLogger() 385 .debug("Adding configuration " + config + " with name " 386 + name); 387 } 388 configurations.add(cd); 389 if (name != null) 390 { 391 namedConfigurations.put(name, config); 392 } 393 394 invalidateInternal(); 395 } 396 finally 397 { 398 endWrite(); 399 } 400 registerListenerAt(config); 401 } 402 403 /** 404 * Adds a new configuration to this combined configuration with an optional 405 * name. The new configuration's properties will be added under the root of 406 * the combined node structure. 407 * 408 * @param config the configuration to add (must not be <b>null</b>) 409 * @param name the name of this configuration (can be <b>null</b>) 410 */ 411 public void addConfiguration(final Configuration config, final String name) 412 { 413 addConfiguration(config, name, null); 414 } 415 416 /** 417 * Adds a new configuration to this combined configuration. The new 418 * configuration is not given a name. Its properties will be added under the 419 * root of the combined node structure. 420 * 421 * @param config the configuration to add (must not be <b>null</b>) 422 */ 423 public void addConfiguration(final Configuration config) 424 { 425 addConfiguration(config, null, null); 426 } 427 428 /** 429 * Returns the number of configurations that are contained in this combined 430 * configuration. 431 * 432 * @return the number of contained configurations 433 */ 434 public int getNumberOfConfigurations() 435 { 436 beginRead(true); 437 try 438 { 439 return getNumberOfConfigurationsInternal(); 440 } 441 finally 442 { 443 endRead(); 444 } 445 } 446 447 /** 448 * Returns the configuration at the specified index. The contained 449 * configurations are numbered in the order they were added to this combined 450 * configuration. The index of the first configuration is 0. 451 * 452 * @param index the index 453 * @return the configuration at this index 454 */ 455 public Configuration getConfiguration(final int index) 456 { 457 beginRead(true); 458 try 459 { 460 final ConfigData cd = configurations.get(index); 461 return cd.getConfiguration(); 462 } 463 finally 464 { 465 endRead(); 466 } 467 } 468 469 /** 470 * Returns the configuration with the given name. This can be <b>null</b> 471 * if no such configuration exists. 472 * 473 * @param name the name of the configuration 474 * @return the configuration with this name 475 */ 476 public Configuration getConfiguration(final String name) 477 { 478 beginRead(true); 479 try 480 { 481 return namedConfigurations.get(name); 482 } 483 finally 484 { 485 endRead(); 486 } 487 } 488 489 /** 490 * Returns a List of all the configurations that have been added. 491 * @return A List of all the configurations. 492 * @since 1.7 493 */ 494 public List<Configuration> getConfigurations() 495 { 496 beginRead(true); 497 try 498 { 499 final List<Configuration> list = 500 new ArrayList<>(getNumberOfConfigurationsInternal()); 501 for (final ConfigData cd : configurations) 502 { 503 list.add(cd.getConfiguration()); 504 } 505 return list; 506 } 507 finally 508 { 509 endRead(); 510 } 511 } 512 513 /** 514 * Returns a List of the names of all the configurations that have been 515 * added in the order they were added. A NULL value will be present in 516 * the list for each configuration that was added without a name. 517 * @return A List of all the configuration names. 518 * @since 1.7 519 */ 520 public List<String> getConfigurationNameList() 521 { 522 beginRead(true); 523 try 524 { 525 final List<String> list = new ArrayList<>(getNumberOfConfigurationsInternal()); 526 for (final ConfigData cd : configurations) 527 { 528 list.add(cd.getName()); 529 } 530 return list; 531 } 532 finally 533 { 534 endRead(); 535 } 536 } 537 538 /** 539 * Removes the specified configuration from this combined configuration. 540 * 541 * @param config the configuration to be removed 542 * @return a flag whether this configuration was found and could be removed 543 */ 544 public boolean removeConfiguration(final Configuration config) 545 { 546 for (int index = 0; index < getNumberOfConfigurations(); index++) 547 { 548 if (configurations.get(index).getConfiguration() == config) 549 { 550 removeConfigurationAt(index); 551 return true; 552 } 553 } 554 555 return false; 556 } 557 558 /** 559 * Removes the configuration at the specified index. 560 * 561 * @param index the index 562 * @return the removed configuration 563 */ 564 public Configuration removeConfigurationAt(final int index) 565 { 566 final ConfigData cd = configurations.remove(index); 567 if (cd.getName() != null) 568 { 569 namedConfigurations.remove(cd.getName()); 570 } 571 unregisterListenerAt(cd.getConfiguration()); 572 invalidateInternal(); 573 return cd.getConfiguration(); 574 } 575 576 /** 577 * Removes the configuration with the specified name. 578 * 579 * @param name the name of the configuration to be removed 580 * @return the removed configuration (<b>null</b> if this configuration 581 * was not found) 582 */ 583 public Configuration removeConfiguration(final String name) 584 { 585 final Configuration conf = getConfiguration(name); 586 if (conf != null) 587 { 588 removeConfiguration(conf); 589 } 590 return conf; 591 } 592 593 /** 594 * Returns a set with the names of all configurations contained in this 595 * combined configuration. Of course here are only these configurations 596 * listed, for which a name was specified when they were added. 597 * 598 * @return a set with the names of the contained configurations (never 599 * <b>null</b>) 600 */ 601 public Set<String> getConfigurationNames() 602 { 603 beginRead(true); 604 try 605 { 606 return namedConfigurations.keySet(); 607 } 608 finally 609 { 610 endRead(); 611 } 612 } 613 614 /** 615 * Invalidates this combined configuration. This means that the next time a 616 * property is accessed the combined node structure must be re-constructed. 617 * Invalidation of a combined configuration also means that an event of type 618 * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other 619 * events most times appear twice (once before and once after an update), 620 * this event is only fired once (after update). 621 */ 622 public void invalidate() 623 { 624 beginWrite(true); 625 try 626 { 627 invalidateInternal(); 628 } 629 finally 630 { 631 endWrite(); 632 } 633 } 634 635 /** 636 * Event listener call back for configuration update events. This method is 637 * called whenever one of the contained configurations was modified. It 638 * invalidates this combined configuration. 639 * 640 * @param event the update event 641 */ 642 @Override 643 public void onEvent(final ConfigurationEvent event) 644 { 645 if (event.isBeforeUpdate()) 646 { 647 invalidate(); 648 } 649 } 650 651 /** 652 * Clears this configuration. All contained configurations will be removed. 653 */ 654 @Override 655 protected void clearInternal() 656 { 657 unregisterListenerAtChildren(); 658 initChildCollections(); 659 invalidateInternal(); 660 } 661 662 /** 663 * Returns a copy of this object. This implementation performs a deep clone, 664 * i.e. all contained configurations will be cloned, too. For this to work, 665 * all contained configurations must be cloneable. Registered event 666 * listeners won't be cloned. The clone will use the same node combiner than 667 * the original. 668 * 669 * @return the copied object 670 */ 671 @Override 672 public Object clone() 673 { 674 beginRead(false); 675 try 676 { 677 final CombinedConfiguration copy = (CombinedConfiguration) super.clone(); 678 copy.initChildCollections(); 679 for (final ConfigData cd : configurations) 680 { 681 copy.addConfiguration(ConfigurationUtils.cloneConfiguration(cd 682 .getConfiguration()), cd.getName(), cd.getAt()); 683 } 684 685 return copy; 686 } 687 finally 688 { 689 endRead(); 690 } 691 } 692 693 /** 694 * Returns the configuration source, in which the specified key is defined. 695 * This method will determine the configuration node that is identified by 696 * the given key. The following constellations are possible: 697 * <ul> 698 * <li>If no node object is found for this key, <b>null</b> is returned.</li> 699 * <li>If the key maps to multiple nodes belonging to different 700 * configuration sources, a {@code IllegalArgumentException} is 701 * thrown (in this case no unique source can be determined).</li> 702 * <li>If exactly one node is found for the key, the (child) configuration 703 * object, to which the node belongs is determined and returned.</li> 704 * <li>For keys that have been added directly to this combined 705 * configuration and that do not belong to the namespaces defined by 706 * existing child configurations this configuration will be returned.</li> 707 * </ul> 708 * 709 * @param key the key of a configuration property 710 * @return the configuration, to which this property belongs or <b>null</b> 711 * if the key cannot be resolved 712 * @throws IllegalArgumentException if the key maps to multiple properties 713 * and the source cannot be determined, or if the key is <b>null</b> 714 * @since 1.5 715 */ 716 public Configuration getSource(final String key) 717 { 718 if (key == null) 719 { 720 throw new IllegalArgumentException("Key must not be null!"); 721 } 722 723 final Set<Configuration> sources = getSources(key); 724 if (sources.isEmpty()) 725 { 726 return null; 727 } 728 final Iterator<Configuration> iterator = sources.iterator(); 729 final Configuration source = iterator.next(); 730 if (iterator.hasNext()) 731 { 732 throw new IllegalArgumentException("The key " + key 733 + " is defined by multiple sources!"); 734 } 735 return source; 736 } 737 738 /** 739 * Returns a set with the configuration sources, in which the specified key 740 * is defined. This method determines the configuration nodes that are 741 * identified by the given key. It then determines the configuration sources 742 * to which these nodes belong and adds them to the result set. Note the 743 * following points: 744 * <ul> 745 * <li>If no node object is found for this key, an empty set is returned.</li> 746 * <li>For keys that have been added directly to this combined configuration 747 * and that do not belong to the namespaces defined by existing child 748 * configurations this combined configuration is contained in the result 749 * set.</li> 750 * </ul> 751 * 752 * @param key the key of a configuration property 753 * @return a set with the configuration sources, which contain this property 754 * @since 2.0 755 */ 756 public Set<Configuration> getSources(final String key) 757 { 758 beginRead(false); 759 try 760 { 761 final List<QueryResult<ImmutableNode>> results = fetchNodeList(key); 762 final Set<Configuration> sources = new HashSet<>(); 763 764 for (final QueryResult<ImmutableNode> result : results) 765 { 766 final Set<Configuration> resultSources = 767 findSourceConfigurations(result.getNode()); 768 if (resultSources.isEmpty()) 769 { 770 // key must be defined in combined configuration 771 sources.add(this); 772 } 773 else 774 { 775 sources.addAll(resultSources); 776 } 777 } 778 779 return sources; 780 } 781 finally 782 { 783 endRead(); 784 } 785 } 786 787 /** 788 * {@inheritDoc} This implementation checks whether a combined root node 789 * is available. If not, it is constructed by requesting a write lock. 790 */ 791 @Override 792 protected void beginRead(final boolean optimize) 793 { 794 if (optimize) 795 { 796 // just need a lock, don't construct configuration 797 super.beginRead(true); 798 return; 799 } 800 801 boolean lockObtained = false; 802 do 803 { 804 super.beginRead(false); 805 if (isUpToDate()) 806 { 807 lockObtained = true; 808 } 809 else 810 { 811 // release read lock and try to obtain a write lock 812 endRead(); 813 beginWrite(false); // this constructs the root node 814 endWrite(); 815 } 816 } while (!lockObtained); 817 } 818 819 /** 820 * {@inheritDoc} This implementation checks whether a combined root node 821 * is available. If not, it is constructed now. 822 */ 823 @Override 824 protected void beginWrite(final boolean optimize) 825 { 826 super.beginWrite(true); 827 if (optimize) 828 { 829 // just need a lock, don't construct configuration 830 return; 831 } 832 833 try 834 { 835 if (!isUpToDate()) 836 { 837 getSubConfigurationParentModel().replaceRoot( 838 constructCombinedNode(), this); 839 upToDate = true; 840 } 841 } 842 catch (final RuntimeException rex) 843 { 844 endWrite(); 845 throw rex; 846 } 847 } 848 849 /** 850 * Returns a flag whether this configuration has been invalidated. This 851 * means that the combined nodes structure has to be rebuilt before the 852 * configuration can be accessed. 853 * 854 * @return a flag whether this configuration is invalid 855 */ 856 private boolean isUpToDate() 857 { 858 return upToDate; 859 } 860 861 /** 862 * Marks this configuration as invalid. This means that the next access 863 * re-creates the root node. An invalidate event is also fired. Note: 864 * This implementation expects that an exclusive (write) lock is held on 865 * this instance. 866 */ 867 private void invalidateInternal() 868 { 869 upToDate = false; 870 fireEvent(COMBINED_INVALIDATE, null, null, false); 871 } 872 873 /** 874 * Initializes internal data structures for storing information about 875 * child configurations. 876 */ 877 private void initChildCollections() 878 { 879 configurations = new ArrayList<>(); 880 namedConfigurations = new HashMap<>(); 881 } 882 883 /** 884 * Creates the root node of this combined configuration. 885 * 886 * @return the combined root node 887 */ 888 private ImmutableNode constructCombinedNode() 889 { 890 if (getNumberOfConfigurationsInternal() < 1) 891 { 892 if (getLogger().isDebugEnabled()) 893 { 894 getLogger().debug("No configurations defined for " + this); 895 } 896 return EMPTY_ROOT; 897 } 898 final Iterator<ConfigData> it = configurations.iterator(); 899 ImmutableNode node = it.next().getTransformedRoot(); 900 while (it.hasNext()) 901 { 902 node = nodeCombiner.combine(node, 903 it.next().getTransformedRoot()); 904 } 905 if (getLogger().isDebugEnabled()) 906 { 907 final ByteArrayOutputStream os = new ByteArrayOutputStream(); 908 final PrintStream stream = new PrintStream(os); 909 TreeUtils.printTree(stream, node); 910 getLogger().debug(os.toString()); 911 } 912 return node; 913 } 914 915 /** 916 * Determines the configurations to which the specified node belongs. This 917 * is done by inspecting the nodes structures of all child configurations. 918 * 919 * @param node the node 920 * @return a set with the owning configurations 921 */ 922 private Set<Configuration> findSourceConfigurations(final ImmutableNode node) 923 { 924 final Set<Configuration> result = new HashSet<>(); 925 final FindNodeVisitor<ImmutableNode> visitor = 926 new FindNodeVisitor<>(node); 927 928 for (final ConfigData cd : configurations) 929 { 930 NodeTreeWalker.INSTANCE.walkBFS(cd.getRootNode(), visitor, 931 getModel().getNodeHandler()); 932 if (visitor.isFound()) 933 { 934 result.add(cd.getConfiguration()); 935 visitor.reset(); 936 } 937 } 938 939 return result; 940 } 941 942 /** 943 * Registers this combined configuration as listener at the given child 944 * configuration. 945 * 946 * @param configuration the child configuration 947 */ 948 private void registerListenerAt(final Configuration configuration) 949 { 950 if (configuration instanceof EventSource) 951 { 952 ((EventSource) configuration).addEventListener( 953 ConfigurationEvent.ANY, this); 954 } 955 } 956 957 /** 958 * Removes this combined configuration as listener from the given child 959 * configuration. 960 * 961 * @param configuration the child configuration 962 */ 963 private void unregisterListenerAt(final Configuration configuration) 964 { 965 if (configuration instanceof EventSource) 966 { 967 ((EventSource) configuration).removeEventListener( 968 ConfigurationEvent.ANY, this); 969 } 970 } 971 972 /** 973 * Removes this combined configuration as listener from all child 974 * configurations. This method is called on a clear() operation. 975 */ 976 private void unregisterListenerAtChildren() 977 { 978 if (configurations != null) 979 { 980 for (final ConfigData child : configurations) 981 { 982 unregisterListenerAt(child.getConfiguration()); 983 } 984 } 985 } 986 987 /** 988 * Returns the number of child configurations in this combined 989 * configuration. The internal list of child configurations is accessed 990 * without synchronization. 991 * 992 * @return the number of child configurations 993 */ 994 private int getNumberOfConfigurationsInternal() 995 { 996 return configurations.size(); 997 } 998 999 /** 1000 * An internal helper class for storing information about contained 1001 * configurations. 1002 */ 1003 private class ConfigData 1004 { 1005 /** Stores a reference to the configuration. */ 1006 private final Configuration configuration; 1007 1008 /** Stores the name under which the configuration is stored. */ 1009 private final String name; 1010 1011 /** Stores the at information as path of nodes. */ 1012 private final Collection<String> atPath; 1013 1014 /** Stores the at string.*/ 1015 private final String at; 1016 1017 /** Stores the root node for this child configuration.*/ 1018 private ImmutableNode rootNode; 1019 1020 /** 1021 * Creates a new instance of {@code ConfigData} and initializes 1022 * it. 1023 * 1024 * @param config the configuration 1025 * @param n the name 1026 * @param at the at position 1027 */ 1028 public ConfigData(final Configuration config, final String n, final String at) 1029 { 1030 configuration = config; 1031 name = n; 1032 atPath = parseAt(at); 1033 this.at = at; 1034 } 1035 1036 /** 1037 * Returns the stored configuration. 1038 * 1039 * @return the configuration 1040 */ 1041 public Configuration getConfiguration() 1042 { 1043 return configuration; 1044 } 1045 1046 /** 1047 * Returns the configuration's name. 1048 * 1049 * @return the name 1050 */ 1051 public String getName() 1052 { 1053 return name; 1054 } 1055 1056 /** 1057 * Returns the at position of this configuration. 1058 * 1059 * @return the at position 1060 */ 1061 public String getAt() 1062 { 1063 return at; 1064 } 1065 1066 /** 1067 * Returns the root node for this child configuration. 1068 * 1069 * @return the root node of this child configuration 1070 * @since 1.5 1071 */ 1072 public ImmutableNode getRootNode() 1073 { 1074 return rootNode; 1075 } 1076 1077 /** 1078 * Returns the transformed root node of the stored configuration. The 1079 * term "transformed" means that an eventually defined at path 1080 * has been applied. 1081 * 1082 * @return the transformed root node 1083 */ 1084 public ImmutableNode getTransformedRoot() 1085 { 1086 final ImmutableNode configRoot = getRootNodeOfConfiguration(); 1087 return atPath == null ? configRoot : prependAtPath(configRoot); 1088 } 1089 1090 /** 1091 * Prepends the at path to the given node. 1092 * 1093 * @param node the root node of the represented configuration 1094 * @return the new root node including the at path 1095 */ 1096 private ImmutableNode prependAtPath(final ImmutableNode node) 1097 { 1098 final ImmutableNode.Builder pathBuilder = new ImmutableNode.Builder(); 1099 final Iterator<String> pathIterator = atPath.iterator(); 1100 prependAtPathComponent(pathBuilder, pathIterator.next(), 1101 pathIterator, node); 1102 return new ImmutableNode.Builder(1).addChild(pathBuilder.create()) 1103 .create(); 1104 } 1105 1106 /** 1107 * Handles a single component of the at path. A corresponding node is 1108 * created and added to the hierarchical path to the original root node 1109 * of the configuration. 1110 * 1111 * @param builder the current node builder object 1112 * @param currentComponent the name of the current path component 1113 * @param components an iterator with all components of the at path 1114 * @param orgRoot the original root node of the wrapped configuration 1115 */ 1116 private void prependAtPathComponent(final ImmutableNode.Builder builder, 1117 final String currentComponent, final Iterator<String> components, 1118 final ImmutableNode orgRoot) 1119 { 1120 builder.name(currentComponent); 1121 if (components.hasNext()) 1122 { 1123 final ImmutableNode.Builder childBuilder = 1124 new ImmutableNode.Builder(); 1125 prependAtPathComponent(childBuilder, components.next(), 1126 components, orgRoot); 1127 builder.addChild(childBuilder.create()); 1128 } 1129 else 1130 { 1131 builder.addChildren(orgRoot.getChildren()); 1132 builder.addAttributes(orgRoot.getAttributes()); 1133 builder.value(orgRoot.getValue()); 1134 } 1135 } 1136 1137 /** 1138 * Obtains the root node of the wrapped configuration. If necessary, a 1139 * hierarchical representation of the configuration has to be created 1140 * first. 1141 * 1142 * @return the root node of the associated configuration 1143 */ 1144 private ImmutableNode getRootNodeOfConfiguration() 1145 { 1146 getConfiguration().lock(LockMode.READ); 1147 try 1148 { 1149 final ImmutableNode root = 1150 ConfigurationUtils 1151 .convertToHierarchical(getConfiguration(), 1152 conversionExpressionEngine).getNodeModel() 1153 .getInMemoryRepresentation(); 1154 rootNode = root; 1155 return root; 1156 } 1157 finally 1158 { 1159 getConfiguration().unlock(LockMode.READ); 1160 } 1161 } 1162 1163 /** 1164 * Splits the at path into its components. 1165 * 1166 * @param at the at string 1167 * @return a collection with the names of the single components 1168 */ 1169 private Collection<String> parseAt(final String at) 1170 { 1171 if (at == null) 1172 { 1173 return null; 1174 } 1175 1176 final Collection<String> result = new ArrayList<>(); 1177 final DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey( 1178 AT_ENGINE, at).iterator(); 1179 while (it.hasNext()) 1180 { 1181 result.add(it.nextKey()); 1182 } 1183 return result; 1184 } 1185 } 1186}