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