View Javadoc

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