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.io.File; 020 import java.io.InputStream; 021 import java.io.OutputStream; 022 import java.io.Reader; 023 import java.io.Writer; 024 import java.math.BigDecimal; 025 import java.math.BigInteger; 026 import java.net.URL; 027 import java.util.Collection; 028 import java.util.Iterator; 029 import java.util.List; 030 import java.util.Properties; 031 import java.util.concurrent.ConcurrentHashMap; 032 import java.util.concurrent.ConcurrentMap; 033 034 import org.apache.commons.beanutils.BeanUtils; 035 import org.apache.commons.configuration.event.ConfigurationErrorEvent; 036 import org.apache.commons.configuration.event.ConfigurationErrorListener; 037 import org.apache.commons.configuration.event.ConfigurationEvent; 038 import org.apache.commons.configuration.event.ConfigurationListener; 039 import org.apache.commons.configuration.interpol.ConfigurationInterpolator; 040 import org.apache.commons.configuration.reloading.ReloadingStrategy; 041 import org.apache.commons.configuration.resolver.EntityResolverSupport; 042 import org.apache.commons.configuration.tree.ConfigurationNode; 043 import org.apache.commons.configuration.tree.ExpressionEngine; 044 import org.apache.commons.lang.text.StrSubstitutor; 045 import org.apache.commons.logging.Log; 046 import org.apache.commons.logging.LogFactory; 047 import org.xml.sax.EntityResolver; 048 import org.xml.sax.SAXParseException; 049 050 /** 051 * This class provides access to multiple configuration files that reside in a location that 052 * can be specified by a pattern allowing applications to be multi-tenant. For example, 053 * providing a pattern of "file:///opt/config/${product}/${client}/config.xml" will result in 054 * "product" and "client" being resolved on every call. The configuration resulting from the 055 * resolved pattern will be saved for future access. 056 * @since 1.6 057 * @author <a 058 * href="http://commons.apache.org/configuration/team-list.html">Commons 059 * Configuration team</a> 060 * @version $Id: MultiFileHierarchicalConfiguration.java 1210174 2011-12-04 18:45:00Z oheger $ 061 */ 062 public class MultiFileHierarchicalConfiguration extends AbstractHierarchicalFileConfiguration 063 implements ConfigurationListener, ConfigurationErrorListener, EntityResolverSupport 064 { 065 /** 066 * Prevent recursion while resolving unprefixed properties. 067 */ 068 private static ThreadLocal<Boolean> recursive = new ThreadLocal<Boolean>() 069 { 070 @Override 071 protected synchronized Boolean initialValue() 072 { 073 return Boolean.FALSE; 074 } 075 }; 076 077 /** Map of configurations */ 078 private final ConcurrentMap<String, XMLConfiguration> configurationsMap = 079 new ConcurrentHashMap<String, XMLConfiguration>(); 080 081 /** key pattern for configurationsMap */ 082 private String pattern; 083 084 /** True if the constructor has finished */ 085 private boolean init; 086 087 /** Return an empty configuration if loading fails */ 088 private boolean ignoreException = true; 089 090 /** Capture the schema validation setting */ 091 private boolean schemaValidation; 092 093 /** Stores a flag whether DTD or Schema validation should be performed.*/ 094 private boolean validating; 095 096 /** A flag whether attribute splitting is disabled.*/ 097 private boolean attributeSplittingDisabled; 098 099 /** The Logger name to use */ 100 private String loggerName = MultiFileHierarchicalConfiguration.class.getName(); 101 102 /** The Reloading strategy to use on created configurations */ 103 private ReloadingStrategy fileStrategy; 104 105 /** The EntityResolver */ 106 private EntityResolver entityResolver; 107 108 /** The internally used helper object for variable substitution. */ 109 private StrSubstitutor localSubst = new StrSubstitutor(new ConfigurationInterpolator()); 110 111 /** 112 * Default Constructor. 113 */ 114 public MultiFileHierarchicalConfiguration() 115 { 116 super(); 117 this.init = true; 118 setLogger(LogFactory.getLog(loggerName)); 119 } 120 121 /** 122 * Construct the configuration with the specified pattern. 123 * @param pathPattern The pattern to use to locate configuration files. 124 */ 125 public MultiFileHierarchicalConfiguration(String pathPattern) 126 { 127 super(); 128 this.pattern = pathPattern; 129 this.init = true; 130 setLogger(LogFactory.getLog(loggerName)); 131 } 132 133 public void setLoggerName(String name) 134 { 135 this.loggerName = name; 136 } 137 138 /** 139 * Set the File pattern 140 * @param pathPattern The pattern for the path to the configuration. 141 */ 142 public void setFilePattern(String pathPattern) 143 { 144 this.pattern = pathPattern; 145 } 146 147 public boolean isSchemaValidation() 148 { 149 return schemaValidation; 150 } 151 152 public void setSchemaValidation(boolean schemaValidation) 153 { 154 this.schemaValidation = schemaValidation; 155 } 156 157 public boolean isValidating() 158 { 159 return validating; 160 } 161 162 public void setValidating(boolean validating) 163 { 164 this.validating = validating; 165 } 166 167 public boolean isAttributeSplittingDisabled() 168 { 169 return attributeSplittingDisabled; 170 } 171 172 public void setAttributeSplittingDisabled(boolean attributeSplittingDisabled) 173 { 174 this.attributeSplittingDisabled = attributeSplittingDisabled; 175 } 176 177 @Override 178 public ReloadingStrategy getReloadingStrategy() 179 { 180 return fileStrategy; 181 } 182 183 @Override 184 public void setReloadingStrategy(ReloadingStrategy strategy) 185 { 186 this.fileStrategy = strategy; 187 } 188 189 public void setEntityResolver(EntityResolver entityResolver) 190 { 191 this.entityResolver = entityResolver; 192 } 193 194 public EntityResolver getEntityResolver() 195 { 196 return this.entityResolver; 197 } 198 199 /** 200 * Set to true if an empty Configuration should be returned when loading fails. If 201 * false an exception will be thrown. 202 * @param ignoreException The ignore value. 203 */ 204 public void setIgnoreException(boolean ignoreException) 205 { 206 this.ignoreException = ignoreException; 207 } 208 209 @Override 210 public void addProperty(String key, Object value) 211 { 212 this.getConfiguration().addProperty(key, value); 213 } 214 215 @Override 216 public void clear() 217 { 218 this.getConfiguration().clear(); 219 } 220 221 @Override 222 public void clearProperty(String key) 223 { 224 this.getConfiguration().clearProperty(key); 225 } 226 227 @Override 228 public boolean containsKey(String key) 229 { 230 return this.getConfiguration().containsKey(key); 231 } 232 233 @Override 234 public BigDecimal getBigDecimal(String key, BigDecimal defaultValue) 235 { 236 return this.getConfiguration().getBigDecimal(key, defaultValue); 237 } 238 239 @Override 240 public BigDecimal getBigDecimal(String key) 241 { 242 return this.getConfiguration().getBigDecimal(key); 243 } 244 245 @Override 246 public BigInteger getBigInteger(String key, BigInteger defaultValue) 247 { 248 return this.getConfiguration().getBigInteger(key, defaultValue); 249 } 250 251 @Override 252 public BigInteger getBigInteger(String key) 253 { 254 return this.getConfiguration().getBigInteger(key); 255 } 256 257 @Override 258 public boolean getBoolean(String key, boolean defaultValue) 259 { 260 return this.getConfiguration().getBoolean(key, defaultValue); 261 } 262 263 @Override 264 public Boolean getBoolean(String key, Boolean defaultValue) 265 { 266 return this.getConfiguration().getBoolean(key, defaultValue); 267 } 268 269 @Override 270 public boolean getBoolean(String key) 271 { 272 return this.getConfiguration().getBoolean(key); 273 } 274 275 @Override 276 public byte getByte(String key, byte defaultValue) 277 { 278 return this.getConfiguration().getByte(key, defaultValue); 279 } 280 281 @Override 282 public Byte getByte(String key, Byte defaultValue) 283 { 284 return this.getConfiguration().getByte(key, defaultValue); 285 } 286 287 @Override 288 public byte getByte(String key) 289 { 290 return this.getConfiguration().getByte(key); 291 } 292 293 @Override 294 public double getDouble(String key, double defaultValue) 295 { 296 return this.getConfiguration().getDouble(key, defaultValue); 297 } 298 299 @Override 300 public Double getDouble(String key, Double defaultValue) 301 { 302 return this.getConfiguration().getDouble(key, defaultValue); 303 } 304 305 @Override 306 public double getDouble(String key) 307 { 308 return this.getConfiguration().getDouble(key); 309 } 310 311 @Override 312 public float getFloat(String key, float defaultValue) 313 { 314 return this.getConfiguration().getFloat(key, defaultValue); 315 } 316 317 @Override 318 public Float getFloat(String key, Float defaultValue) 319 { 320 return this.getConfiguration().getFloat(key, defaultValue); 321 } 322 323 @Override 324 public float getFloat(String key) 325 { 326 return this.getConfiguration().getFloat(key); 327 } 328 329 @Override 330 public int getInt(String key, int defaultValue) 331 { 332 return this.getConfiguration().getInt(key, defaultValue); 333 } 334 335 @Override 336 public int getInt(String key) 337 { 338 return this.getConfiguration().getInt(key); 339 } 340 341 @Override 342 public Integer getInteger(String key, Integer defaultValue) 343 { 344 return this.getConfiguration().getInteger(key, defaultValue); 345 } 346 347 @Override 348 public Iterator<String> getKeys() 349 { 350 return this.getConfiguration().getKeys(); 351 } 352 353 @Override 354 public Iterator<String> getKeys(String prefix) 355 { 356 return this.getConfiguration().getKeys(prefix); 357 } 358 359 @Override 360 public List<Object> getList(String key, List<Object> defaultValue) 361 { 362 return this.getConfiguration().getList(key, defaultValue); 363 } 364 365 @Override 366 public List<Object> getList(String key) 367 { 368 return this.getConfiguration().getList(key); 369 } 370 371 @Override 372 public long getLong(String key, long defaultValue) 373 { 374 return this.getConfiguration().getLong(key, defaultValue); 375 } 376 377 @Override 378 public Long getLong(String key, Long defaultValue) 379 { 380 return this.getConfiguration().getLong(key, defaultValue); 381 } 382 383 @Override 384 public long getLong(String key) 385 { 386 return this.getConfiguration().getLong(key); 387 } 388 389 @Override 390 public Properties getProperties(String key) 391 { 392 return this.getConfiguration().getProperties(key); 393 } 394 395 @Override 396 public Object getProperty(String key) 397 { 398 return this.getConfiguration().getProperty(key); 399 } 400 401 @Override 402 public short getShort(String key, short defaultValue) 403 { 404 return this.getConfiguration().getShort(key, defaultValue); 405 } 406 407 @Override 408 public Short getShort(String key, Short defaultValue) 409 { 410 return this.getConfiguration().getShort(key, defaultValue); 411 } 412 413 @Override 414 public short getShort(String key) 415 { 416 return this.getConfiguration().getShort(key); 417 } 418 419 @Override 420 public String getString(String key, String defaultValue) 421 { 422 return this.getConfiguration().getString(key, defaultValue); 423 } 424 425 @Override 426 public String getString(String key) 427 { 428 return this.getConfiguration().getString(key); 429 } 430 431 @Override 432 public String[] getStringArray(String key) 433 { 434 return this.getConfiguration().getStringArray(key); 435 } 436 437 @Override 438 public boolean isEmpty() 439 { 440 return this.getConfiguration().isEmpty(); 441 } 442 443 @Override 444 public void setProperty(String key, Object value) 445 { 446 if (init) 447 { 448 this.getConfiguration().setProperty(key, value); 449 } 450 } 451 452 @Override 453 public Configuration subset(String prefix) 454 { 455 return this.getConfiguration().subset(prefix); 456 } 457 458 @Override 459 public Object getReloadLock() 460 { 461 return this.getConfiguration().getReloadLock(); 462 } 463 464 @Override 465 public Node getRoot() 466 { 467 return this.getConfiguration().getRoot(); 468 } 469 470 @Override 471 public void setRoot(Node node) 472 { 473 if (init) 474 { 475 this.getConfiguration().setRoot(node); 476 } 477 else 478 { 479 super.setRoot(node); 480 } 481 } 482 483 @Override 484 public ConfigurationNode getRootNode() 485 { 486 return this.getConfiguration().getRootNode(); 487 } 488 489 @Override 490 public void setRootNode(ConfigurationNode rootNode) 491 { 492 if (init) 493 { 494 this.getConfiguration().setRootNode(rootNode); 495 } 496 else 497 { 498 super.setRootNode(rootNode); 499 } 500 } 501 502 @Override 503 public ExpressionEngine getExpressionEngine() 504 { 505 return super.getExpressionEngine(); 506 } 507 508 @Override 509 public void setExpressionEngine(ExpressionEngine expressionEngine) 510 { 511 super.setExpressionEngine(expressionEngine); 512 } 513 514 @Override 515 public void addNodes(String key, Collection<? extends ConfigurationNode> nodes) 516 { 517 this.getConfiguration().addNodes(key, nodes); 518 } 519 520 @Override 521 public SubnodeConfiguration configurationAt(String key, boolean supportUpdates) 522 { 523 return this.getConfiguration().configurationAt(key, supportUpdates); 524 } 525 526 @Override 527 public SubnodeConfiguration configurationAt(String key) 528 { 529 return this.getConfiguration().configurationAt(key); 530 } 531 532 @Override 533 public List<HierarchicalConfiguration> configurationsAt(String key) 534 { 535 return this.getConfiguration().configurationsAt(key); 536 } 537 538 @Override 539 public void clearTree(String key) 540 { 541 this.getConfiguration().clearTree(key); 542 } 543 544 @Override 545 public int getMaxIndex(String key) 546 { 547 return this.getConfiguration().getMaxIndex(key); 548 } 549 550 @Override 551 public Configuration interpolatedConfiguration() 552 { 553 return this.getConfiguration().interpolatedConfiguration(); 554 } 555 556 @Override 557 public void addConfigurationListener(ConfigurationListener l) 558 { 559 super.addConfigurationListener(l); 560 } 561 562 @Override 563 public boolean removeConfigurationListener(ConfigurationListener l) 564 { 565 return super.removeConfigurationListener(l); 566 } 567 568 @Override 569 public Collection<ConfigurationListener> getConfigurationListeners() 570 { 571 return super.getConfigurationListeners(); 572 } 573 574 @Override 575 public void clearConfigurationListeners() 576 { 577 super.clearConfigurationListeners(); 578 } 579 580 @Override 581 public void addErrorListener(ConfigurationErrorListener l) 582 { 583 super.addErrorListener(l); 584 } 585 586 @Override 587 public boolean removeErrorListener(ConfigurationErrorListener l) 588 { 589 return super.removeErrorListener(l); 590 } 591 592 @Override 593 public void clearErrorListeners() 594 { 595 super.clearErrorListeners(); 596 } 597 598 @Override 599 public Collection<ConfigurationErrorListener> getErrorListeners() 600 { 601 return super.getErrorListeners(); 602 } 603 604 public void save(Writer writer) throws ConfigurationException 605 { 606 if (init) 607 { 608 this.getConfiguration().save(writer); 609 } 610 } 611 612 public void load(Reader reader) throws ConfigurationException 613 { 614 if (init) 615 { 616 this.getConfiguration().load(reader); 617 } 618 } 619 620 @Override 621 public void load() throws ConfigurationException 622 { 623 this.getConfiguration(); 624 } 625 626 @Override 627 public void load(String fileName) throws ConfigurationException 628 { 629 this.getConfiguration().load(fileName); 630 } 631 632 @Override 633 public void load(File file) throws ConfigurationException 634 { 635 this.getConfiguration().load(file); 636 } 637 638 @Override 639 public void load(URL url) throws ConfigurationException 640 { 641 this.getConfiguration().load(url); 642 } 643 644 @Override 645 public void load(InputStream in) throws ConfigurationException 646 { 647 this.getConfiguration().load(in); 648 } 649 650 @Override 651 public void load(InputStream in, String encoding) throws ConfigurationException 652 { 653 this.getConfiguration().load(in, encoding); 654 } 655 656 @Override 657 public void save() throws ConfigurationException 658 { 659 this.getConfiguration().save(); 660 } 661 662 @Override 663 public void save(String fileName) throws ConfigurationException 664 { 665 this.getConfiguration().save(fileName); 666 } 667 668 @Override 669 public void save(File file) throws ConfigurationException 670 { 671 this.getConfiguration().save(file); 672 } 673 674 @Override 675 public void save(URL url) throws ConfigurationException 676 { 677 this.getConfiguration().save(url); 678 } 679 680 @Override 681 public void save(OutputStream out) throws ConfigurationException 682 { 683 this.getConfiguration().save(out); 684 } 685 686 @Override 687 public void save(OutputStream out, String encoding) throws ConfigurationException 688 { 689 this.getConfiguration().save(out, encoding); 690 } 691 692 @Override 693 public void configurationChanged(ConfigurationEvent event) 694 { 695 if (event.getSource() instanceof XMLConfiguration) 696 { 697 for (ConfigurationListener listener : getConfigurationListeners()) 698 { 699 listener.configurationChanged(event); 700 } 701 } 702 } 703 704 @Override 705 public void configurationError(ConfigurationErrorEvent event) 706 { 707 if (event.getSource() instanceof XMLConfiguration) 708 { 709 for (ConfigurationErrorListener listener : getErrorListeners()) 710 { 711 listener.configurationError(event); 712 } 713 } 714 715 if (event.getType() == AbstractFileConfiguration.EVENT_RELOAD) 716 { 717 if (isThrowable(event.getCause())) 718 { 719 throw new ConfigurationRuntimeException(event.getCause()); 720 } 721 } 722 } 723 724 /* 725 * Don't allow resolveContainerStore to be called recursively. 726 * @param key The key to resolve. 727 * @return The value of the key. 728 */ 729 @Override 730 protected Object resolveContainerStore(String key) 731 { 732 if (recursive.get().booleanValue()) 733 { 734 return null; 735 } 736 recursive.set(Boolean.TRUE); 737 try 738 { 739 return super.resolveContainerStore(key); 740 } 741 finally 742 { 743 recursive.set(Boolean.FALSE); 744 } 745 } 746 747 /** 748 * Remove the current Configuration. 749 */ 750 public void removeConfiguration() 751 { 752 String path = getSubstitutor().replace(pattern); 753 configurationsMap.remove(path); 754 } 755 756 /** 757 * First checks to see if the cache exists, if it does, get the associated Configuration. 758 * If not it will load a new Configuration and save it in the cache. 759 * 760 * @return the Configuration associated with the current value of the path pattern. 761 */ 762 private AbstractHierarchicalFileConfiguration getConfiguration() 763 { 764 if (pattern == null) 765 { 766 throw new ConfigurationRuntimeException("File pattern must be defined"); 767 } 768 String path = localSubst.replace(pattern); 769 770 if (configurationsMap.containsKey(path)) 771 { 772 return configurationsMap.get(path); 773 } 774 775 if (path.equals(pattern)) 776 { 777 XMLConfiguration configuration = new XMLConfiguration() 778 { 779 @Override 780 public void load() throws ConfigurationException 781 { 782 } 783 @Override 784 public void save() throws ConfigurationException 785 { 786 } 787 }; 788 789 configurationsMap.putIfAbsent(pattern, configuration); 790 791 return configuration; 792 } 793 794 XMLConfiguration configuration = new XMLConfiguration(); 795 if (loggerName != null) 796 { 797 Log log = LogFactory.getLog(loggerName); 798 if (log != null) 799 { 800 configuration.setLogger(log); 801 } 802 } 803 configuration.setBasePath(getBasePath()); 804 configuration.setFileName(path); 805 configuration.setFileSystem(getFileSystem()); 806 configuration.setExpressionEngine(getExpressionEngine()); 807 ReloadingStrategy strategy = createReloadingStrategy(); 808 if (strategy != null) 809 { 810 configuration.setReloadingStrategy(strategy); 811 } 812 configuration.setDelimiterParsingDisabled(isDelimiterParsingDisabled()); 813 configuration.setAttributeSplittingDisabled(isAttributeSplittingDisabled()); 814 configuration.setValidating(validating); 815 configuration.setSchemaValidation(schemaValidation); 816 configuration.setEntityResolver(entityResolver); 817 configuration.setListDelimiter(getListDelimiter()); 818 configuration.addConfigurationListener(this); 819 configuration.addErrorListener(this); 820 try 821 { 822 configuration.load(); 823 } 824 catch (ConfigurationException ce) 825 { 826 if (isThrowable(ce)) 827 { 828 throw new ConfigurationRuntimeException(ce); 829 } 830 } 831 configurationsMap.putIfAbsent(path, configuration); 832 return configurationsMap.get(path); 833 } 834 835 private boolean isThrowable(Throwable throwable) 836 { 837 if (!ignoreException) 838 { 839 return true; 840 } 841 Throwable cause = throwable.getCause(); 842 while (cause != null && !(cause instanceof SAXParseException)) 843 { 844 cause = cause.getCause(); 845 } 846 return cause != null; 847 } 848 849 /** 850 * Clone the FileReloadingStrategy since each file needs its own. 851 * @return A new FileReloadingStrategy. 852 */ 853 private ReloadingStrategy createReloadingStrategy() 854 { 855 if (fileStrategy == null) 856 { 857 return null; 858 } 859 try 860 { 861 ReloadingStrategy strategy = (ReloadingStrategy) BeanUtils.cloneBean(fileStrategy); 862 strategy.setConfiguration(null); 863 return strategy; 864 } 865 catch (Exception ex) 866 { 867 return null; 868 } 869 } 870 871 }