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