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