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