001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.configuration; 018 019 import java.math.BigDecimal; 020 import java.math.BigInteger; 021 import java.util.ArrayList; 022 import java.util.Collection; 023 import java.util.HashMap; 024 import java.util.Iterator; 025 import java.util.List; 026 import java.util.Map; 027 import java.util.Properties; 028 import java.util.Set; 029 030 import org.apache.commons.configuration.event.ConfigurationErrorListener; 031 import org.apache.commons.configuration.event.ConfigurationListener; 032 import org.apache.commons.configuration.interpol.ConfigurationInterpolator; 033 import org.apache.commons.configuration.tree.ConfigurationNode; 034 import org.apache.commons.configuration.tree.ExpressionEngine; 035 import org.apache.commons.configuration.tree.NodeCombiner; 036 import org.apache.commons.lang.text.StrSubstitutor; 037 import org.apache.commons.logging.Log; 038 import org.apache.commons.logging.LogFactory; 039 040 /** 041 * DynamicCombinedConfiguration allows a set of CombinedConfigurations to be used. Each CombinedConfiguration 042 * is referenced by a key that is dynamically constructed from a key pattern on each call. The key pattern 043 * will be resolved using the configured ConfigurationInterpolator. 044 * @since 1.6 045 * @author <a 046 * href="http://commons.apache.org/configuration/team-list.html">Commons 047 * Configuration team</a> 048 * @version $Id: DynamicCombinedConfiguration.java 1158121 2011-08-16 06:21:42Z oheger $ 049 */ 050 public class DynamicCombinedConfiguration extends CombinedConfiguration 051 { 052 /** 053 * Prevent recursion while resolving unprefixed properties. 054 */ 055 private static ThreadLocal recursive = new ThreadLocal() 056 { 057 protected synchronized Object initialValue() 058 { 059 return Boolean.FALSE; 060 } 061 }; 062 063 /** The CombinedConfigurations */ 064 private Map configs = new HashMap(); 065 066 /** Stores a list with the contained configurations. */ 067 private List configurations = new ArrayList(); 068 069 /** Stores a map with the named configurations. */ 070 private Map namedConfigurations = new HashMap(); 071 072 /** The key pattern for the CombinedConfiguration map */ 073 private String keyPattern; 074 075 /** Stores the combiner. */ 076 private NodeCombiner nodeCombiner; 077 078 /** The name of the logger to use for each CombinedConfiguration */ 079 private String loggerName = DynamicCombinedConfiguration.class.getName(); 080 081 /** The object for handling variable substitution in key patterns. */ 082 private StrSubstitutor localSubst = new StrSubstitutor(new ConfigurationInterpolator()); 083 084 /** 085 * Creates a new instance of <code>CombinedConfiguration</code> and 086 * initializes the combiner to be used. 087 * 088 * @param comb the node combiner (can be <b>null</b>, then a union combiner 089 * is used as default) 090 */ 091 public DynamicCombinedConfiguration(NodeCombiner comb) 092 { 093 super(); 094 setNodeCombiner(comb); 095 setIgnoreReloadExceptions(false); 096 setLogger(LogFactory.getLog(DynamicCombinedConfiguration.class)); 097 } 098 099 /** 100 * Creates a new instance of <code>CombinedConfiguration</code> that uses 101 * a union combiner. 102 * 103 * @see org.apache.commons.configuration.tree.UnionCombiner 104 */ 105 public DynamicCombinedConfiguration() 106 { 107 super(); 108 setIgnoreReloadExceptions(false); 109 setLogger(LogFactory.getLog(DynamicCombinedConfiguration.class)); 110 } 111 112 public void setKeyPattern(String pattern) 113 { 114 this.keyPattern = pattern; 115 } 116 117 public String getKeyPattern() 118 { 119 return this.keyPattern; 120 } 121 122 /** 123 * Set the name of the Logger to use on each CombinedConfiguration. 124 * @param name The Logger name. 125 */ 126 public void setLoggerName(String name) 127 { 128 this.loggerName = name; 129 } 130 131 /** 132 * Returns the node combiner that is used for creating the combined node 133 * structure. 134 * 135 * @return the node combiner 136 */ 137 public NodeCombiner getNodeCombiner() 138 { 139 return nodeCombiner; 140 } 141 142 /** 143 * Sets the node combiner. This object will be used when the combined node 144 * structure is to be constructed. It must not be <b>null</b>, otherwise an 145 * <code>IllegalArgumentException</code> exception is thrown. Changing the 146 * node combiner causes an invalidation of this combined configuration, so 147 * that the new combiner immediately takes effect. 148 * 149 * @param nodeCombiner the node combiner 150 */ 151 public void setNodeCombiner(NodeCombiner nodeCombiner) 152 { 153 if (nodeCombiner == null) 154 { 155 throw new IllegalArgumentException( 156 "Node combiner must not be null!"); 157 } 158 this.nodeCombiner = nodeCombiner; 159 invalidateAll(); 160 } 161 /** 162 * Adds a new configuration to this combined configuration. It is possible 163 * (but not mandatory) to give the new configuration a name. This name must 164 * be unique, otherwise a <code>ConfigurationRuntimeException</code> will 165 * be thrown. With the optional <code>at</code> argument you can specify 166 * where in the resulting node structure the content of the added 167 * configuration should appear. This is a string that uses dots as property 168 * delimiters (independent on the current expression engine). For instance 169 * if you pass in the string <code>"database.tables"</code>, 170 * all properties of the added configuration will occur in this branch. 171 * 172 * @param config the configuration to add (must not be <b>null</b>) 173 * @param name the name of this configuration (can be <b>null</b>) 174 * @param at the position of this configuration in the combined tree (can be 175 * <b>null</b>) 176 */ 177 public void addConfiguration(AbstractConfiguration config, String name, 178 String at) 179 { 180 ConfigData cd = new ConfigData(config, name, at); 181 configurations.add(cd); 182 if (name != null) 183 { 184 namedConfigurations.put(name, config); 185 } 186 } 187 /** 188 * Returns the number of configurations that are contained in this combined 189 * configuration. 190 * 191 * @return the number of contained configurations 192 */ 193 public int getNumberOfConfigurations() 194 { 195 return configurations.size(); 196 } 197 198 /** 199 * Returns the configuration at the specified index. The contained 200 * configurations are numbered in the order they were added to this combined 201 * configuration. The index of the first configuration is 0. 202 * 203 * @param index the index 204 * @return the configuration at this index 205 */ 206 public Configuration getConfiguration(int index) 207 { 208 ConfigData cd = (ConfigData) configurations.get(index); 209 return cd.getConfiguration(); 210 } 211 212 /** 213 * Returns the configuration with the given name. This can be <b>null</b> 214 * if no such configuration exists. 215 * 216 * @param name the name of the configuration 217 * @return the configuration with this name 218 */ 219 public Configuration getConfiguration(String name) 220 { 221 return (Configuration) namedConfigurations.get(name); 222 } 223 224 /** 225 * Returns a set with the names of all configurations contained in this 226 * combined configuration. Of course here are only these configurations 227 * listed, for which a name was specified when they were added. 228 * 229 * @return a set with the names of the contained configurations (never 230 * <b>null</b>) 231 */ 232 public Set getConfigurationNames() 233 { 234 return namedConfigurations.keySet(); 235 } 236 237 /** 238 * Removes the configuration with the specified name. 239 * 240 * @param name the name of the configuration to be removed 241 * @return the removed configuration (<b>null</b> if this configuration 242 * was not found) 243 */ 244 public Configuration removeConfiguration(String name) 245 { 246 Configuration conf = getConfiguration(name); 247 if (conf != null) 248 { 249 removeConfiguration(conf); 250 } 251 return conf; 252 } 253 254 /** 255 * Removes the specified configuration from this combined configuration. 256 * 257 * @param config the configuration to be removed 258 * @return a flag whether this configuration was found and could be removed 259 */ 260 public boolean removeConfiguration(Configuration config) 261 { 262 for (int index = 0; index < getNumberOfConfigurations(); index++) 263 { 264 if (((ConfigData) configurations.get(index)).getConfiguration() == config) 265 { 266 removeConfigurationAt(index); 267 268 } 269 } 270 271 return super.removeConfiguration(config); 272 } 273 274 /** 275 * Removes the configuration at the specified index. 276 * 277 * @param index the index 278 * @return the removed configuration 279 */ 280 public Configuration removeConfigurationAt(int index) 281 { 282 ConfigData cd = (ConfigData) configurations.remove(index); 283 if (cd.getName() != null) 284 { 285 namedConfigurations.remove(cd.getName()); 286 } 287 return super.removeConfigurationAt(index); 288 } 289 /** 290 * Returns the configuration root node of this combined configuration. This 291 * method will construct a combined node structure using the current node 292 * combiner if necessary. 293 * 294 * @return the combined root node 295 */ 296 public ConfigurationNode getRootNode() 297 { 298 return getCurrentConfig().getRootNode(); 299 } 300 301 public void setRootNode(ConfigurationNode rootNode) 302 { 303 if (configs != null) 304 { 305 this.getCurrentConfig().setRootNode(rootNode); 306 } 307 else 308 { 309 super.setRootNode(rootNode); 310 } 311 } 312 313 public void addProperty(String key, Object value) 314 { 315 this.getCurrentConfig().addProperty(key, value); 316 } 317 318 public void clear() 319 { 320 if (configs != null) 321 { 322 this.getCurrentConfig().clear(); 323 } 324 } 325 326 public void clearProperty(String key) 327 { 328 this.getCurrentConfig().clearProperty(key); 329 } 330 331 public boolean containsKey(String key) 332 { 333 return this.getCurrentConfig().containsKey(key); 334 } 335 336 public BigDecimal getBigDecimal(String key, BigDecimal defaultValue) 337 { 338 return this.getCurrentConfig().getBigDecimal(key, defaultValue); 339 } 340 341 public BigDecimal getBigDecimal(String key) 342 { 343 return this.getCurrentConfig().getBigDecimal(key); 344 } 345 346 public BigInteger getBigInteger(String key, BigInteger defaultValue) 347 { 348 return this.getCurrentConfig().getBigInteger(key, defaultValue); 349 } 350 351 public BigInteger getBigInteger(String key) 352 { 353 return this.getCurrentConfig().getBigInteger(key); 354 } 355 356 public boolean getBoolean(String key, boolean defaultValue) 357 { 358 return this.getCurrentConfig().getBoolean(key, defaultValue); 359 } 360 361 public Boolean getBoolean(String key, Boolean defaultValue) 362 { 363 return this.getCurrentConfig().getBoolean(key, defaultValue); 364 } 365 366 public boolean getBoolean(String key) 367 { 368 return this.getCurrentConfig().getBoolean(key); 369 } 370 371 public byte getByte(String key, byte defaultValue) 372 { 373 return this.getCurrentConfig().getByte(key, defaultValue); 374 } 375 376 public Byte getByte(String key, Byte defaultValue) 377 { 378 return this.getCurrentConfig().getByte(key, defaultValue); 379 } 380 381 public byte getByte(String key) 382 { 383 return this.getCurrentConfig().getByte(key); 384 } 385 386 public double getDouble(String key, double defaultValue) 387 { 388 return this.getCurrentConfig().getDouble(key, defaultValue); 389 } 390 391 public Double getDouble(String key, Double defaultValue) 392 { 393 return this.getCurrentConfig().getDouble(key, defaultValue); 394 } 395 396 public double getDouble(String key) 397 { 398 return this.getCurrentConfig().getDouble(key); 399 } 400 401 public float getFloat(String key, float defaultValue) 402 { 403 return this.getCurrentConfig().getFloat(key, defaultValue); 404 } 405 406 public Float getFloat(String key, Float defaultValue) 407 { 408 return this.getCurrentConfig().getFloat(key, defaultValue); 409 } 410 411 public float getFloat(String key) 412 { 413 return this.getCurrentConfig().getFloat(key); 414 } 415 416 public int getInt(String key, int defaultValue) 417 { 418 return this.getCurrentConfig().getInt(key, defaultValue); 419 } 420 421 public int getInt(String key) 422 { 423 return this.getCurrentConfig().getInt(key); 424 } 425 426 public Integer getInteger(String key, Integer defaultValue) 427 { 428 return this.getCurrentConfig().getInteger(key, defaultValue); 429 } 430 431 public Iterator getKeys() 432 { 433 return this.getCurrentConfig().getKeys(); 434 } 435 436 public Iterator getKeys(String prefix) 437 { 438 return this.getCurrentConfig().getKeys(prefix); 439 } 440 441 public List getList(String key, List defaultValue) 442 { 443 return this.getCurrentConfig().getList(key, defaultValue); 444 } 445 446 public List getList(String key) 447 { 448 return this.getCurrentConfig().getList(key); 449 } 450 451 public long getLong(String key, long defaultValue) 452 { 453 return this.getCurrentConfig().getLong(key, defaultValue); 454 } 455 456 public Long getLong(String key, Long defaultValue) 457 { 458 return this.getCurrentConfig().getLong(key, defaultValue); 459 } 460 461 public long getLong(String key) 462 { 463 return this.getCurrentConfig().getLong(key); 464 } 465 466 public Properties getProperties(String key) 467 { 468 return this.getCurrentConfig().getProperties(key); 469 } 470 471 public Object getProperty(String key) 472 { 473 return this.getCurrentConfig().getProperty(key); 474 } 475 476 public short getShort(String key, short defaultValue) 477 { 478 return this.getCurrentConfig().getShort(key, defaultValue); 479 } 480 481 public Short getShort(String key, Short defaultValue) 482 { 483 return this.getCurrentConfig().getShort(key, defaultValue); 484 } 485 486 public short getShort(String key) 487 { 488 return this.getCurrentConfig().getShort(key); 489 } 490 491 public String getString(String key, String defaultValue) 492 { 493 return this.getCurrentConfig().getString(key, defaultValue); 494 } 495 496 public String getString(String key) 497 { 498 return this.getCurrentConfig().getString(key); 499 } 500 501 public String[] getStringArray(String key) 502 { 503 return this.getCurrentConfig().getStringArray(key); 504 } 505 506 public boolean isEmpty() 507 { 508 return this.getCurrentConfig().isEmpty(); 509 } 510 511 public void setProperty(String key, Object value) 512 { 513 if (configs != null) 514 { 515 this.getCurrentConfig().setProperty(key, value); 516 } 517 } 518 519 public Configuration subset(String prefix) 520 { 521 return this.getCurrentConfig().subset(prefix); 522 } 523 524 public Node getRoot() 525 { 526 return this.getCurrentConfig().getRoot(); 527 } 528 529 public void setRoot(Node node) 530 { 531 if (configs != null) 532 { 533 this.getCurrentConfig().setRoot(node); 534 } 535 else 536 { 537 super.setRoot(node); 538 } 539 } 540 541 public ExpressionEngine getExpressionEngine() 542 { 543 return super.getExpressionEngine(); 544 } 545 546 public void setExpressionEngine(ExpressionEngine expressionEngine) 547 { 548 super.setExpressionEngine(expressionEngine); 549 } 550 551 public void addNodes(String key, Collection nodes) 552 { 553 this.getCurrentConfig().addNodes(key, nodes); 554 } 555 556 public SubnodeConfiguration configurationAt(String key, boolean supportUpdates) 557 { 558 return this.getCurrentConfig().configurationAt(key, supportUpdates); 559 } 560 561 public SubnodeConfiguration configurationAt(String key) 562 { 563 return this.getCurrentConfig().configurationAt(key); 564 } 565 566 public List configurationsAt(String key) 567 { 568 return this.getCurrentConfig().configurationsAt(key); 569 } 570 571 public void clearTree(String key) 572 { 573 this.getCurrentConfig().clearTree(key); 574 } 575 576 public int getMaxIndex(String key) 577 { 578 return this.getCurrentConfig().getMaxIndex(key); 579 } 580 581 public Configuration interpolatedConfiguration() 582 { 583 return this.getCurrentConfig().interpolatedConfiguration(); 584 } 585 586 587 /** 588 * Returns the configuration source, in which the specified key is defined. 589 * This method will determine the configuration node that is identified by 590 * the given key. The following constellations are possible: 591 * <ul> 592 * <li>If no node object is found for this key, <b>null</b> is returned.</li> 593 * <li>If the key maps to multiple nodes belonging to different 594 * configuration sources, a <code>IllegalArgumentException</code> is 595 * thrown (in this case no unique source can be determined).</li> 596 * <li>If exactly one node is found for the key, the (child) configuration 597 * object, to which the node belongs is determined and returned.</li> 598 * <li>For keys that have been added directly to this combined 599 * configuration and that do not belong to the namespaces defined by 600 * existing child configurations this configuration will be returned.</li> 601 * </ul> 602 * 603 * @param key the key of a configuration property 604 * @return the configuration, to which this property belongs or <b>null</b> 605 * if the key cannot be resolved 606 * @throws IllegalArgumentException if the key maps to multiple properties 607 * and the source cannot be determined, or if the key is <b>null</b> 608 */ 609 public Configuration getSource(String key) 610 { 611 if (key == null) 612 { 613 throw new IllegalArgumentException("Key must not be null!"); 614 } 615 return getCurrentConfig().getSource(key); 616 } 617 618 public void addConfigurationListener(ConfigurationListener l) 619 { 620 super.addConfigurationListener(l); 621 622 Iterator iter = configs.values().iterator(); 623 while (iter.hasNext()) 624 { 625 CombinedConfiguration config = (CombinedConfiguration) iter.next(); 626 config.addConfigurationListener(l); 627 } 628 } 629 630 public boolean removeConfigurationListener(ConfigurationListener l) 631 { 632 Iterator iter = configs.values().iterator(); 633 while (iter.hasNext()) 634 { 635 CombinedConfiguration config = (CombinedConfiguration) iter.next(); 636 config.removeConfigurationListener(l); 637 } 638 return super.removeConfigurationListener(l); 639 } 640 641 public Collection getConfigurationListeners() 642 { 643 return super.getConfigurationListeners(); 644 } 645 646 public void clearConfigurationListeners() 647 { 648 Iterator iter = configs.values().iterator(); 649 while (iter.hasNext()) 650 { 651 CombinedConfiguration config = (CombinedConfiguration) iter.next(); 652 config.clearConfigurationListeners(); 653 } 654 super.clearConfigurationListeners(); 655 } 656 657 public void addErrorListener(ConfigurationErrorListener l) 658 { 659 Iterator iter = configs.values().iterator(); 660 while (iter.hasNext()) 661 { 662 CombinedConfiguration config = (CombinedConfiguration) iter.next(); 663 config.addErrorListener(l); 664 } 665 super.addErrorListener(l); 666 } 667 668 public boolean removeErrorListener(ConfigurationErrorListener l) 669 { 670 Iterator iter = configs.values().iterator(); 671 while (iter.hasNext()) 672 { 673 CombinedConfiguration config = (CombinedConfiguration) iter.next(); 674 config.removeErrorListener(l); 675 } 676 return super.removeErrorListener(l); 677 } 678 679 public void clearErrorListeners() 680 { 681 Iterator iter = configs.values().iterator(); 682 while (iter.hasNext()) 683 { 684 CombinedConfiguration config = (CombinedConfiguration) iter.next(); 685 config.clearErrorListeners(); 686 } 687 super.clearErrorListeners(); 688 } 689 690 public Collection getErrorListeners() 691 { 692 return super.getErrorListeners(); 693 } 694 695 696 697 /** 698 * Returns a copy of this object. This implementation performs a deep clone, 699 * i.e. all contained configurations will be cloned, too. For this to work, 700 * all contained configurations must be cloneable. Registered event 701 * listeners won't be cloned. The clone will use the same node combiner than 702 * the original. 703 * 704 * @return the copied object 705 */ 706 public Object clone() 707 { 708 return super.clone(); 709 } 710 711 712 713 /** 714 * Invalidates the current combined configuration. This means that the next time a 715 * property is accessed the combined node structure must be re-constructed. 716 * Invalidation of a combined configuration also means that an event of type 717 * <code>EVENT_COMBINED_INVALIDATE</code> is fired. Note that while other 718 * events most times appear twice (once before and once after an update), 719 * this event is only fired once (after update). 720 */ 721 public void invalidate() 722 { 723 getCurrentConfig().invalidate(); 724 } 725 726 public void invalidateAll() 727 { 728 if (configs == null) 729 { 730 return; 731 } 732 Iterator iter = configs.values().iterator(); 733 while (iter.hasNext()) 734 { 735 CombinedConfiguration config = (CombinedConfiguration) iter.next(); 736 config.invalidate(); 737 } 738 } 739 740 /* 741 * Don't allow resolveContainerStore to be called recursively. 742 * @param key The key to resolve. 743 * @return The value of the key. 744 */ 745 protected Object resolveContainerStore(String key) 746 { 747 if (((Boolean) recursive.get()).booleanValue()) 748 { 749 return null; 750 } 751 recursive.set(Boolean.TRUE); 752 try 753 { 754 return super.resolveContainerStore(key); 755 } 756 finally 757 { 758 recursive.set(Boolean.FALSE); 759 } 760 } 761 762 private CombinedConfiguration getCurrentConfig() 763 { 764 String key = localSubst.replace(keyPattern); 765 CombinedConfiguration config; 766 synchronized (getNodeCombiner()) 767 { 768 config = (CombinedConfiguration) configs.get(key); 769 if (config == null) 770 { 771 config = new CombinedConfiguration(getNodeCombiner()); 772 if (loggerName != null) 773 { 774 Log log = LogFactory.getLog(loggerName); 775 if (log != null) 776 { 777 config.setLogger(log); 778 } 779 } 780 config.setIgnoreReloadExceptions(isIgnoreReloadExceptions()); 781 config.setExpressionEngine(this.getExpressionEngine()); 782 config.setDelimiterParsingDisabled(isDelimiterParsingDisabled()); 783 config.setConversionExpressionEngine(getConversionExpressionEngine()); 784 config.setListDelimiter(getListDelimiter()); 785 Iterator iter = getErrorListeners().iterator(); 786 while (iter.hasNext()) 787 { 788 ConfigurationErrorListener listener = (ConfigurationErrorListener) iter.next(); 789 config.addErrorListener(listener); 790 } 791 iter = getConfigurationListeners().iterator(); 792 while (iter.hasNext()) 793 { 794 ConfigurationListener listener = (ConfigurationListener) iter.next(); 795 config.addConfigurationListener(listener); 796 } 797 config.setForceReloadCheck(isForceReloadCheck()); 798 iter = configurations.iterator(); 799 while (iter.hasNext()) 800 { 801 ConfigData data = (ConfigData) iter.next(); 802 config.addConfiguration(data.getConfiguration(), data.getName(), 803 data.getAt()); 804 } 805 configs.put(key, config); 806 } 807 } 808 if (getLogger().isDebugEnabled()) 809 { 810 getLogger().debug("Returning config for " + key + ": " + config); 811 } 812 return config; 813 } 814 815 /** 816 * Internal class that identifies each Configuration. 817 */ 818 static class ConfigData 819 { 820 /** Stores a reference to the configuration. */ 821 private AbstractConfiguration configuration; 822 823 /** Stores the name under which the configuration is stored. */ 824 private String name; 825 826 /** Stores the at string.*/ 827 private String at; 828 829 /** 830 * Creates a new instance of <code>ConfigData</code> and initializes 831 * it. 832 * 833 * @param config the configuration 834 * @param n the name 835 * @param at the at position 836 */ 837 public ConfigData(AbstractConfiguration config, String n, String at) 838 { 839 configuration = config; 840 name = n; 841 this.at = at; 842 } 843 844 /** 845 * Returns the stored configuration. 846 * 847 * @return the configuration 848 */ 849 public AbstractConfiguration getConfiguration() 850 { 851 return configuration; 852 } 853 854 /** 855 * Returns the configuration's name. 856 * 857 * @return the name 858 */ 859 public String getName() 860 { 861 return name; 862 } 863 864 /** 865 * Returns the at position of this configuration. 866 * 867 * @return the at position 868 */ 869 public String getAt() 870 { 871 return at; 872 } 873 874 } 875 }