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