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.math.BigDecimal; 020import java.math.BigInteger; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.Iterator; 026import java.util.List; 027import java.util.Map; 028import java.util.Properties; 029import java.util.Set; 030import java.util.concurrent.ConcurrentHashMap; 031import java.util.concurrent.ConcurrentMap; 032 033import org.apache.commons.configuration2.event.Event; 034import org.apache.commons.configuration2.event.EventListener; 035import org.apache.commons.configuration2.event.EventType; 036import org.apache.commons.configuration2.interpol.ConfigurationInterpolator; 037import org.apache.commons.configuration2.interpol.Lookup; 038import org.apache.commons.configuration2.io.ConfigurationLogger; 039import org.apache.commons.configuration2.tree.ExpressionEngine; 040import org.apache.commons.configuration2.tree.ImmutableNode; 041import org.apache.commons.configuration2.tree.NodeCombiner; 042 043/** 044 * <p> 045 * DynamicCombinedConfiguration allows a set of CombinedConfigurations to be used. 046 * </p> 047 * <p> 048 * Each CombinedConfiguration is referenced by a key that is dynamically constructed from a key pattern on each call. 049 * The key pattern will be resolved using the configured ConfigurationInterpolator. 050 * </p> 051 * <p> 052 * This Configuration implementation uses the configured {@code Synchronizer} to guard itself against concurrent access. 053 * If there are multiple threads accessing an instance concurrently, a fully functional {@code Synchronizer} 054 * implementation (e.g. {@code ReadWriteSynchronizer}) has to be used to ensure consistency and to avoid exceptions. The 055 * {@code Synchronizer} assigned to an instance is also passed to child configuration objects when they are created. 056 * </p> 057 * 058 * @since 1.6 059 */ 060public class DynamicCombinedConfiguration extends CombinedConfiguration { 061 /** 062 * Stores the current configuration for each involved thread. This value is set at the beginning of an operation and 063 * removed at the end. 064 */ 065 private static final ThreadLocal<CurrentConfigHolder> CURRENT_CONFIG = new ThreadLocal<>(); 066 067 /** The CombinedConfigurations */ 068 private final ConcurrentMap<String, CombinedConfiguration> configs = new ConcurrentHashMap<>(); 069 070 /** Stores a list with the contained configurations. */ 071 private final List<ConfigData> configurations = new ArrayList<>(); 072 073 /** Stores a map with the named configurations. */ 074 private final Map<String, Configuration> namedConfigurations = new HashMap<>(); 075 076 /** The key pattern for the CombinedConfiguration map */ 077 private String keyPattern; 078 079 /** Stores the combiner. */ 080 private NodeCombiner nodeCombiner; 081 082 /** The name of the logger to use for each CombinedConfiguration */ 083 private String loggerName = DynamicCombinedConfiguration.class.getName(); 084 085 /** The object for handling variable substitution in key patterns. */ 086 private final ConfigurationInterpolator localSubst; 087 088 /** 089 * Creates a new instance of {@code DynamicCombinedConfiguration} and initializes the combiner to be used. 090 * 091 * @param comb the node combiner (can be <b>null</b>, then a union combiner is used as default) 092 */ 093 public DynamicCombinedConfiguration(final NodeCombiner comb) { 094 setNodeCombiner(comb); 095 initLogger(new ConfigurationLogger(DynamicCombinedConfiguration.class)); 096 localSubst = initLocalInterpolator(); 097 } 098 099 /** 100 * Creates a new instance of {@code DynamicCombinedConfiguration} that uses a union combiner. 101 * 102 * @see org.apache.commons.configuration2.tree.UnionCombiner 103 */ 104 public DynamicCombinedConfiguration() { 105 initLogger(new ConfigurationLogger(DynamicCombinedConfiguration.class)); 106 localSubst = initLocalInterpolator(); 107 } 108 109 public void setKeyPattern(final String pattern) { 110 this.keyPattern = pattern; 111 } 112 113 public String getKeyPattern() { 114 return this.keyPattern; 115 } 116 117 /** 118 * Set the name of the Logger to use on each CombinedConfiguration. 119 * 120 * @param name The Logger name. 121 */ 122 public void setLoggerName(final String name) { 123 this.loggerName = name; 124 } 125 126 /** 127 * Returns the node combiner that is used for creating the combined node structure. 128 * 129 * @return the node combiner 130 */ 131 @Override 132 public NodeCombiner getNodeCombiner() { 133 return nodeCombiner; 134 } 135 136 /** 137 * Sets the node combiner. This object will be used when the combined node structure is to be constructed. It must not 138 * be <b>null</b>, otherwise an {@code IllegalArgumentException} exception is thrown. Changing the node combiner causes 139 * an invalidation of this combined configuration, so that the new combiner immediately takes effect. 140 * 141 * @param nodeCombiner the node combiner 142 */ 143 @Override 144 public void setNodeCombiner(final NodeCombiner nodeCombiner) { 145 if (nodeCombiner == null) { 146 throw new IllegalArgumentException("Node combiner must not be null!"); 147 } 148 this.nodeCombiner = nodeCombiner; 149 invalidateAll(); 150 } 151 152 /** 153 * Adds a new configuration to this combined configuration. It is possible (but not mandatory) to give the new 154 * configuration a name. This name must be unique, otherwise a {@code ConfigurationRuntimeException} will be thrown. 155 * With the optional {@code at} argument you can specify where in the resulting node structure the content of the added 156 * configuration should appear. This is a string that uses dots as property delimiters (independent on the current 157 * expression engine). For instance if you pass in the string {@code "database.tables"}, all properties of the added 158 * configuration will occur in this branch. 159 * 160 * @param config the configuration to add (must not be <b>null</b>) 161 * @param name the name of this configuration (can be <b>null</b>) 162 * @param at the position of this configuration in the combined tree (can be <b>null</b>) 163 */ 164 @Override 165 public void addConfiguration(final Configuration config, final String name, final String at) { 166 beginWrite(true); 167 try { 168 final ConfigData cd = new ConfigData(config, name, at); 169 configurations.add(cd); 170 if (name != null) { 171 namedConfigurations.put(name, config); 172 } 173 174 // clear cache of all child configurations 175 configs.clear(); 176 } finally { 177 endWrite(); 178 } 179 } 180 181 /** 182 * Returns the number of configurations that are contained in this combined configuration. 183 * 184 * @return the number of contained configurations 185 */ 186 @Override 187 public int getNumberOfConfigurations() { 188 beginRead(false); 189 try { 190 return configurations.size(); 191 } finally { 192 endRead(); 193 } 194 } 195 196 /** 197 * Returns the configuration at the specified index. The contained configurations are numbered in the order they were 198 * added to this combined configuration. The index of the first configuration is 0. 199 * 200 * @param index the index 201 * @return the configuration at this index 202 */ 203 @Override 204 public Configuration getConfiguration(final int index) { 205 beginRead(false); 206 try { 207 final ConfigData cd = configurations.get(index); 208 return cd.getConfiguration(); 209 } finally { 210 endRead(); 211 } 212 } 213 214 /** 215 * Returns the configuration with the given name. This can be <b>null</b> if no such configuration exists. 216 * 217 * @param name the name of the configuration 218 * @return the configuration with this name 219 */ 220 @Override 221 public Configuration getConfiguration(final String name) { 222 beginRead(false); 223 try { 224 return namedConfigurations.get(name); 225 } finally { 226 endRead(); 227 } 228 } 229 230 /** 231 * Returns a set with the names of all configurations contained in this combined configuration. Of course here are only 232 * these configurations listed, for which a name was specified when they were added. 233 * 234 * @return a set with the names of the contained configurations (never <b>null</b>) 235 */ 236 @Override 237 public Set<String> getConfigurationNames() { 238 beginRead(false); 239 try { 240 return namedConfigurations.keySet(); 241 } finally { 242 endRead(); 243 } 244 } 245 246 /** 247 * Removes the configuration with the specified name. 248 * 249 * @param name the name of the configuration to be removed 250 * @return the removed configuration (<b>null</b> if this configuration was not found) 251 */ 252 @Override 253 public Configuration removeConfiguration(final String name) { 254 final Configuration conf = getConfiguration(name); 255 if (conf != null) { 256 removeConfiguration(conf); 257 } 258 return conf; 259 } 260 261 /** 262 * Removes the specified configuration from this combined configuration. 263 * 264 * @param config the configuration to be removed 265 * @return a flag whether this configuration was found and could be removed 266 */ 267 @Override 268 public boolean removeConfiguration(final Configuration config) { 269 beginWrite(false); 270 try { 271 for (int index = 0; index < getNumberOfConfigurations(); index++) { 272 if (configurations.get(index).getConfiguration() == config) { 273 removeConfigurationAt(index); 274 return true; 275 } 276 } 277 278 return false; 279 } finally { 280 endWrite(); 281 } 282 } 283 284 /** 285 * Removes the configuration at the specified index. 286 * 287 * @param index the index 288 * @return the removed configuration 289 */ 290 @Override 291 public Configuration removeConfigurationAt(final int index) { 292 beginWrite(false); 293 try { 294 final ConfigData cd = configurations.remove(index); 295 if (cd.getName() != null) { 296 namedConfigurations.remove(cd.getName()); 297 } 298 return cd.getConfiguration(); 299 } finally { 300 endWrite(); 301 } 302 } 303 304 @Override 305 protected void addPropertyInternal(final String key, final Object value) { 306 this.getCurrentConfig().addProperty(key, value); 307 } 308 309 @Override 310 protected void clearInternal() { 311 this.getCurrentConfig().clear(); 312 } 313 314 @Override 315 protected void clearPropertyDirect(final String key) { 316 this.getCurrentConfig().clearProperty(key); 317 } 318 319 @Override 320 protected boolean containsKeyInternal(final String key) { 321 return this.getCurrentConfig().containsKey(key); 322 } 323 324 @Override 325 public BigDecimal getBigDecimal(final String key, final BigDecimal defaultValue) { 326 return this.getCurrentConfig().getBigDecimal(key, defaultValue); 327 } 328 329 @Override 330 public BigDecimal getBigDecimal(final String key) { 331 return this.getCurrentConfig().getBigDecimal(key); 332 } 333 334 @Override 335 public BigInteger getBigInteger(final String key, final BigInteger defaultValue) { 336 return this.getCurrentConfig().getBigInteger(key, defaultValue); 337 } 338 339 @Override 340 public BigInteger getBigInteger(final String key) { 341 return this.getCurrentConfig().getBigInteger(key); 342 } 343 344 @Override 345 public boolean getBoolean(final String key, final boolean defaultValue) { 346 return this.getCurrentConfig().getBoolean(key, defaultValue); 347 } 348 349 @Override 350 public Boolean getBoolean(final String key, final Boolean defaultValue) { 351 return this.getCurrentConfig().getBoolean(key, defaultValue); 352 } 353 354 @Override 355 public boolean getBoolean(final String key) { 356 return this.getCurrentConfig().getBoolean(key); 357 } 358 359 @Override 360 public byte getByte(final String key, final byte defaultValue) { 361 return this.getCurrentConfig().getByte(key, defaultValue); 362 } 363 364 @Override 365 public Byte getByte(final String key, final Byte defaultValue) { 366 return this.getCurrentConfig().getByte(key, defaultValue); 367 } 368 369 @Override 370 public byte getByte(final String key) { 371 return this.getCurrentConfig().getByte(key); 372 } 373 374 @Override 375 public double getDouble(final String key, final double defaultValue) { 376 return this.getCurrentConfig().getDouble(key, defaultValue); 377 } 378 379 @Override 380 public Double getDouble(final String key, final Double defaultValue) { 381 return this.getCurrentConfig().getDouble(key, defaultValue); 382 } 383 384 @Override 385 public double getDouble(final String key) { 386 return this.getCurrentConfig().getDouble(key); 387 } 388 389 @Override 390 public float getFloat(final String key, final float defaultValue) { 391 return this.getCurrentConfig().getFloat(key, defaultValue); 392 } 393 394 @Override 395 public Float getFloat(final String key, final Float defaultValue) { 396 return this.getCurrentConfig().getFloat(key, defaultValue); 397 } 398 399 @Override 400 public float getFloat(final String key) { 401 return this.getCurrentConfig().getFloat(key); 402 } 403 404 @Override 405 public int getInt(final String key, final int defaultValue) { 406 return this.getCurrentConfig().getInt(key, defaultValue); 407 } 408 409 @Override 410 public int getInt(final String key) { 411 return this.getCurrentConfig().getInt(key); 412 } 413 414 @Override 415 public Integer getInteger(final String key, final Integer defaultValue) { 416 return this.getCurrentConfig().getInteger(key, defaultValue); 417 } 418 419 @Override 420 protected Iterator<String> getKeysInternal() { 421 return this.getCurrentConfig().getKeys(); 422 } 423 424 @Override 425 protected Iterator<String> getKeysInternal(final String prefix) { 426 return this.getCurrentConfig().getKeys(prefix); 427 } 428 429 @Override 430 public List<Object> getList(final String key, final List<?> defaultValue) { 431 return this.getCurrentConfig().getList(key, defaultValue); 432 } 433 434 @Override 435 public List<Object> getList(final String key) { 436 return this.getCurrentConfig().getList(key); 437 } 438 439 @Override 440 public long getLong(final String key, final long defaultValue) { 441 return this.getCurrentConfig().getLong(key, defaultValue); 442 } 443 444 @Override 445 public Long getLong(final String key, final Long defaultValue) { 446 return this.getCurrentConfig().getLong(key, defaultValue); 447 } 448 449 @Override 450 public long getLong(final String key) { 451 return this.getCurrentConfig().getLong(key); 452 } 453 454 @Override 455 public Properties getProperties(final String key) { 456 return this.getCurrentConfig().getProperties(key); 457 } 458 459 @Override 460 protected Object getPropertyInternal(final String key) { 461 return this.getCurrentConfig().getProperty(key); 462 } 463 464 @Override 465 public short getShort(final String key, final short defaultValue) { 466 return this.getCurrentConfig().getShort(key, defaultValue); 467 } 468 469 @Override 470 public Short getShort(final String key, final Short defaultValue) { 471 return this.getCurrentConfig().getShort(key, defaultValue); 472 } 473 474 @Override 475 public short getShort(final String key) { 476 return this.getCurrentConfig().getShort(key); 477 } 478 479 @Override 480 public String getString(final String key, final String defaultValue) { 481 return this.getCurrentConfig().getString(key, defaultValue); 482 } 483 484 @Override 485 public String getString(final String key) { 486 return this.getCurrentConfig().getString(key); 487 } 488 489 @Override 490 public String[] getStringArray(final String key) { 491 return this.getCurrentConfig().getStringArray(key); 492 } 493 494 @Override 495 protected boolean isEmptyInternal() { 496 return this.getCurrentConfig().isEmpty(); 497 } 498 499 @Override 500 protected int sizeInternal() { 501 return this.getCurrentConfig().size(); 502 } 503 504 @Override 505 protected void setPropertyInternal(final String key, final Object value) { 506 this.getCurrentConfig().setProperty(key, value); 507 } 508 509 @Override 510 public Configuration subset(final String prefix) { 511 return this.getCurrentConfig().subset(prefix); 512 } 513 514 @Override 515 public ExpressionEngine getExpressionEngine() { 516 return super.getExpressionEngine(); 517 } 518 519 @Override 520 public void setExpressionEngine(final ExpressionEngine expressionEngine) { 521 super.setExpressionEngine(expressionEngine); 522 } 523 524 @Override 525 protected void addNodesInternal(final String key, final Collection<? extends ImmutableNode> nodes) { 526 this.getCurrentConfig().addNodes(key, nodes); 527 } 528 529 @Override 530 public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key, final boolean supportUpdates) { 531 return this.getCurrentConfig().configurationAt(key, supportUpdates); 532 } 533 534 @Override 535 public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key) { 536 return this.getCurrentConfig().configurationAt(key); 537 } 538 539 @Override 540 public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(final String key) { 541 return this.getCurrentConfig().configurationsAt(key); 542 } 543 544 @Override 545 protected Object clearTreeInternal(final String key) { 546 this.getCurrentConfig().clearTree(key); 547 return Collections.emptyList(); 548 } 549 550 @Override 551 protected int getMaxIndexInternal(final String key) { 552 return this.getCurrentConfig().getMaxIndex(key); 553 } 554 555 @Override 556 public Configuration interpolatedConfiguration() { 557 return this.getCurrentConfig().interpolatedConfiguration(); 558 } 559 560 /** 561 * Returns the configuration source, in which the specified key is defined. This method will determine the configuration 562 * node that is identified by the given key. The following constellations are possible: 563 * <ul> 564 * <li>If no node object is found for this key, <b>null</b> is returned.</li> 565 * <li>If the key maps to multiple nodes belonging to different configuration sources, a 566 * {@code IllegalArgumentException} is thrown (in this case no unique source can be determined).</li> 567 * <li>If exactly one node is found for the key, the (child) configuration object, to which the node belongs is 568 * determined and returned.</li> 569 * <li>For keys that have been added directly to this combined configuration and that do not belong to the namespaces 570 * defined by existing child configurations this configuration will be returned.</li> 571 * </ul> 572 * 573 * @param key the key of a configuration property 574 * @return the configuration, to which this property belongs or <b>null</b> if the key cannot be resolved 575 * @throws IllegalArgumentException if the key maps to multiple properties and the source cannot be determined, or if 576 * the key is <b>null</b> 577 */ 578 @Override 579 public Configuration getSource(final String key) { 580 if (key == null) { 581 throw new IllegalArgumentException("Key must not be null!"); 582 } 583 return getCurrentConfig().getSource(key); 584 } 585 586 @Override 587 public void clearEventListeners() { 588 for (final CombinedConfiguration cc : configs.values()) { 589 cc.clearEventListeners(); 590 } 591 super.clearEventListeners(); 592 } 593 594 @Override 595 public <T extends Event> void addEventListener(final EventType<T> eventType, final EventListener<? super T> listener) { 596 for (final CombinedConfiguration cc : configs.values()) { 597 cc.addEventListener(eventType, listener); 598 } 599 super.addEventListener(eventType, listener); 600 } 601 602 @Override 603 public <T extends Event> boolean removeEventListener(final EventType<T> eventType, final EventListener<? super T> listener) { 604 for (final CombinedConfiguration cc : configs.values()) { 605 cc.removeEventListener(eventType, listener); 606 } 607 return super.removeEventListener(eventType, listener); 608 } 609 610 @Override 611 public void clearErrorListeners() { 612 for (final CombinedConfiguration cc : configs.values()) { 613 cc.clearErrorListeners(); 614 } 615 super.clearErrorListeners(); 616 } 617 618 /** 619 * Returns a copy of this object. This implementation performs a deep clone, i.e. all contained configurations will be 620 * cloned, too. For this to work, all contained configurations must be cloneable. Registered event listeners won't be 621 * cloned. The clone will use the same node combiner than the original. 622 * 623 * @return the copied object 624 */ 625 @Override 626 public Object clone() { 627 return super.clone(); 628 } 629 630 /** 631 * Invalidates the current combined configuration. This means that the next time a property is accessed the combined 632 * node structure must be re-constructed. Invalidation of a combined configuration also means that an event of type 633 * {@code EVENT_COMBINED_INVALIDATE} is fired. Note that while other events most times appear twice (once before and 634 * once after an update), this event is only fired once (after update). 635 */ 636 @Override 637 public void invalidate() { 638 getCurrentConfig().invalidate(); 639 } 640 641 public void invalidateAll() { 642 for (final CombinedConfiguration cc : configs.values()) { 643 cc.invalidate(); 644 } 645 } 646 647 /** 648 * {@inheritDoc} This implementation ensures that the current configuration is initialized. The lock counter is 649 * increased. 650 */ 651 @Override 652 protected void beginRead(final boolean optimize) { 653 final CurrentConfigHolder cch = ensureCurrentConfiguration(); 654 cch.incrementLockCount(); 655 if (!optimize && cch.getCurrentConfiguration() == null) { 656 // delegate to beginWrite() which creates the child configuration 657 beginWrite(false); 658 endWrite(); 659 } 660 661 // This actually uses our own synchronizer 662 cch.getCurrentConfiguration().beginRead(optimize); 663 } 664 665 /** 666 * {@inheritDoc} This implementation ensures that the current configuration is initialized. If necessary, a new child 667 * configuration instance is created. 668 */ 669 @Override 670 protected void beginWrite(final boolean optimize) { 671 final CurrentConfigHolder cch = ensureCurrentConfiguration(); 672 cch.incrementLockCount(); 673 674 super.beginWrite(optimize); 675 if (!optimize && cch.getCurrentConfiguration() == null) { 676 cch.setCurrentConfiguration(createChildConfiguration()); 677 configs.put(cch.getKey(), cch.getCurrentConfiguration()); 678 initChildConfiguration(cch.getCurrentConfiguration()); 679 } 680 } 681 682 /** 683 * {@inheritDoc} This implementation clears the current configuration if necessary. 684 */ 685 @Override 686 protected void endRead() { 687 CURRENT_CONFIG.get().getCurrentConfiguration().endRead(); 688 releaseLock(); 689 } 690 691 /** 692 * {@inheritDoc} This implementation clears the current configuration if necessary. 693 */ 694 @Override 695 protected void endWrite() { 696 super.endWrite(); 697 releaseLock(); 698 } 699 700 /** 701 * Decrements the lock count of the current configuration holder. If it reaches 0, the current configuration is removed. 702 * (It is then reevaluated when the next operation starts.) 703 */ 704 private void releaseLock() { 705 final CurrentConfigHolder cch = CURRENT_CONFIG.get(); 706 assert cch != null : "No current configuration!"; 707 if (cch.decrementLockCountAndCheckRelease()) { 708 CURRENT_CONFIG.remove(); 709 } 710 } 711 712 /** 713 * Returns the current configuration. This configuration was initialized at the beginning of an operation and stored in 714 * a thread-local variable. Some methods of this class call this method directly without requesting a lock before. To 715 * deal with this, we always request an additional read lock. 716 * 717 * @return the current configuration 718 */ 719 private CombinedConfiguration getCurrentConfig() { 720 CombinedConfiguration config; 721 String key; 722 beginRead(false); 723 try { 724 config = CURRENT_CONFIG.get().getCurrentConfiguration(); 725 key = CURRENT_CONFIG.get().getKey(); 726 } finally { 727 endRead(); 728 } 729 730 if (getLogger().isDebugEnabled()) { 731 getLogger().debug("Returning config for " + key + ": " + config); 732 } 733 return config; 734 } 735 736 /** 737 * Creates a new, uninitialized child configuration. 738 * 739 * @return the new child configuration 740 */ 741 private CombinedConfiguration createChildConfiguration() { 742 return new CombinedConfiguration(getNodeCombiner()); 743 } 744 745 /** 746 * Initializes a newly created child configuration. This method copies a bunch of settings from this instance to the 747 * child configuration. 748 * 749 * @param config the child configuration to be initialized 750 */ 751 private void initChildConfiguration(final CombinedConfiguration config) { 752 if (loggerName != null) { 753 config.setLogger(new ConfigurationLogger(loggerName)); 754 } 755 config.setExpressionEngine(this.getExpressionEngine()); 756 config.setConversionExpressionEngine(getConversionExpressionEngine()); 757 config.setListDelimiterHandler(getListDelimiterHandler()); 758 copyEventListeners(config); 759 for (final ConfigData data : configurations) { 760 config.addConfiguration(data.getConfiguration(), data.getName(), data.getAt()); 761 } 762 config.setSynchronizer(getSynchronizer()); 763 } 764 765 /** 766 * Creates a {@code ConfigurationInterpolator} instance for performing local variable substitutions. This implementation 767 * returns an object which shares the prefix lookups from this configuration's {@code ConfigurationInterpolator}, but 768 * does not define any other lookups. 769 * 770 * @return the {@code ConfigurationInterpolator} 771 */ 772 private ConfigurationInterpolator initLocalInterpolator() { 773 return new ConfigurationInterpolator() { 774 @Override 775 protected Lookup fetchLookupForPrefix(final String prefix) { 776 return ConfigurationInterpolator.nullSafeLookup(getInterpolator().getLookups().get(prefix)); 777 } 778 }; 779 } 780 781 /** 782 * Checks whether the current configuration is set. If not, a {@code CurrentConfigHolder} is now created and 783 * initialized, and associated with the current thread. The member for the current configuration is undefined if for the 784 * current key no configuration exists yet. 785 * 786 * @return the {@code CurrentConfigHolder} instance for the current thread 787 */ 788 private CurrentConfigHolder ensureCurrentConfiguration() { 789 CurrentConfigHolder cch = CURRENT_CONFIG.get(); 790 if (cch == null) { 791 final String key = String.valueOf(localSubst.interpolate(keyPattern)); 792 cch = new CurrentConfigHolder(key); 793 cch.setCurrentConfiguration(configs.get(key)); 794 CURRENT_CONFIG.set(cch); 795 } 796 return cch; 797 } 798 799 /** 800 * Internal class that identifies each Configuration. 801 */ 802 static class ConfigData { 803 /** Stores a reference to the configuration. */ 804 private final Configuration configuration; 805 806 /** Stores the name under which the configuration is stored. */ 807 private final String name; 808 809 /** Stores the at string. */ 810 private final String at; 811 812 /** 813 * Creates a new instance of {@code ConfigData} and initializes it. 814 * 815 * @param config the configuration 816 * @param n the name 817 * @param at the at position 818 */ 819 public ConfigData(final Configuration config, final String n, final String at) { 820 configuration = config; 821 name = n; 822 this.at = at; 823 } 824 825 /** 826 * Returns the stored configuration. 827 * 828 * @return the configuration 829 */ 830 public Configuration getConfiguration() { 831 return configuration; 832 } 833 834 /** 835 * Returns the configuration's name. 836 * 837 * @return the name 838 */ 839 public String getName() { 840 return name; 841 } 842 843 /** 844 * Returns the at position of this configuration. 845 * 846 * @return the at position 847 */ 848 public String getAt() { 849 return at; 850 } 851 852 } 853 854 /** 855 * A simple data class holding information about the current configuration while an operation for a thread is processed. 856 */ 857 private static class CurrentConfigHolder { 858 /** Stores the current configuration of the current thread. */ 859 private CombinedConfiguration currentConfiguration; 860 861 /** 862 * Stores the key of the configuration evaluated for the current thread at the beginning of an operation. 863 */ 864 private final String key; 865 866 /** A counter for reentrant locks. */ 867 private int lockCount; 868 869 /** 870 * Creates a new instance of {@code CurrentConfigHolder} and initializes it with the key for the current configuration. 871 * 872 * @param curKey the current key 873 */ 874 public CurrentConfigHolder(final String curKey) { 875 key = curKey; 876 } 877 878 /** 879 * Returns the current configuration. 880 * 881 * @return the current configuration 882 */ 883 public CombinedConfiguration getCurrentConfiguration() { 884 return currentConfiguration; 885 } 886 887 /** 888 * Sets the current configuration. 889 * 890 * @param currentConfiguration the current configuration 891 */ 892 public void setCurrentConfiguration(final CombinedConfiguration currentConfiguration) { 893 this.currentConfiguration = currentConfiguration; 894 } 895 896 /** 897 * Returns the current key. 898 * 899 * @return the current key 900 */ 901 public String getKey() { 902 return key; 903 } 904 905 /** 906 * Increments the lock counter. 907 */ 908 public void incrementLockCount() { 909 lockCount++; 910 } 911 912 /** 913 * Decrements the lock counter and checks whether it has reached 0. In this cause, the operation is complete, and the 914 * lock can be released. 915 * 916 * @return <b>true</b> if the lock count reaches 0, <b>false</b> otherwise 917 */ 918 public boolean decrementLockCountAndCheckRelease() { 919 return --lockCount == 0; 920 } 921 } 922}