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  import java.util.LinkedList;
36  import java.util.List;
37  
38  import org.apache.commons.configuration.reloading.InvariantReloadingStrategy;
39  import org.apache.commons.configuration.reloading.ReloadingStrategy;
40  import org.apache.commons.lang.StringUtils;
41  import org.apache.commons.logging.LogFactory;
42  
43  /***
44   * <p>Partial implementation of the <code>FileConfiguration</code> interface.
45   * Developers of file based configuration may want to extend this class,
46   * the two methods left to implement are <code>{@link FileConfiguration#load(Reader)}</code>
47   * and <code>{@link FileConfiguration#save(Writer)}</code>.</p>
48   * <p>This base class already implements a couple of ways to specify the location
49   * of the file this configuration is based on. The following possibilities
50   * exist:
51   * <ul><li>URLs: With the method <code>setURL()</code> a full URL to the
52   * configuration source can be specified. This is the most flexible way. Note
53   * that the <code>save()</code> methods support only <em>file:</em> URLs.</li>
54   * <li>Files: The <code>setFile()</code> method allows to specify the
55   * configuration source as a file. This can be either a relative or an
56   * absolute file. In the former case the file is resolved based on the current
57   * directory.</li>
58   * <li>As file paths in string form: With the <code>setPath()</code> method a
59   * full path to a configuration file can be provided as a string.</li>
60   * <li>Separated as base path and file name: This is the native form in which
61   * the location is stored. The base path is a string defining either a local
62   * directory or a URL. It can be set using the <code>setBasePath()</code>
63   * method. The file name, non surprisingly, defines the name of the configuration
64   * file.</li></ul></p>
65   * <p>Note that the <code>load()</code> methods do not wipe out the configuration's
66   * content before the new configuration file is loaded. Thus it is very easy to
67   * construct a union configuration by simply loading multiple configuration
68   * files, e.g.</p>
69   * <p><pre>
70   * config.load(configFile1);
71   * config.load(configFile2);
72   * </pre></p>
73   * <p>After executing this code fragment, the resulting configuration will
74   * contain both the properties of configFile1 and configFile2. On the other
75   * hand, if the current configuration file is to be reloaded, <code>clear()</code>
76   * should be called first. Otherwise the properties are doubled. This behavior
77   * is analogous to the behavior of the <code>load(InputStream)</code> method
78   * in <code>java.util.Properties</code>.</p>
79   *
80   * @author Emmanuel Bourg
81   * @version $Revision: 712401 $, $Date: 2008-11-08 16:29:56 +0100 (Sa, 08 Nov 2008) $
82   * @since 1.0-rc2
83   */
84  public abstract class AbstractFileConfiguration extends BaseConfiguration implements FileConfiguration
85  {
86      /*** Constant for the configuration reload event.*/
87      public static final int EVENT_RELOAD = 20;
88  
89      /*** Stores the file name.*/
90      protected String fileName;
91  
92      /*** Stores the base path.*/
93      protected String basePath;
94  
95      /*** The auto save flag.*/
96      protected boolean autoSave;
97  
98      /*** Holds a reference to the reloading strategy.*/
99      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 &quot;file&quot;, 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 &quot;file&quot; 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 &quot;No reloading mode&quot;. 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 &quot;No reloading mode&quot;.
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 }