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 018 package org.apache.commons.configuration; 019 020 import java.io.File; 021 import java.io.IOException; 022 import java.io.InputStream; 023 import java.io.InputStreamReader; 024 import java.io.OutputStream; 025 import java.io.OutputStreamWriter; 026 import java.io.Reader; 027 import java.io.UnsupportedEncodingException; 028 import java.io.Writer; 029 import java.net.URL; 030 import java.util.Iterator; 031 import java.util.LinkedList; 032 import java.util.List; 033 034 import org.apache.commons.configuration.reloading.InvariantReloadingStrategy; 035 import org.apache.commons.configuration.reloading.ReloadingStrategy; 036 import org.apache.commons.lang.StringUtils; 037 import org.apache.commons.logging.LogFactory; 038 039 /** 040 * <p>Partial implementation of the <code>FileConfiguration</code> interface. 041 * Developers of file based configuration may want to extend this class, 042 * the two methods left to implement are <code>{@link FileConfiguration#load(Reader)}</code> 043 * and <code>{@link FileConfiguration#save(Writer)}</code>.</p> 044 * <p>This base class already implements a couple of ways to specify the location 045 * of the file this configuration is based on. The following possibilities 046 * exist: 047 * <ul><li>URLs: With the method <code>setURL()</code> a full URL to the 048 * configuration source can be specified. This is the most flexible way. Note 049 * that the <code>save()</code> methods support only <em>file:</em> URLs.</li> 050 * <li>Files: The <code>setFile()</code> method allows to specify the 051 * configuration source as a file. This can be either a relative or an 052 * absolute file. In the former case the file is resolved based on the current 053 * directory.</li> 054 * <li>As file paths in string form: With the <code>setPath()</code> method a 055 * full path to a configuration file can be provided as a string.</li> 056 * <li>Separated as base path and file name: This is the native form in which 057 * the location is stored. The base path is a string defining either a local 058 * directory or a URL. It can be set using the <code>setBasePath()</code> 059 * method. The file name, non surprisingly, defines the name of the configuration 060 * file.</li></ul></p> 061 * <p>Note that the <code>load()</code> methods do not wipe out the configuration's 062 * content before the new configuration file is loaded. Thus it is very easy to 063 * construct a union configuration by simply loading multiple configuration 064 * files, e.g.</p> 065 * <p><pre> 066 * config.load(configFile1); 067 * config.load(configFile2); 068 * </pre></p> 069 * <p>After executing this code fragment, the resulting configuration will 070 * contain both the properties of configFile1 and configFile2. On the other 071 * hand, if the current configuration file is to be reloaded, <code>clear()</code> 072 * should be called first. Otherwise the properties are doubled. This behavior 073 * is analogous to the behavior of the <code>load(InputStream)</code> method 074 * in <code>java.util.Properties</code>.</p> 075 * 076 * @author Emmanuel Bourg 077 * @version $Id: AbstractFileConfiguration.java 1158113 2011-08-16 05:59:54Z oheger $ 078 * @since 1.0-rc2 079 */ 080 public abstract class AbstractFileConfiguration 081 extends BaseConfiguration 082 implements FileConfiguration, FileSystemBased 083 { 084 /** Constant for the configuration reload event.*/ 085 public static final int EVENT_RELOAD = 20; 086 087 /** Constant fro the configuration changed event. */ 088 public static final int EVENT_CONFIG_CHANGED = 21; 089 090 /** The root of the file scheme */ 091 private static final String FILE_SCHEME = "file:"; 092 093 /** Stores the file name.*/ 094 protected String fileName; 095 096 /** Stores the base path.*/ 097 protected String basePath; 098 099 /** The auto save flag.*/ 100 protected boolean autoSave; 101 102 /** Holds a reference to the reloading strategy.*/ 103 protected ReloadingStrategy strategy; 104 105 /** A lock object for protecting reload operations.*/ 106 protected Object reloadLock = new Lock("AbstractFileConfiguration"); 107 108 /** Stores the encoding of the configuration file.*/ 109 private String encoding; 110 111 /** Stores the URL from which the configuration file was loaded.*/ 112 private URL sourceURL; 113 114 /** A counter that prohibits reloading.*/ 115 private int noReload; 116 117 /** The FileSystem being used for this Configuration */ 118 private FileSystem fileSystem = FileSystem.getDefaultFileSystem(); 119 120 /** 121 * Default constructor 122 * 123 * @since 1.1 124 */ 125 public AbstractFileConfiguration() 126 { 127 initReloadingStrategy(); 128 setLogger(LogFactory.getLog(getClass())); 129 addErrorLogListener(); 130 } 131 132 /** 133 * Creates and loads the configuration from the specified file. The passed 134 * in string must be a valid file name, either absolute or relativ. 135 * 136 * @param fileName The name of the file to load. 137 * 138 * @throws ConfigurationException Error while loading the file 139 * @since 1.1 140 */ 141 public AbstractFileConfiguration(String fileName) throws ConfigurationException 142 { 143 this(); 144 145 // store the file name 146 setFileName(fileName); 147 148 // load the file 149 load(); 150 } 151 152 /** 153 * Creates and loads the configuration from the specified file. 154 * 155 * @param file The file to load. 156 * @throws ConfigurationException Error while loading the file 157 * @since 1.1 158 */ 159 public AbstractFileConfiguration(File file) throws ConfigurationException 160 { 161 this(); 162 163 // set the file and update the url, the base path and the file name 164 setFile(file); 165 166 // load the file 167 if (file.exists()) 168 { 169 load(); 170 } 171 } 172 173 /** 174 * Creates and loads the configuration from the specified URL. 175 * 176 * @param url The location of the file to load. 177 * @throws ConfigurationException Error while loading the file 178 * @since 1.1 179 */ 180 public AbstractFileConfiguration(URL url) throws ConfigurationException 181 { 182 this(); 183 184 // set the URL and update the base path and the file name 185 setURL(url); 186 187 // load the file 188 load(); 189 } 190 191 public void setFileSystem(FileSystem fileSystem) 192 { 193 if (fileSystem == null) 194 { 195 throw new NullPointerException("A valid FileSystem must be specified"); 196 } 197 this.fileSystem = fileSystem; 198 } 199 200 public void resetFileSystem() 201 { 202 this.fileSystem = FileSystem.getDefaultFileSystem(); 203 } 204 205 public FileSystem getFileSystem() 206 { 207 return this.fileSystem; 208 } 209 210 public Object getReloadLock() 211 { 212 return reloadLock; 213 } 214 215 216 /** 217 * Load the configuration from the underlying location. 218 * 219 * @throws ConfigurationException if loading of the configuration fails 220 */ 221 public void load() throws ConfigurationException 222 { 223 if (sourceURL != null) 224 { 225 load(sourceURL); 226 } 227 else 228 { 229 load(getFileName()); 230 } 231 } 232 233 /** 234 * Locate the specified file and load the configuration. This does not 235 * change the source of the configuration (i.e. the internally maintained file name). 236 * Use one of the setter methods for this purpose. 237 * 238 * @param fileName the name of the file to be loaded 239 * @throws ConfigurationException if an error occurs 240 */ 241 public void load(String fileName) throws ConfigurationException 242 { 243 try 244 { 245 URL url = ConfigurationUtils.locate(this.fileSystem, basePath, fileName); 246 247 if (url == null) 248 { 249 throw new ConfigurationException("Cannot locate configuration source " + fileName); 250 } 251 load(url); 252 } 253 catch (ConfigurationException e) 254 { 255 throw e; 256 } 257 catch (Exception e) 258 { 259 throw new ConfigurationException("Unable to load the configuration file " + fileName, e); 260 } 261 } 262 263 /** 264 * Load the configuration from the specified file. This does not change 265 * the source of the configuration (i.e. the internally maintained file 266 * name). Use one of the setter methods for this purpose. 267 * 268 * @param file the file to load 269 * @throws ConfigurationException if an error occurs 270 */ 271 public void load(File file) throws ConfigurationException 272 { 273 try 274 { 275 load(ConfigurationUtils.toURL(file)); 276 } 277 catch (ConfigurationException e) 278 { 279 throw e; 280 } 281 catch (Exception e) 282 { 283 throw new ConfigurationException("Unable to load the configuration file " + file, e); 284 } 285 } 286 287 /** 288 * Load the configuration from the specified URL. This does not change the 289 * source of the configuration (i.e. the internally maintained file name). 290 * Use on of the setter methods for this purpose. 291 * 292 * @param url the URL of the file to be loaded 293 * @throws ConfigurationException if an error occurs 294 */ 295 public void load(URL url) throws ConfigurationException 296 { 297 if (sourceURL == null) 298 { 299 if (StringUtils.isEmpty(getBasePath())) 300 { 301 // ensure that we have a valid base path 302 setBasePath(url.toString()); 303 } 304 sourceURL = url; 305 } 306 307 InputStream in = null; 308 309 try 310 { 311 in = fileSystem.getInputStream(url); 312 load(in); 313 } 314 catch (ConfigurationException e) 315 { 316 throw e; 317 } 318 catch (Exception e) 319 { 320 throw new ConfigurationException("Unable to load the configuration from the URL " + url, e); 321 } 322 finally 323 { 324 // close the input stream 325 try 326 { 327 if (in != null) 328 { 329 in.close(); 330 } 331 } 332 catch (IOException e) 333 { 334 getLogger().warn("Could not close input stream", e); 335 } 336 } 337 } 338 339 /** 340 * Load the configuration from the specified stream, using the encoding 341 * returned by {@link #getEncoding()}. 342 * 343 * @param in the input stream 344 * 345 * @throws ConfigurationException if an error occurs during the load operation 346 */ 347 public void load(InputStream in) throws ConfigurationException 348 { 349 load(in, getEncoding()); 350 } 351 352 /** 353 * Load the configuration from the specified stream, using the specified 354 * encoding. If the encoding is null the default encoding is used. 355 * 356 * @param in the input stream 357 * @param encoding the encoding used. <code>null</code> to use the default encoding 358 * 359 * @throws ConfigurationException if an error occurs during the load operation 360 */ 361 public void load(InputStream in, String encoding) throws ConfigurationException 362 { 363 Reader reader = null; 364 365 if (encoding != null) 366 { 367 try 368 { 369 reader = new InputStreamReader(in, encoding); 370 } 371 catch (UnsupportedEncodingException e) 372 { 373 throw new ConfigurationException( 374 "The requested encoding is not supported, try the default encoding.", e); 375 } 376 } 377 378 if (reader == null) 379 { 380 reader = new InputStreamReader(in); 381 } 382 383 load(reader); 384 } 385 386 /** 387 * Save the configuration. Before this method can be called a valid file 388 * name must have been set. 389 * 390 * @throws ConfigurationException if an error occurs or no file name has 391 * been set yet 392 */ 393 public void save() throws ConfigurationException 394 { 395 if (getFileName() == null) 396 { 397 throw new ConfigurationException("No file name has been set!"); 398 } 399 400 if (sourceURL != null) 401 { 402 save(sourceURL); 403 } 404 else 405 { 406 save(fileName); 407 } 408 strategy.init(); 409 } 410 411 /** 412 * Save the configuration to the specified file. This doesn't change the 413 * source of the configuration, use setFileName() if you need it. 414 * 415 * @param fileName the file name 416 * 417 * @throws ConfigurationException if an error occurs during the save operation 418 */ 419 public void save(String fileName) throws ConfigurationException 420 { 421 try 422 { 423 URL url = this.fileSystem.getURL(basePath, fileName); 424 425 if (url == null) 426 { 427 throw new ConfigurationException("Cannot locate configuration source " + fileName); 428 } 429 save(url); 430 /*File file = ConfigurationUtils.getFile(basePath, fileName); 431 if (file == null) 432 { 433 throw new ConfigurationException("Invalid file name for save: " + fileName); 434 } 435 save(file); */ 436 } 437 catch (ConfigurationException e) 438 { 439 throw e; 440 } 441 catch (Exception e) 442 { 443 throw new ConfigurationException("Unable to save the configuration to the file " + fileName, e); 444 } 445 } 446 447 /** 448 * Save the configuration to the specified URL. 449 * This doesn't change the source of the configuration, use setURL() 450 * if you need it. 451 * 452 * @param url the URL 453 * 454 * @throws ConfigurationException if an error occurs during the save operation 455 */ 456 public void save(URL url) throws ConfigurationException 457 { 458 OutputStream out = null; 459 try 460 { 461 out = fileSystem.getOutputStream(url); 462 save(out); 463 if (out instanceof VerifiableOutputStream) 464 { 465 ((VerifiableOutputStream) out).verify(); 466 } 467 } 468 catch (IOException e) 469 { 470 throw new ConfigurationException("Could not save to URL " + url, e); 471 } 472 finally 473 { 474 closeSilent(out); 475 } 476 } 477 478 /** 479 * Save the configuration to the specified file. The file is created 480 * automatically if it doesn't exist. This doesn't change the source 481 * of the configuration, use {@link #setFile} if you need it. 482 * 483 * @param file the target file 484 * 485 * @throws ConfigurationException if an error occurs during the save operation 486 */ 487 public void save(File file) throws ConfigurationException 488 { 489 OutputStream out = null; 490 491 try 492 { 493 out = fileSystem.getOutputStream(file); 494 save(out); 495 } 496 finally 497 { 498 closeSilent(out); 499 } 500 } 501 502 /** 503 * Save the configuration to the specified stream, using the encoding 504 * returned by {@link #getEncoding()}. 505 * 506 * @param out the output stream 507 * 508 * @throws ConfigurationException if an error occurs during the save operation 509 */ 510 public void save(OutputStream out) throws ConfigurationException 511 { 512 save(out, getEncoding()); 513 } 514 515 /** 516 * Save the configuration to the specified stream, using the specified 517 * encoding. If the encoding is null the default encoding is used. 518 * 519 * @param out the output stream 520 * @param encoding the encoding to use 521 * @throws ConfigurationException if an error occurs during the save operation 522 */ 523 public void save(OutputStream out, String encoding) throws ConfigurationException 524 { 525 Writer writer = null; 526 527 if (encoding != null) 528 { 529 try 530 { 531 writer = new OutputStreamWriter(out, encoding); 532 } 533 catch (UnsupportedEncodingException e) 534 { 535 throw new ConfigurationException( 536 "The requested encoding is not supported, try the default encoding.", e); 537 } 538 } 539 540 if (writer == null) 541 { 542 writer = new OutputStreamWriter(out); 543 } 544 545 save(writer); 546 } 547 548 /** 549 * Return the name of the file. 550 * 551 * @return the file name 552 */ 553 public String getFileName() 554 { 555 return fileName; 556 } 557 558 /** 559 * Set the name of the file. The passed in file name can contain a 560 * relative path. 561 * It must be used when referring files with relative paths from classpath. 562 * Use <code>{@link AbstractFileConfiguration#setPath(String) 563 * setPath()}</code> to set a full qualified file name. 564 * 565 * @param fileName the name of the file 566 */ 567 public void setFileName(String fileName) 568 { 569 if (fileName != null && fileName.startsWith(FILE_SCHEME) && !fileName.startsWith("file://")) 570 { 571 fileName = "file://" + fileName.substring(FILE_SCHEME.length()); 572 } 573 574 sourceURL = null; 575 this.fileName = fileName; 576 getLogger().debug("FileName set to " + fileName); 577 } 578 579 /** 580 * Return the base path. 581 * 582 * @return the base path 583 * @see FileConfiguration#getBasePath() 584 */ 585 public String getBasePath() 586 { 587 return basePath; 588 } 589 590 /** 591 * Sets the base path. The base path is typically either a path to a 592 * directory or a URL. Together with the value passed to the 593 * <code>setFileName()</code> method it defines the location of the 594 * configuration file to be loaded. The strategies for locating the file are 595 * quite tolerant. For instance if the file name is already an absolute path 596 * or a fully defined URL, the base path will be ignored. The base path can 597 * also be a URL, in which case the file name is interpreted in this URL's 598 * context. Because the base path is used by some of the derived classes for 599 * resolving relative file names it should contain a meaningful value. If 600 * other methods are used for determining the location of the configuration 601 * file (e.g. <code>setFile()</code> or <code>setURL()</code>), the 602 * base path is automatically set. 603 * 604 * @param basePath the base path. 605 */ 606 public void setBasePath(String basePath) 607 { 608 if (basePath != null && basePath.startsWith(FILE_SCHEME) && !basePath.startsWith("file://")) 609 { 610 basePath = "file://" + basePath.substring(FILE_SCHEME.length()); 611 } 612 sourceURL = null; 613 this.basePath = basePath; 614 getLogger().debug("Base path set to " + basePath); 615 } 616 617 /** 618 * Return the file where the configuration is stored. If the base path is a 619 * URL with a protocol different than "file", or the configuration 620 * file is within a compressed archive, the return value 621 * will not point to a valid file object. 622 * 623 * @return the file where the configuration is stored; this can be <b>null</b> 624 */ 625 public File getFile() 626 { 627 if (getFileName() == null && sourceURL == null) 628 { 629 return null; 630 } 631 else if (sourceURL != null) 632 { 633 return ConfigurationUtils.fileFromURL(sourceURL); 634 } 635 else 636 { 637 return ConfigurationUtils.getFile(getBasePath(), getFileName()); 638 } 639 } 640 641 /** 642 * Set the file where the configuration is stored. The passed in file is 643 * made absolute if it is not yet. Then the file's path component becomes 644 * the base path and its name component becomes the file name. 645 * 646 * @param file the file where the configuration is stored 647 */ 648 public void setFile(File file) 649 { 650 sourceURL = null; 651 setFileName(file.getName()); 652 setBasePath((file.getParentFile() != null) ? file.getParentFile() 653 .getAbsolutePath() : null); 654 } 655 656 /** 657 * Returns the full path to the file this configuration is based on. The 658 * return value is a valid File path only if this configuration is based on 659 * a file on the local disk. 660 * If the configuration was loaded from a packed archive the returned value 661 * is the string form of the URL from which the configuration was loaded. 662 * 663 * @return the full path to the configuration file 664 */ 665 public String getPath() 666 { 667 return fileSystem.getPath(getFile(), sourceURL, getBasePath(), getFileName()); 668 } 669 670 /** 671 * Sets the location of this configuration as a full or relative path name. 672 * The passed in path should represent a valid file name on the file system. 673 * It must not be used to specify relative paths for files that exist 674 * in classpath, either plain file system or compressed archive, 675 * because this method expands any relative path to an absolute one which 676 * may end in an invalid absolute path for classpath references. 677 * 678 * @param path the full path name of the configuration file 679 */ 680 public void setPath(String path) 681 { 682 setFile(new File(path)); 683 } 684 685 URL getSourceURL() 686 { 687 return sourceURL; 688 } 689 690 /** 691 * Return the URL where the configuration is stored. 692 * 693 * @return the configuration's location as URL 694 */ 695 public URL getURL() 696 { 697 return (sourceURL != null) ? sourceURL 698 : ConfigurationUtils.locate(this.fileSystem, getBasePath(), getFileName()); 699 } 700 701 /** 702 * Set the location of this configuration as a URL. For loading this can be 703 * an arbitrary URL with a supported protocol. If the configuration is to 704 * be saved, too, a URL with the "file" protocol should be 705 * provided. 706 * 707 * @param url the location of this configuration as URL 708 */ 709 public void setURL(URL url) 710 { 711 setBasePath(ConfigurationUtils.getBasePath(url)); 712 setFileName(ConfigurationUtils.getFileName(url)); 713 sourceURL = url; 714 getLogger().debug("URL set to " + url); 715 } 716 717 public void setAutoSave(boolean autoSave) 718 { 719 this.autoSave = autoSave; 720 } 721 722 public boolean isAutoSave() 723 { 724 return autoSave; 725 } 726 727 /** 728 * Save the configuration if the automatic persistence is enabled 729 * and if a file is specified. 730 */ 731 protected void possiblySave() 732 { 733 if (autoSave && fileName != null) 734 { 735 try 736 { 737 save(); 738 } 739 catch (ConfigurationException e) 740 { 741 throw new ConfigurationRuntimeException("Failed to auto-save", e); 742 } 743 } 744 } 745 746 /** 747 * Adds a new property to this configuration. This implementation checks if 748 * the auto save mode is enabled and saves the configuration if necessary. 749 * 750 * @param key the key of the new property 751 * @param value the value 752 */ 753 public void addProperty(String key, Object value) 754 { 755 synchronized (reloadLock) 756 { 757 super.addProperty(key, value); 758 possiblySave(); 759 } 760 } 761 762 /** 763 * Sets a new value for the specified property. This implementation checks 764 * if the auto save mode is enabled and saves the configuration if 765 * necessary. 766 * 767 * @param key the key of the affected property 768 * @param value the value 769 */ 770 public void setProperty(String key, Object value) 771 { 772 synchronized (reloadLock) 773 { 774 super.setProperty(key, value); 775 possiblySave(); 776 } 777 } 778 779 public void clearProperty(String key) 780 { 781 synchronized (reloadLock) 782 { 783 super.clearProperty(key); 784 possiblySave(); 785 } 786 } 787 788 public ReloadingStrategy getReloadingStrategy() 789 { 790 return strategy; 791 } 792 793 public void setReloadingStrategy(ReloadingStrategy strategy) 794 { 795 this.strategy = strategy; 796 strategy.setConfiguration(this); 797 strategy.init(); 798 } 799 800 /** 801 * Performs a reload operation if necessary. This method is called on each 802 * access of this configuration. It asks the associated reloading strategy 803 * whether a reload should be performed. If this is the case, the 804 * configuration is cleared and loaded again from its source. If this 805 * operation causes an exception, the registered error listeners will be 806 * notified. The error event passed to the listeners is of type 807 * <code>EVENT_RELOAD</code> and contains the exception that caused the 808 * event. 809 */ 810 public void reload() 811 { 812 reload(false); 813 } 814 815 public boolean reload(boolean checkReload) 816 { 817 synchronized (reloadLock) 818 { 819 if (noReload == 0) 820 { 821 try 822 { 823 enterNoReload(); // avoid reentrant calls 824 825 if (strategy.reloadingRequired()) 826 { 827 if (getLogger().isInfoEnabled()) 828 { 829 getLogger().info("Reloading configuration. URL is " + getURL()); 830 } 831 refresh(); 832 833 // notify the strategy 834 strategy.reloadingPerformed(); 835 } 836 } 837 catch (Exception e) 838 { 839 fireError(EVENT_RELOAD, null, null, e); 840 // todo rollback the changes if the file can't be reloaded 841 if (checkReload) 842 { 843 return false; 844 } 845 } 846 finally 847 { 848 exitNoReload(); 849 } 850 } 851 } 852 return true; 853 } 854 855 /** 856 * Reloads the associated configuration file. This method first clears the 857 * content of this configuration, then the associated configuration file is 858 * loaded again. Updates on this configuration which have not yet been saved 859 * are lost. Calling this method is like invoking <code>reload()</code> 860 * without checking the reloading strategy. 861 * 862 * @throws ConfigurationException if an error occurs 863 * @since 1.7 864 */ 865 public void refresh() throws ConfigurationException 866 { 867 fireEvent(EVENT_RELOAD, null, getURL(), true); 868 setDetailEvents(false); 869 boolean autoSaveBak = this.isAutoSave(); // save the current state 870 this.setAutoSave(false); // deactivate autoSave to prevent information loss 871 try 872 { 873 clear(); 874 load(); 875 } 876 finally 877 { 878 this.setAutoSave(autoSaveBak); // set autoSave to previous value 879 setDetailEvents(true); 880 } 881 fireEvent(EVENT_RELOAD, null, getURL(), false); 882 } 883 884 /** 885 * Send notification that the configuration has changed. 886 */ 887 public void configurationChanged() 888 { 889 fireEvent(EVENT_CONFIG_CHANGED, null, getURL(), true); 890 } 891 892 /** 893 * Enters the "No reloading mode". As long as this mode is active 894 * no reloading will be performed. This is necessary for some 895 * implementations of <code>save()</code> in derived classes, which may 896 * cause a reload while accessing the properties to save. This may cause the 897 * whole configuration to be erased. To avoid this, this method can be 898 * called first. After a call to this method there always must be a 899 * corresponding call of <code>{@link #exitNoReload()}</code> later! (If 900 * necessary, <code>finally</code> blocks must be used to ensure this. 901 */ 902 protected void enterNoReload() 903 { 904 synchronized (reloadLock) 905 { 906 noReload++; 907 } 908 } 909 910 /** 911 * Leaves the "No reloading mode". 912 * 913 * @see #enterNoReload() 914 */ 915 protected void exitNoReload() 916 { 917 synchronized (reloadLock) 918 { 919 if (noReload > 0) // paranoia check 920 { 921 noReload--; 922 } 923 } 924 } 925 926 /** 927 * Sends an event to all registered listeners. This implementation ensures 928 * that no reloads are performed while the listeners are invoked. So 929 * infinite loops can be avoided that can be caused by event listeners 930 * accessing the configuration's properties when they are invoked. 931 * 932 * @param type the event type 933 * @param propName the name of the property 934 * @param propValue the value of the property 935 * @param before the before update flag 936 */ 937 protected void fireEvent(int type, String propName, Object propValue, boolean before) 938 { 939 enterNoReload(); 940 try 941 { 942 super.fireEvent(type, propName, propValue, before); 943 } 944 finally 945 { 946 exitNoReload(); 947 } 948 } 949 950 public Object getProperty(String key) 951 { 952 synchronized (reloadLock) 953 { 954 reload(); 955 return super.getProperty(key); 956 } 957 } 958 959 public boolean isEmpty() 960 { 961 reload(); 962 synchronized (reloadLock) 963 { 964 return super.isEmpty(); 965 } 966 } 967 968 public boolean containsKey(String key) 969 { 970 reload(); 971 synchronized (reloadLock) 972 { 973 return super.containsKey(key); 974 } 975 } 976 977 /** 978 * Returns an <code>Iterator</code> with the keys contained in this 979 * configuration. This implementation performs a reload if necessary before 980 * obtaining the keys. The <code>Iterator</code> returned by this method 981 * points to a snapshot taken when this method was called. Later changes at 982 * the set of keys (including those caused by a reload) won't be visible. 983 * This is because a reload can happen at any time during iteration, and it 984 * is impossible to determine how this reload affects the current iteration. 985 * When using the iterator a client has to be aware that changes of the 986 * configuration are possible at any time. For instance, if after a reload 987 * operation some keys are no longer present, the iterator will still return 988 * those keys because they were found when it was created. 989 * 990 * @return an <code>Iterator</code> with the keys of this configuration 991 */ 992 public Iterator getKeys() 993 { 994 reload(); 995 List keyList = new LinkedList(); 996 enterNoReload(); 997 try 998 { 999 for (Iterator it = super.getKeys(); it.hasNext();) 1000 { 1001 keyList.add(it.next()); 1002 } 1003 1004 return keyList.iterator(); 1005 } 1006 finally 1007 { 1008 exitNoReload(); 1009 } 1010 } 1011 1012 public String getEncoding() 1013 { 1014 return encoding; 1015 } 1016 1017 public void setEncoding(String encoding) 1018 { 1019 this.encoding = encoding; 1020 } 1021 1022 /** 1023 * Creates a copy of this configuration. The new configuration object will 1024 * contain the same properties as the original, but it will lose any 1025 * connection to a source file (if one exists); this includes setting the 1026 * source URL, base path, and file name to <b>null</b>. This is done to 1027 * avoid race conditions if both the original and the copy are modified and 1028 * then saved. 1029 * 1030 * @return the copy 1031 * @since 1.3 1032 */ 1033 public Object clone() 1034 { 1035 AbstractFileConfiguration copy = (AbstractFileConfiguration) super.clone(); 1036 copy.setBasePath(null); 1037 copy.setFileName(null); 1038 copy.initReloadingStrategy(); 1039 return copy; 1040 } 1041 1042 /** 1043 * Helper method for initializing the reloading strategy. 1044 */ 1045 private void initReloadingStrategy() 1046 { 1047 setReloadingStrategy(new InvariantReloadingStrategy()); 1048 } 1049 1050 /** 1051 * A helper method for closing an output stream. Occurring exceptions will 1052 * be ignored. 1053 * 1054 * @param out the output stream to be closed (may be <b>null</b>) 1055 * @since 1.5 1056 */ 1057 protected void closeSilent(OutputStream out) 1058 { 1059 try 1060 { 1061 if (out != null) 1062 { 1063 out.close(); 1064 } 1065 } 1066 catch (IOException e) 1067 { 1068 getLogger().warn("Could not close output stream", e); 1069 } 1070 } 1071 }