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 * @version $Id: CombinedConfiguration.java 1790899 2017-04-10 21:56:46Z ggregory $ 183 */ 184public class CombinedConfiguration extends BaseHierarchicalConfiguration implements 185 EventListener<ConfigurationEvent>, Cloneable 186{ 187 /** 188 * Constant for the event type fired when the internal node structure of a 189 * combined configuration becomes invalid. 190 * 191 * @since 2.0 192 */ 193 public static final EventType<ConfigurationEvent> COMBINED_INVALIDATE = 194 new EventType<>(ConfigurationEvent.ANY, 195 "COMBINED_INVALIDATE"); 196 197 /** Constant for the expression engine for parsing the at path. */ 198 private static final DefaultExpressionEngine AT_ENGINE = DefaultExpressionEngine.INSTANCE; 199 200 /** Constant for the default node combiner. */ 201 private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner(); 202 203 /** Constant for a root node for an empty configuration. */ 204 private static final ImmutableNode EMPTY_ROOT = new ImmutableNode.Builder() 205 .create(); 206 207 /** Stores the combiner. */ 208 private NodeCombiner nodeCombiner; 209 210 /** Stores a list with the contained configurations. */ 211 private List<ConfigData> configurations; 212 213 /** Stores a map with the named configurations. */ 214 private Map<String, Configuration> namedConfigurations; 215 216 /** 217 * An expression engine used for converting child configurations to 218 * hierarchical ones. 219 */ 220 private ExpressionEngine conversionExpressionEngine; 221 222 /** A flag whether this configuration is up-to-date. */ 223 private boolean upToDate; 224 225 /** 226 * Creates a new instance of {@code CombinedConfiguration} and 227 * initializes the combiner to be used. 228 * 229 * @param comb the node combiner (can be <b>null</b>, then a union combiner 230 * is used as default) 231 */ 232 public CombinedConfiguration(NodeCombiner comb) 233 { 234 nodeCombiner = (comb != null) ? comb : DEFAULT_COMBINER; 235 initChildCollections(); 236 } 237 238 /** 239 * Creates a new instance of {@code CombinedConfiguration} that uses 240 * a union combiner. 241 * 242 * @see org.apache.commons.configuration2.tree.UnionCombiner 243 */ 244 public CombinedConfiguration() 245 { 246 this(null); 247 } 248 249 /** 250 * Returns the node combiner that is used for creating the combined node 251 * structure. 252 * 253 * @return the node combiner 254 */ 255 public NodeCombiner getNodeCombiner() 256 { 257 beginRead(true); 258 try 259 { 260 return nodeCombiner; 261 } 262 finally 263 { 264 endRead(); 265 } 266 } 267 268 /** 269 * Sets the node combiner. This object will be used when the combined node 270 * structure is to be constructed. It must not be <b>null</b>, otherwise an 271 * {@code IllegalArgumentException} exception is thrown. Changing the 272 * node combiner causes an invalidation of this combined configuration, so 273 * that the new combiner immediately takes effect. 274 * 275 * @param nodeCombiner the node combiner 276 */ 277 public void setNodeCombiner(NodeCombiner nodeCombiner) 278 { 279 if (nodeCombiner == null) 280 { 281 throw new IllegalArgumentException( 282 "Node combiner must not be null!"); 283 } 284 285 beginWrite(true); 286 try 287 { 288 this.nodeCombiner = nodeCombiner; 289 invalidateInternal(); 290 } 291 finally 292 { 293 endWrite(); 294 } 295 } 296 297 /** 298 * Returns the {@code ExpressionEngine} for converting flat child 299 * configurations to hierarchical ones. 300 * 301 * @return the conversion expression engine 302 * @since 1.6 303 */ 304 public ExpressionEngine getConversionExpressionEngine() 305 { 306 beginRead(true); 307 try 308 { 309 return conversionExpressionEngine; 310 } 311 finally 312 { 313 endRead(); 314 } 315 } 316 317 /** 318 * Sets the {@code ExpressionEngine} for converting flat child 319 * configurations to hierarchical ones. When constructing the root node for 320 * this combined configuration the properties of all child configurations 321 * must be combined to a single hierarchical node structure. In this 322 * process, non hierarchical configurations are converted to hierarchical 323 * ones first. This can be problematic if a child configuration contains 324 * keys that are no compatible with the default expression engine used by 325 * hierarchical configurations. Therefore it is possible to specify a 326 * specific expression engine to be used for this purpose. 327 * 328 * @param conversionExpressionEngine the conversion expression engine 329 * @see ConfigurationUtils#convertToHierarchical(Configuration, ExpressionEngine) 330 * @since 1.6 331 */ 332 public void setConversionExpressionEngine( 333 ExpressionEngine conversionExpressionEngine) 334 { 335 beginWrite(true); 336 try 337 { 338 this.conversionExpressionEngine = conversionExpressionEngine; 339 } 340 finally 341 { 342 endWrite(); 343 } 344 } 345 346 /** 347 * Adds a new configuration to this combined configuration. It is possible 348 * (but not mandatory) to give the new configuration a name. This name must 349 * be unique, otherwise a {@code ConfigurationRuntimeException} will 350 * be thrown. With the optional {@code at} argument you can specify 351 * where in the resulting node structure the content of the added 352 * configuration should appear. This is a string that uses dots as property 353 * delimiters (independent on the current expression engine). For instance 354 * if you pass in the string {@code "database.tables"}, 355 * all properties of the added configuration will occur in this branch. 356 * 357 * @param config the configuration to add (must not be <b>null</b>) 358 * @param name the name of this configuration (can be <b>null</b>) 359 * @param at the position of this configuration in the combined tree (can be 360 * <b>null</b>) 361 */ 362 public void addConfiguration(Configuration config, String name, 363 String at) 364 { 365 if (config == null) 366 { 367 throw new IllegalArgumentException( 368 "Added configuration must not be null!"); 369 } 370 371 beginWrite(true); 372 try 373 { 374 if (name != null && namedConfigurations.containsKey(name)) 375 { 376 throw new ConfigurationRuntimeException( 377 "A configuration with the name '" 378 + name 379 + "' already exists in this combined configuration!"); 380 } 381 382 ConfigData cd = new ConfigData(config, name, at); 383 if (getLogger().isDebugEnabled()) 384 { 385 getLogger() 386 .debug("Adding configuration " + config + " with name " 387 + name); 388 } 389 configurations.add(cd); 390 if (name != null) 391 { 392 namedConfigurations.put(name, config); 393 } 394 395 invalidateInternal(); 396 } 397 finally 398 { 399 endWrite(); 400 } 401 registerListenerAt(config); 402 } 403 404 /** 405 * Adds a new configuration to this combined configuration with an optional 406 * name. The new configuration's properties will be added under the root of 407 * the combined node structure. 408 * 409 * @param config the configuration to add (must not be <b>null</b>) 410 * @param name the name of this configuration (can be <b>null</b>) 411 */ 412 public void addConfiguration(Configuration config, String name) 413 { 414 addConfiguration(config, name, null); 415 } 416 417 /** 418 * Adds a new configuration to this combined configuration. The new 419 * configuration is not given a name. Its properties will be added under the 420 * root of the combined node structure. 421 * 422 * @param config the configuration to add (must not be <b>null</b>) 423 */ 424 public void addConfiguration(Configuration config) 425 { 426 addConfiguration(config, null, null); 427 } 428 429 /** 430 * Returns the number of configurations that are contained in this combined 431 * configuration. 432 * 433 * @return the number of contained configurations 434 */ 435 public int getNumberOfConfigurations() 436 { 437 beginRead(true); 438 try 439 { 440 return getNumberOfConfigurationsInternal(); 441 } 442 finally 443 { 444 endRead(); 445 } 446 } 447 448 /** 449 * Returns the configuration at the specified index. The contained 450 * configurations are numbered in the order they were added to this combined 451 * configuration. The index of the first configuration is 0. 452 * 453 * @param index the index 454 * @return the configuration at this index 455 */ 456 public Configuration getConfiguration(int index) 457 { 458 beginRead(true); 459 try 460 { 461 ConfigData cd = configurations.get(index); 462 return cd.getConfiguration(); 463 } 464 finally 465 { 466 endRead(); 467 } 468 } 469 470 /** 471 * Returns the configuration with the given name. This can be <b>null</b> 472 * if no such configuration exists. 473 * 474 * @param name the name of the configuration 475 * @return the configuration with this name 476 */ 477 public Configuration getConfiguration(String name) 478 { 479 beginRead(true); 480 try 481 { 482 return namedConfigurations.get(name); 483 } 484 finally 485 { 486 endRead(); 487 } 488 } 489 490 /** 491 * Returns a List of all the configurations that have been added. 492 * @return A List of all the configurations. 493 * @since 1.7 494 */ 495 public List<Configuration> getConfigurations() 496 { 497 beginRead(true); 498 try 499 { 500 List<Configuration> list = 501 new ArrayList<>(getNumberOfConfigurationsInternal()); 502 for (ConfigData cd : configurations) 503 { 504 list.add(cd.getConfiguration()); 505 } 506 return list; 507 } 508 finally 509 { 510 endRead(); 511 } 512 } 513 514 /** 515 * Returns a List of the names of all the configurations that have been 516 * added in the order they were added. A NULL value will be present in 517 * the list for each configuration that was added without a name. 518 * @return A List of all the configuration names. 519 * @since 1.7 520 */ 521 public List<String> getConfigurationNameList() 522 { 523 beginRead(true); 524 try 525 { 526 List<String> list = new ArrayList<>(getNumberOfConfigurationsInternal()); 527 for (ConfigData cd : configurations) 528 { 529 list.add(cd.getName()); 530 } 531 return list; 532 } 533 finally 534 { 535 endRead(); 536 } 537 } 538 539 /** 540 * Removes the specified configuration from this combined configuration. 541 * 542 * @param config the configuration to be removed 543 * @return a flag whether this configuration was found and could be removed 544 */ 545 public boolean removeConfiguration(Configuration config) 546 { 547 for (int index = 0; index < getNumberOfConfigurations(); index++) 548 { 549 if (configurations.get(index).getConfiguration() == config) 550 { 551 removeConfigurationAt(index); 552 return true; 553 } 554 } 555 556 return false; 557 } 558 559 /** 560 * Removes the configuration at the specified index. 561 * 562 * @param index the index 563 * @return the removed configuration 564 */ 565 public Configuration removeConfigurationAt(int index) 566 { 567 ConfigData cd = configurations.remove(index); 568 if (cd.getName() != null) 569 { 570 namedConfigurations.remove(cd.getName()); 571 } 572 unregisterListenerAt(cd.getConfiguration()); 573 invalidateInternal(); 574 return cd.getConfiguration(); 575 } 576 577 /** 578 * Removes the configuration with the specified name. 579 * 580 * @param name the name of the configuration to be removed 581 * @return the removed configuration (<b>null</b> if this configuration 582 * was not found) 583 */ 584 public Configuration removeConfiguration(String name) 585 { 586 Configuration conf = getConfiguration(name); 587 if (conf != null) 588 { 589 removeConfiguration(conf); 590 } 591 return conf; 592 } 593 594 /** 595 * Returns a set with the names of all configurations contained in this 596 * combined configuration. Of course here are only these configurations 597 * listed, for which a name was specified when they were added. 598 * 599 * @return a set with the names of the contained configurations (never 600 * <b>null</b>) 601 */ 602 public Set<String> getConfigurationNames() 603 { 604 beginRead(true); 605 try 606 { 607 return namedConfigurations.keySet(); 608 } 609 finally 610 { 611 endRead(); 612 } 613 } 614 615 /** 616 * Invalidates this combined configuration. This means that the next time a 617 * property is accessed the combined node structure must be re-constructed. 618 * Invalidation of a combined configuration also means that an event of type 619 * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other 620 * events most times appear twice (once before and once after an update), 621 * this event is only fired once (after update). 622 */ 623 public void invalidate() 624 { 625 beginWrite(true); 626 try 627 { 628 invalidateInternal(); 629 } 630 finally 631 { 632 endWrite(); 633 } 634 } 635 636 /** 637 * Event listener call back for configuration update events. This method is 638 * called whenever one of the contained configurations was modified. It 639 * invalidates this combined configuration. 640 * 641 * @param event the update event 642 */ 643 @Override 644 public void onEvent(ConfigurationEvent event) 645 { 646 if (event.isBeforeUpdate()) 647 { 648 invalidate(); 649 } 650 } 651 652 /** 653 * Clears this configuration. All contained configurations will be removed. 654 */ 655 @Override 656 protected void clearInternal() 657 { 658 unregisterListenerAtChildren(); 659 initChildCollections(); 660 invalidateInternal(); 661 } 662 663 /** 664 * Returns a copy of this object. This implementation performs a deep clone, 665 * i.e. all contained configurations will be cloned, too. For this to work, 666 * all contained configurations must be cloneable. Registered event 667 * listeners won't be cloned. The clone will use the same node combiner than 668 * the original. 669 * 670 * @return the copied object 671 */ 672 @Override 673 public Object clone() 674 { 675 beginRead(false); 676 try 677 { 678 CombinedConfiguration copy = (CombinedConfiguration) super.clone(); 679 copy.initChildCollections(); 680 for (ConfigData cd : configurations) 681 { 682 copy.addConfiguration(ConfigurationUtils.cloneConfiguration(cd 683 .getConfiguration()), cd.getName(), cd.getAt()); 684 } 685 686 return copy; 687 } 688 finally 689 { 690 endRead(); 691 } 692 } 693 694 /** 695 * Returns the configuration source, in which the specified key is defined. 696 * This method will determine the configuration node that is identified by 697 * the given key. The following constellations are possible: 698 * <ul> 699 * <li>If no node object is found for this key, <b>null</b> is returned.</li> 700 * <li>If the key maps to multiple nodes belonging to different 701 * configuration sources, a {@code IllegalArgumentException} is 702 * thrown (in this case no unique source can be determined).</li> 703 * <li>If exactly one node is found for the key, the (child) configuration 704 * object, to which the node belongs is determined and returned.</li> 705 * <li>For keys that have been added directly to this combined 706 * configuration and that do not belong to the namespaces defined by 707 * existing child configurations this configuration will be returned.</li> 708 * </ul> 709 * 710 * @param key the key of a configuration property 711 * @return the configuration, to which this property belongs or <b>null</b> 712 * if the key cannot be resolved 713 * @throws IllegalArgumentException if the key maps to multiple properties 714 * and the source cannot be determined, or if the key is <b>null</b> 715 * @since 1.5 716 */ 717 public Configuration getSource(String key) 718 { 719 if (key == null) 720 { 721 throw new IllegalArgumentException("Key must not be null!"); 722 } 723 724 Set<Configuration> sources = getSources(key); 725 if (sources.isEmpty()) 726 { 727 return null; 728 } 729 Iterator<Configuration> iterator = sources.iterator(); 730 Configuration source = iterator.next(); 731 if (iterator.hasNext()) 732 { 733 throw new IllegalArgumentException("The key " + key 734 + " is defined by multiple sources!"); 735 } 736 return source; 737 } 738 739 /** 740 * Returns a set with the configuration sources, in which the specified key 741 * is defined. This method determines the configuration nodes that are 742 * identified by the given key. It then determines the configuration sources 743 * to which these nodes belong and adds them to the result set. Note the 744 * following points: 745 * <ul> 746 * <li>If no node object is found for this key, an empty set is returned.</li> 747 * <li>For keys that have been added directly to this combined configuration 748 * and that do not belong to the namespaces defined by existing child 749 * configurations this combined configuration is contained in the result 750 * set.</li> 751 * </ul> 752 * 753 * @param key the key of a configuration property 754 * @return a set with the configuration sources, which contain this property 755 * @since 2.0 756 */ 757 public Set<Configuration> getSources(String key) 758 { 759 beginRead(false); 760 try 761 { 762 List<QueryResult<ImmutableNode>> results = fetchNodeList(key); 763 Set<Configuration> sources = new HashSet<>(); 764 765 for (QueryResult<ImmutableNode> result : results) 766 { 767 Set<Configuration> resultSources = 768 findSourceConfigurations(result.getNode()); 769 if (resultSources.isEmpty()) 770 { 771 // key must be defined in combined configuration 772 sources.add(this); 773 } 774 else 775 { 776 sources.addAll(resultSources); 777 } 778 } 779 780 return sources; 781 } 782 finally 783 { 784 endRead(); 785 } 786 } 787 788 /** 789 * {@inheritDoc} This implementation checks whether a combined root node 790 * is available. If not, it is constructed by requesting a write lock. 791 */ 792 @Override 793 protected void beginRead(boolean optimize) 794 { 795 if (optimize) 796 { 797 // just need a lock, don't construct configuration 798 super.beginRead(true); 799 return; 800 } 801 802 boolean lockObtained = false; 803 do 804 { 805 super.beginRead(false); 806 if (isUpToDate()) 807 { 808 lockObtained = true; 809 } 810 else 811 { 812 // release read lock and try to obtain a write lock 813 endRead(); 814 beginWrite(false); // this constructs the root node 815 endWrite(); 816 } 817 } while (!lockObtained); 818 } 819 820 /** 821 * {@inheritDoc} This implementation checks whether a combined root node 822 * is available. If not, it is constructed now. 823 */ 824 @Override 825 protected void beginWrite(boolean optimize) 826 { 827 super.beginWrite(true); 828 if (optimize) 829 { 830 // just need a lock, don't construct configuration 831 return; 832 } 833 834 try 835 { 836 if (!isUpToDate()) 837 { 838 getSubConfigurationParentModel().replaceRoot( 839 constructCombinedNode(), this); 840 upToDate = true; 841 } 842 } 843 catch (RuntimeException rex) 844 { 845 endWrite(); 846 throw rex; 847 } 848 } 849 850 /** 851 * Returns a flag whether this configuration has been invalidated. This 852 * means that the combined nodes structure has to be rebuilt before the 853 * configuration can be accessed. 854 * 855 * @return a flag whether this configuration is invalid 856 */ 857 private boolean isUpToDate() 858 { 859 return upToDate; 860 } 861 862 /** 863 * Marks this configuration as invalid. This means that the next access 864 * re-creates the root node. An invalidate event is also fired. Note: 865 * This implementation expects that an exclusive (write) lock is held on 866 * this instance. 867 */ 868 private void invalidateInternal() 869 { 870 upToDate = false; 871 fireEvent(COMBINED_INVALIDATE, null, null, false); 872 } 873 874 /** 875 * Initializes internal data structures for storing information about 876 * child configurations. 877 */ 878 private void initChildCollections() 879 { 880 configurations = new ArrayList<>(); 881 namedConfigurations = new HashMap<>(); 882 } 883 884 /** 885 * Creates the root node of this combined configuration. 886 * 887 * @return the combined root node 888 */ 889 private ImmutableNode constructCombinedNode() 890 { 891 if (getNumberOfConfigurationsInternal() < 1) 892 { 893 if (getLogger().isDebugEnabled()) 894 { 895 getLogger().debug("No configurations defined for " + this); 896 } 897 return EMPTY_ROOT; 898 } 899 900 else 901 { 902 Iterator<ConfigData> it = configurations.iterator(); 903 ImmutableNode node = it.next().getTransformedRoot(); 904 while (it.hasNext()) 905 { 906 node = nodeCombiner.combine(node, 907 it.next().getTransformedRoot()); 908 } 909 if (getLogger().isDebugEnabled()) 910 { 911 ByteArrayOutputStream os = new ByteArrayOutputStream(); 912 PrintStream stream = new PrintStream(os); 913 TreeUtils.printTree(stream, node); 914 getLogger().debug(os.toString()); 915 } 916 return node; 917 } 918 } 919 920 /** 921 * Determines the configurations to which the specified node belongs. This 922 * is done by inspecting the nodes structures of all child configurations. 923 * 924 * @param node the node 925 * @return a set with the owning configurations 926 */ 927 private Set<Configuration> findSourceConfigurations(ImmutableNode node) 928 { 929 Set<Configuration> result = new HashSet<>(); 930 FindNodeVisitor<ImmutableNode> visitor = 931 new FindNodeVisitor<>(node); 932 933 for (ConfigData cd : configurations) 934 { 935 NodeTreeWalker.INSTANCE.walkBFS(cd.getRootNode(), visitor, 936 getModel().getNodeHandler()); 937 if (visitor.isFound()) 938 { 939 result.add(cd.getConfiguration()); 940 visitor.reset(); 941 } 942 } 943 944 return result; 945 } 946 947 /** 948 * Registers this combined configuration as listener at the given child 949 * configuration. 950 * 951 * @param configuration the child configuration 952 */ 953 private void registerListenerAt(Configuration configuration) 954 { 955 if (configuration instanceof EventSource) 956 { 957 ((EventSource) configuration).addEventListener( 958 ConfigurationEvent.ANY, this); 959 } 960 } 961 962 /** 963 * Removes this combined configuration as listener from the given child 964 * configuration. 965 * 966 * @param configuration the child configuration 967 */ 968 private void unregisterListenerAt(Configuration configuration) 969 { 970 if (configuration instanceof EventSource) 971 { 972 ((EventSource) configuration).removeEventListener( 973 ConfigurationEvent.ANY, this); 974 } 975 } 976 977 /** 978 * Removes this combined configuration as listener from all child 979 * configurations. This method is called on a clear() operation. 980 */ 981 private void unregisterListenerAtChildren() 982 { 983 if (configurations != null) 984 { 985 for (ConfigData child : configurations) 986 { 987 unregisterListenerAt(child.getConfiguration()); 988 } 989 } 990 } 991 992 /** 993 * Returns the number of child configurations in this combined 994 * configuration. The internal list of child configurations is accessed 995 * without synchronization. 996 * 997 * @return the number of child configurations 998 */ 999 private int getNumberOfConfigurationsInternal() 1000 { 1001 return configurations.size(); 1002 } 1003 1004 /** 1005 * An internal helper class for storing information about contained 1006 * configurations. 1007 */ 1008 private class ConfigData 1009 { 1010 /** Stores a reference to the configuration. */ 1011 private final Configuration configuration; 1012 1013 /** Stores the name under which the configuration is stored. */ 1014 private final String name; 1015 1016 /** Stores the at information as path of nodes. */ 1017 private final Collection<String> atPath; 1018 1019 /** Stores the at string.*/ 1020 private final String at; 1021 1022 /** Stores the root node for this child configuration.*/ 1023 private ImmutableNode rootNode; 1024 1025 /** 1026 * Creates a new instance of {@code ConfigData} and initializes 1027 * it. 1028 * 1029 * @param config the configuration 1030 * @param n the name 1031 * @param at the at position 1032 */ 1033 public ConfigData(Configuration config, String n, String at) 1034 { 1035 configuration = config; 1036 name = n; 1037 atPath = parseAt(at); 1038 this.at = at; 1039 } 1040 1041 /** 1042 * Returns the stored configuration. 1043 * 1044 * @return the configuration 1045 */ 1046 public Configuration getConfiguration() 1047 { 1048 return configuration; 1049 } 1050 1051 /** 1052 * Returns the configuration's name. 1053 * 1054 * @return the name 1055 */ 1056 public String getName() 1057 { 1058 return name; 1059 } 1060 1061 /** 1062 * Returns the at position of this configuration. 1063 * 1064 * @return the at position 1065 */ 1066 public String getAt() 1067 { 1068 return at; 1069 } 1070 1071 /** 1072 * Returns the root node for this child configuration. 1073 * 1074 * @return the root node of this child configuration 1075 * @since 1.5 1076 */ 1077 public ImmutableNode getRootNode() 1078 { 1079 return rootNode; 1080 } 1081 1082 /** 1083 * Returns the transformed root node of the stored configuration. The 1084 * term "transformed" means that an eventually defined at path 1085 * has been applied. 1086 * 1087 * @return the transformed root node 1088 */ 1089 public ImmutableNode getTransformedRoot() 1090 { 1091 ImmutableNode configRoot = getRootNodeOfConfiguration(); 1092 return (atPath == null) ? configRoot : prependAtPath(configRoot); 1093 } 1094 1095 /** 1096 * Prepends the at path to the given node. 1097 * 1098 * @param node the root node of the represented configuration 1099 * @return the new root node including the at path 1100 */ 1101 private ImmutableNode prependAtPath(ImmutableNode node) 1102 { 1103 ImmutableNode.Builder pathBuilder = new ImmutableNode.Builder(); 1104 Iterator<String> pathIterator = atPath.iterator(); 1105 prependAtPathComponent(pathBuilder, pathIterator.next(), 1106 pathIterator, node); 1107 return new ImmutableNode.Builder(1).addChild(pathBuilder.create()) 1108 .create(); 1109 } 1110 1111 /** 1112 * Handles a single component of the at path. A corresponding node is 1113 * created and added to the hierarchical path to the original root node 1114 * of the configuration. 1115 * 1116 * @param builder the current node builder object 1117 * @param currentComponent the name of the current path component 1118 * @param components an iterator with all components of the at path 1119 * @param orgRoot the original root node of the wrapped configuration 1120 */ 1121 private void prependAtPathComponent(ImmutableNode.Builder builder, 1122 String currentComponent, Iterator<String> components, 1123 ImmutableNode orgRoot) 1124 { 1125 builder.name(currentComponent); 1126 if (components.hasNext()) 1127 { 1128 ImmutableNode.Builder childBuilder = 1129 new ImmutableNode.Builder(); 1130 prependAtPathComponent(childBuilder, components.next(), 1131 components, orgRoot); 1132 builder.addChild(childBuilder.create()); 1133 } 1134 else 1135 { 1136 builder.addChildren(orgRoot.getChildren()); 1137 builder.addAttributes(orgRoot.getAttributes()); 1138 builder.value(orgRoot.getValue()); 1139 } 1140 } 1141 1142 /** 1143 * Obtains the root node of the wrapped configuration. If necessary, a 1144 * hierarchical representation of the configuration has to be created 1145 * first. 1146 * 1147 * @return the root node of the associated configuration 1148 */ 1149 private ImmutableNode getRootNodeOfConfiguration() 1150 { 1151 getConfiguration().lock(LockMode.READ); 1152 try 1153 { 1154 ImmutableNode root = 1155 ConfigurationUtils 1156 .convertToHierarchical(getConfiguration(), 1157 conversionExpressionEngine).getNodeModel() 1158 .getInMemoryRepresentation(); 1159 rootNode = root; 1160 return root; 1161 } 1162 finally 1163 { 1164 getConfiguration().unlock(LockMode.READ); 1165 } 1166 } 1167 1168 /** 1169 * Splits the at path into its components. 1170 * 1171 * @param at the at string 1172 * @return a collection with the names of the single components 1173 */ 1174 private Collection<String> parseAt(String at) 1175 { 1176 if (at == null) 1177 { 1178 return null; 1179 } 1180 1181 Collection<String> result = new ArrayList<>(); 1182 DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey( 1183 AT_ENGINE, at).iterator(); 1184 while (it.hasNext()) 1185 { 1186 result.add(it.nextKey()); 1187 } 1188 return result; 1189 } 1190 } 1191}