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