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