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