View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.configuration;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.InputStreamReader;
24  import java.io.OutputStream;
25  import java.io.OutputStreamWriter;
26  import java.io.Reader;
27  import java.io.UnsupportedEncodingException;
28  import java.io.Writer;
29  import java.net.URL;
30  import java.util.Iterator;
31  import java.util.LinkedList;
32  import java.util.List;
33  
34  import org.apache.commons.configuration.reloading.InvariantReloadingStrategy;
35  import org.apache.commons.configuration.reloading.ReloadingStrategy;
36  import org.apache.commons.lang.StringUtils;
37  import org.apache.commons.logging.LogFactory;
38  
39  /**
40   * <p>Partial implementation of the {@code FileConfiguration} interface.
41   * Developers of file based configuration may want to extend this class,
42   * the two methods left to implement are {@link FileConfiguration#load(Reader)}
43   * and {@link FileConfiguration#save(Writer)}.</p>
44   * <p>This base class already implements a couple of ways to specify the location
45   * of the file this configuration is based on. The following possibilities
46   * exist:
47   * <ul><li>URLs: With the method {@code setURL()} a full URL to the
48   * configuration source can be specified. This is the most flexible way. Note
49   * that the {@code save()} methods support only <em>file:</em> URLs.</li>
50   * <li>Files: The {@code setFile()} method allows to specify the
51   * configuration source as a file. This can be either a relative or an
52   * absolute file. In the former case the file is resolved based on the current
53   * directory.</li>
54   * <li>As file paths in string form: With the {@code setPath()} method a
55   * full path to a configuration file can be provided as a string.</li>
56   * <li>Separated as base path and file name: This is the native form in which
57   * the location is stored. The base path is a string defining either a local
58   * directory or a URL. It can be set using the {@code setBasePath()}
59   * method. The file name, non surprisingly, defines the name of the configuration
60   * file.</li></ul></p>
61   * <p>The configuration source to be loaded can be specified using one of the
62   * methods described above. Then the parameterless {@code load()} method can be
63   * called. Alternatively, one of the {@code load()} methods can be used which is
64   * passed the source directly. These methods typically do not change the
65   * internally stored file; however, if the configuration is not yet associated
66   * with a configuration source, the first call to one of the {@code load()}
67   * methods sets the base path and the source URL. This fact has to be taken
68   * into account when calling {@code load()} multiple times with different file
69   * paths.</p>
70   * <p>Note that the {@code load()} methods do not wipe out the configuration's
71   * content before the new configuration file is loaded. Thus it is very easy to
72   * construct a union configuration by simply loading multiple configuration
73   * files, e.g.</p>
74   * <p><pre>
75   * config.load(configFile1);
76   * config.load(configFile2);
77   * </pre></p>
78   * <p>After executing this code fragment, the resulting configuration will
79   * contain both the properties of configFile1 and configFile2. On the other
80   * hand, if the current configuration file is to be reloaded, {@code clear()}
81   * should be called first. Otherwise the properties are doubled. This behavior
82   * is analogous to the behavior of the {@code load(InputStream)} method
83   * in {@code java.util.Properties}.</p>
84   *
85   * @author Emmanuel Bourg
86   * @version $Id: AbstractFileConfiguration.java 1234118 2012-01-20 20:36:04Z oheger $
87   * @since 1.0-rc2
88   */
89  public abstract class AbstractFileConfiguration
90  extends BaseConfiguration
91  implements FileConfiguration, FileSystemBased
92  {
93      /** Constant for the configuration reload event.*/
94      public static final int EVENT_RELOAD = 20;
95  
96      /** Constant fro the configuration changed event. */
97      public static final int EVENT_CONFIG_CHANGED = 21;
98  
99      /** 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 &quot;file&quot;, 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 &quot;file&quot; 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 &quot;No reloading mode&quot;. 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 &quot;No reloading mode&quot;.
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 }