001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.configuration;
019    
020    import java.io.File;
021    import java.io.FileOutputStream;
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.io.InputStreamReader;
025    import java.io.OutputStream;
026    import java.io.OutputStreamWriter;
027    import java.io.Reader;
028    import java.io.UnsupportedEncodingException;
029    import java.io.Writer;
030    import java.net.HttpURLConnection;
031    import java.net.MalformedURLException;
032    import java.net.URL;
033    import java.net.URLConnection;
034    import java.util.Iterator;
035    import java.util.LinkedList;
036    import java.util.List;
037    
038    import org.apache.commons.configuration.reloading.InvariantReloadingStrategy;
039    import org.apache.commons.configuration.reloading.ReloadingStrategy;
040    import org.apache.commons.lang.StringUtils;
041    import org.apache.commons.logging.LogFactory;
042    
043    /**
044     * <p>Partial implementation of the <code>FileConfiguration</code> interface.
045     * Developers of file based configuration may want to extend this class,
046     * the two methods left to implement are <code>{@link FileConfiguration#load(Reader)}</code>
047     * and <code>{@link FileConfiguration#save(Writer)}</code>.</p>
048     * <p>This base class already implements a couple of ways to specify the location
049     * of the file this configuration is based on. The following possibilities
050     * exist:
051     * <ul><li>URLs: With the method <code>setURL()</code> a full URL to the
052     * configuration source can be specified. This is the most flexible way. Note
053     * that the <code>save()</code> methods support only <em>file:</em> URLs.</li>
054     * <li>Files: The <code>setFile()</code> method allows to specify the
055     * configuration source as a file. This can be either a relative or an
056     * absolute file. In the former case the file is resolved based on the current
057     * directory.</li>
058     * <li>As file paths in string form: With the <code>setPath()</code> method a
059     * full path to a configuration file can be provided as a string.</li>
060     * <li>Separated as base path and file name: This is the native form in which
061     * the location is stored. The base path is a string defining either a local
062     * directory or a URL. It can be set using the <code>setBasePath()</code>
063     * method. The file name, non surprisingly, defines the name of the configuration
064     * file.</li></ul></p>
065     * <p>Note that the <code>load()</code> methods do not wipe out the configuration's
066     * content before the new configuration file is loaded. Thus it is very easy to
067     * construct a union configuration by simply loading multiple configuration
068     * files, e.g.</p>
069     * <p><pre>
070     * config.load(configFile1);
071     * config.load(configFile2);
072     * </pre></p>
073     * <p>After executing this code fragment, the resulting configuration will
074     * contain both the properties of configFile1 and configFile2. On the other
075     * hand, if the current configuration file is to be reloaded, <code>clear()</code>
076     * should be called first. Otherwise the properties are doubled. This behavior
077     * is analogous to the behavior of the <code>load(InputStream)</code> method
078     * in <code>java.util.Properties</code>.</p>
079     *
080     * @author Emmanuel Bourg
081     * @version $Revision: 712401 $, $Date: 2008-11-08 16:29:56 +0100 (Sa, 08 Nov 2008) $
082     * @since 1.0-rc2
083     */
084    public abstract class AbstractFileConfiguration extends BaseConfiguration implements FileConfiguration
085    {
086        /** Constant for the configuration reload event.*/
087        public static final int EVENT_RELOAD = 20;
088    
089        /** Stores the file name.*/
090        protected String fileName;
091    
092        /** Stores the base path.*/
093        protected String basePath;
094    
095        /** The auto save flag.*/
096        protected boolean autoSave;
097    
098        /** Holds a reference to the reloading strategy.*/
099        protected ReloadingStrategy strategy;
100    
101        /** A lock object for protecting reload operations.*/
102        private Object reloadLock = new Object();
103    
104        /** Stores the encoding of the configuration file.*/
105        private String encoding;
106    
107        /** Stores the URL from which the configuration file was loaded.*/
108        private URL sourceURL;
109    
110        /** A counter that prohibits reloading.*/
111        private int noReload;
112    
113        /**
114         * Default constructor
115         *
116         * @since 1.1
117         */
118        public AbstractFileConfiguration()
119        {
120            initReloadingStrategy();
121            setLogger(LogFactory.getLog(getClass()));
122            addErrorLogListener();
123        }
124    
125        /**
126         * Creates and loads the configuration from the specified file. The passed
127         * in string must be a valid file name, either absolute or relativ.
128         *
129         * @param fileName The name of the file to load.
130         *
131         * @throws ConfigurationException Error while loading the file
132         * @since 1.1
133         */
134        public AbstractFileConfiguration(String fileName) throws ConfigurationException
135        {
136            this();
137    
138            // store the file name
139            setFileName(fileName);
140    
141            // load the file
142            load();
143        }
144    
145        /**
146         * Creates and loads the configuration from the specified file.
147         *
148         * @param file The file to load.
149         * @throws ConfigurationException Error while loading the file
150         * @since 1.1
151         */
152        public AbstractFileConfiguration(File file) throws ConfigurationException
153        {
154            this();
155    
156            // set the file and update the url, the base path and the file name
157            setFile(file);
158    
159            // load the file
160            if (file.exists())
161            {
162                load();
163            }
164        }
165    
166        /**
167         * Creates and loads the configuration from the specified URL.
168         *
169         * @param url The location of the file to load.
170         * @throws ConfigurationException Error while loading the file
171         * @since 1.1
172         */
173        public AbstractFileConfiguration(URL url) throws ConfigurationException
174        {
175            this();
176    
177            // set the URL and update the base path and the file name
178            setURL(url);
179    
180            // load the file
181            load();
182        }
183    
184        /**
185         * Load the configuration from the underlying location.
186         *
187         * @throws ConfigurationException if loading of the configuration fails
188         */
189        public void load() throws ConfigurationException
190        {
191            if (sourceURL != null)
192            {
193                load(sourceURL);
194            }
195            else
196            {
197                load(getFileName());
198            }
199        }
200    
201        /**
202         * Locate the specified file and load the configuration. This does not
203         * change the source of the configuration (i.e. the internally maintained file name).
204         * Use one of the setter methods for this purpose.
205         *
206         * @param fileName the name of the file to be loaded
207         * @throws ConfigurationException if an error occurs
208         */
209        public void load(String fileName) throws ConfigurationException
210        {
211            try
212            {
213                URL url = ConfigurationUtils.locate(basePath, fileName);
214    
215                if (url == null)
216                {
217                    throw new ConfigurationException("Cannot locate configuration source " + fileName);
218                }
219                load(url);
220            }
221            catch (ConfigurationException e)
222            {
223                throw e;
224            }
225            catch (Exception e)
226            {
227                throw new ConfigurationException("Unable to load the configuration file " + fileName, e);
228            }
229        }
230    
231        /**
232         * Load the configuration from the specified file. This does not change
233         * the source of the configuration (i.e. the internally maintained file
234         * name). Use one of the setter methods for this purpose.
235         *
236         * @param file the file to load
237         * @throws ConfigurationException if an error occurs
238         */
239        public void load(File file) throws ConfigurationException
240        {
241            try
242            {
243                load(ConfigurationUtils.toURL(file));
244            }
245            catch (ConfigurationException e)
246            {
247                throw e;
248            }
249            catch (Exception e)
250            {
251                throw new ConfigurationException("Unable to load the configuration file " + file, e);
252            }
253        }
254    
255        /**
256         * Load the configuration from the specified URL. This does not change the
257         * source of the configuration (i.e. the internally maintained file name).
258         * Use on of the setter methods for this purpose.
259         *
260         * @param url the URL of the file to be loaded
261         * @throws ConfigurationException if an error occurs
262         */
263        public void load(URL url) throws ConfigurationException
264        {
265            if (sourceURL == null)
266            {
267                if (StringUtils.isEmpty(getBasePath()))
268                {
269                    // ensure that we have a valid base path
270                    setBasePath(url.toString());
271                }
272                sourceURL = url;
273            }
274    
275            // throw an exception if the target URL is a directory
276            File file = ConfigurationUtils.fileFromURL(url);
277            if (file != null && file.isDirectory())
278            {
279                throw new ConfigurationException("Cannot load a configuration from a directory");
280            }
281    
282            InputStream in = null;
283    
284            try
285            {
286                in = url.openStream();
287                load(in);
288            }
289            catch (ConfigurationException e)
290            {
291                throw e;
292            }
293            catch (Exception e)
294            {
295                throw new ConfigurationException("Unable to load the configuration from the URL " + url, e);
296            }
297            finally
298            {
299                // close the input stream
300                try
301                {
302                    if (in != null)
303                    {
304                        in.close();
305                    }
306                }
307                catch (IOException e)
308                {
309                    getLogger().warn("Could not close input stream", e);
310                }
311            }
312        }
313    
314        /**
315         * Load the configuration from the specified stream, using the encoding
316         * returned by {@link #getEncoding()}.
317         *
318         * @param in the input stream
319         *
320         * @throws ConfigurationException if an error occurs during the load operation
321         */
322        public void load(InputStream in) throws ConfigurationException
323        {
324            load(in, getEncoding());
325        }
326    
327        /**
328         * Load the configuration from the specified stream, using the specified
329         * encoding. If the encoding is null the default encoding is used.
330         *
331         * @param in the input stream
332         * @param encoding the encoding used. <code>null</code> to use the default encoding
333         *
334         * @throws ConfigurationException if an error occurs during the load operation
335         */
336        public void load(InputStream in, String encoding) throws ConfigurationException
337        {
338            Reader reader = null;
339    
340            if (encoding != null)
341            {
342                try
343                {
344                    reader = new InputStreamReader(in, encoding);
345                }
346                catch (UnsupportedEncodingException e)
347                {
348                    throw new ConfigurationException(
349                            "The requested encoding is not supported, try the default encoding.", e);
350                }
351            }
352    
353            if (reader == null)
354            {
355                reader = new InputStreamReader(in);
356            }
357    
358            load(reader);
359        }
360    
361        /**
362         * Save the configuration. Before this method can be called a valid file
363         * name must have been set.
364         *
365         * @throws ConfigurationException if an error occurs or no file name has
366         * been set yet
367         */
368        public void save() throws ConfigurationException
369        {
370            if (getFileName() == null)
371            {
372                throw new ConfigurationException("No file name has been set!");
373            }
374    
375            if (sourceURL != null)
376            {
377                save(sourceURL);
378            }
379            else
380            {
381                save(fileName);
382            }
383            strategy.init();
384        }
385    
386        /**
387         * Save the configuration to the specified file. This doesn't change the
388         * source of the configuration, use setFileName() if you need it.
389         *
390         * @param fileName the file name
391         *
392         * @throws ConfigurationException if an error occurs during the save operation
393         */
394        public void save(String fileName) throws ConfigurationException
395        {
396            try
397            {
398                File file = ConfigurationUtils.getFile(basePath, fileName);
399                if (file == null)
400                {
401                    throw new ConfigurationException("Invalid file name for save: " + fileName);
402                }
403                save(file);
404            }
405            catch (ConfigurationException e)
406            {
407                throw e;
408            }
409            catch (Exception e)
410            {
411                throw new ConfigurationException("Unable to save the configuration to the file " + fileName, e);
412            }
413        }
414    
415        /**
416         * Save the configuration to the specified URL.
417         * This doesn't change the source of the configuration, use setURL()
418         * if you need it.
419         *
420         * @param url the URL
421         *
422         * @throws ConfigurationException if an error occurs during the save operation
423         */
424        public void save(URL url) throws ConfigurationException
425        {
426            // file URLs have to be converted to Files since FileURLConnection is
427            // read only (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4191800)
428            File file = ConfigurationUtils.fileFromURL(url);
429            if (file != null)
430            {
431                save(file);
432            }
433            else
434            {
435                // for non file URLs save through an URLConnection
436                OutputStream out = null;
437                try
438                {
439                    URLConnection connection = url.openConnection();
440                    connection.setDoOutput(true);
441    
442                    // use the PUT method for http URLs
443                    if (connection instanceof HttpURLConnection)
444                    {
445                        HttpURLConnection conn = (HttpURLConnection) connection;
446                        conn.setRequestMethod("PUT");
447                    }
448    
449                    out = connection.getOutputStream();
450                    save(out);
451    
452                    // check the response code for http URLs and throw an exception if an error occured
453                    if (connection instanceof HttpURLConnection)
454                    {
455                        HttpURLConnection conn = (HttpURLConnection) connection;
456                        if (conn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST)
457                        {
458                            throw new IOException("HTTP Error " + conn.getResponseCode() + " " + conn.getResponseMessage());
459                        }
460                    }
461                }
462                catch (IOException e)
463                {
464                    throw new ConfigurationException("Could not save to URL " + url, e);
465                }
466                finally
467                {
468                    closeSilent(out);
469                }
470            }
471        }
472    
473        /**
474         * Save the configuration to the specified file. The file is created
475         * automatically if it doesn't exist. This doesn't change the source
476         * of the configuration, use {@link #setFile} if you need it.
477         *
478         * @param file the target file
479         *
480         * @throws ConfigurationException if an error occurs during the save operation
481         */
482        public void save(File file) throws ConfigurationException
483        {
484            OutputStream out = null;
485    
486            try
487            {
488                // create the file if necessary
489                createPath(file);
490                out = new FileOutputStream(file);
491                save(out);
492            }
493            catch (IOException e)
494            {
495                throw new ConfigurationException("Unable to save the configuration to the file " + file, e);
496            }
497            finally
498            {
499                closeSilent(out);
500            }
501        }
502    
503        /**
504         * Save the configuration to the specified stream, using the encoding
505         * returned by {@link #getEncoding()}.
506         *
507         * @param out the output stream
508         *
509         * @throws ConfigurationException if an error occurs during the save operation
510         */
511        public void save(OutputStream out) throws ConfigurationException
512        {
513            save(out, getEncoding());
514        }
515    
516        /**
517         * Save the configuration to the specified stream, using the specified
518         * encoding. If the encoding is null the default encoding is used.
519         *
520         * @param out the output stream
521         * @param encoding the encoding to use
522         * @throws ConfigurationException if an error occurs during the save operation
523         */
524        public void save(OutputStream out, String encoding) throws ConfigurationException
525        {
526            Writer writer = null;
527    
528            if (encoding != null)
529            {
530                try
531                {
532                    writer = new OutputStreamWriter(out, encoding);
533                }
534                catch (UnsupportedEncodingException e)
535                {
536                    throw new ConfigurationException(
537                            "The requested encoding is not supported, try the default encoding.", e);
538                }
539            }
540    
541            if (writer == null)
542            {
543                writer = new OutputStreamWriter(out);
544            }
545    
546            save(writer);
547        }
548    
549        /**
550         * Return the name of the file.
551         *
552         * @return the file name
553         */
554        public String getFileName()
555        {
556            return fileName;
557        }
558    
559        /**
560         * Set the name of the file. The passed in file name can contain a
561         * relative path.
562         * It must be used when referring files with relative paths from classpath.
563         * Use <code>{@link AbstractFileConfiguration#setPath(String)
564         * setPath()}</code> to set a full qualified file name.
565         *
566         * @param fileName the name of the file
567         */
568        public void setFileName(String fileName)
569        {
570            sourceURL = null;
571            this.fileName = fileName;
572        }
573    
574        /**
575         * Return the base path.
576         *
577         * @return the base path
578         * @see FileConfiguration#getBasePath()
579         */
580        public String getBasePath()
581        {
582            return basePath;
583        }
584    
585        /**
586         * Sets the base path. The base path is typically either a path to a
587         * directory or a URL. Together with the value passed to the
588         * <code>setFileName()</code> method it defines the location of the
589         * configuration file to be loaded. The strategies for locating the file are
590         * quite tolerant. For instance if the file name is already an absolute path
591         * or a fully defined URL, the base path will be ignored. The base path can
592         * also be a URL, in which case the file name is interpreted in this URL's
593         * context. Because the base path is used by some of the derived classes for
594         * resolving relative file names it should contain a meaningful value. If
595         * other methods are used for determining the location of the configuration
596         * file (e.g. <code>setFile()</code> or <code>setURL()</code>), the
597         * base path is automatically set.
598         *
599         * @param basePath the base path.
600         */
601        public void setBasePath(String basePath)
602        {
603            sourceURL = null;
604            this.basePath = basePath;
605        }
606    
607        /**
608         * Return the file where the configuration is stored. If the base path is a
609         * URL with a protocol different than &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    }