Coverage Report - org.apache.commons.configuration.AbstractFileConfiguration
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractFileConfiguration
90%
238/265
97%
36/37
2,479
 
 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  1353
     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  1353
     {
 120  1353
         initReloadingStrategy();
 121  1353
         setLogger(LogFactory.getLog(getClass()));
 122  1353
         addErrorLogListener();
 123  1353
     }
 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  270
         this();
 137  
 
 138  
         // store the file name
 139  270
         setFileName(fileName);
 140  
 
 141  
         // load the file
 142  270
         load();
 143  268
     }
 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  21
         this();
 155  
 
 156  
         // set the file and update the url, the base path and the file name
 157  21
         setFile(file);
 158  
 
 159  
         // load the file
 160  21
         if (file.exists())
 161  
         {
 162  18
             load();
 163  
         }
 164  20
     }
 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  2
         this();
 176  
 
 177  
         // set the URL and update the base path and the file name
 178  2
         setURL(url);
 179  
 
 180  
         // load the file
 181  2
         load();
 182  2
     }
 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  2898
         if (sourceURL != null)
 192  
         {
 193  2142
             load(sourceURL);
 194  
         }
 195  
         else
 196  
         {
 197  756
             load(getFileName());
 198  
         }
 199  2871
     }
 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  763
             URL url = ConfigurationUtils.locate(basePath, fileName);
 214  
 
 215  763
             if (url == null)
 216  
             {
 217  23
                 throw new ConfigurationException("Cannot locate configuration source " + fileName);
 218  
             }
 219  740
             load(url);
 220  
         }
 221  28
         catch (ConfigurationException e)
 222  
         {
 223  28
             throw e;
 224  
         }
 225  0
         catch (Exception e)
 226  
         {
 227  0
             throw new ConfigurationException("Unable to load the configuration file " + fileName, e);
 228  735
         }
 229  735
     }
 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  13
             load(ConfigurationUtils.toURL(file));
 244  
         }
 245  3
         catch (ConfigurationException e)
 246  
         {
 247  3
             throw e;
 248  
         }
 249  0
         catch (Exception e)
 250  
         {
 251  0
             throw new ConfigurationException("Unable to load the configuration file " + file, e);
 252  10
         }
 253  10
     }
 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  7297
         if (sourceURL == null)
 266  
         {
 267  752
             if (StringUtils.isEmpty(getBasePath()))
 268  
             {
 269  
                 // ensure that we have a valid base path
 270  354
                 setBasePath(url.toString());
 271  
             }
 272  752
             sourceURL = url;
 273  
         }
 274  
 
 275  
         // throw an exception if the target URL is a directory
 276  7297
         File file = ConfigurationUtils.fileFromURL(url);
 277  7297
         if (file != null && file.isDirectory())
 278  
         {
 279  4
             throw new ConfigurationException("Cannot load a configuration from a directory");
 280  
         }
 281  
 
 282  7293
         InputStream in = null;
 283  
 
 284  
         try
 285  
         {
 286  7293
             in = url.openStream();
 287  7293
             load(in);
 288  7289
         }
 289  4
         catch (ConfigurationException e)
 290  
         {
 291  4
             throw e;
 292  
         }
 293  0
         catch (Exception e)
 294  
         {
 295  0
             throw new ConfigurationException("Unable to load the configuration from the URL " + url, e);
 296  
         }
 297  
         finally
 298  
         {
 299  
             // close the input stream
 300  4
             try
 301  
             {
 302  7293
                 if (in != null)
 303  
                 {
 304  7293
                     in.close();
 305  
                 }
 306  
             }
 307  0
             catch (IOException e)
 308  
             {
 309  0
                 getLogger().warn("Could not close input stream", e);
 310  14586
             }
 311  7289
         }
 312  7289
     }
 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  6839
         load(in, getEncoding());
 325  6838
     }
 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  6840
         Reader reader = null;
 339  
 
 340  6840
         if (encoding != null)
 341  
         {
 342  
             try
 343  
             {
 344  6774
                 reader = new InputStreamReader(in, encoding);
 345  
             }
 346  0
             catch (UnsupportedEncodingException e)
 347  
             {
 348  0
                 throw new ConfigurationException(
 349  
                         "The requested encoding is not supported, try the default encoding.", e);
 350  6774
             }
 351  
         }
 352  
 
 353  6840
         if (reader == null)
 354  
         {
 355  66
             reader = new InputStreamReader(in);
 356  
         }
 357  
 
 358  6840
         load(reader);
 359  6839
     }
 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  50
         if (getFileName() == null)
 371  
         {
 372  4
             throw new ConfigurationException("No file name has been set!");
 373  
         }
 374  
 
 375  46
         if (sourceURL != null)
 376  
         {
 377  22
             save(sourceURL);
 378  
         }
 379  
         else
 380  
         {
 381  24
             save(fileName);
 382  
         }
 383  46
         strategy.init();
 384  46
     }
 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  35
             File file = ConfigurationUtils.getFile(basePath, fileName);
 399  35
             if (file == null)
 400  
             {
 401  1
                 throw new ConfigurationException("Invalid file name for save: " + fileName);
 402  
             }
 403  34
             save(file);
 404  
         }
 405  1
         catch (ConfigurationException e)
 406  
         {
 407  1
             throw e;
 408  
         }
 409  0
         catch (Exception e)
 410  
         {
 411  0
             throw new ConfigurationException("Unable to save the configuration to the file " + fileName, e);
 412  34
         }
 413  34
     }
 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  27
         File file = ConfigurationUtils.fileFromURL(url);
 429  27
         if (file != null)
 430  
         {
 431  23
             save(file);
 432  
         }
 433  
         else
 434  
         {
 435  
             // for non file URLs save through an URLConnection
 436  4
             OutputStream out = null;
 437  
             try
 438  
             {
 439  4
                 URLConnection connection = url.openConnection();
 440  4
                 connection.setDoOutput(true);
 441  
 
 442  
                 // use the PUT method for http URLs
 443  4
                 if (connection instanceof HttpURLConnection)
 444  
                 {
 445  3
                     HttpURLConnection conn = (HttpURLConnection) connection;
 446  3
                     conn.setRequestMethod("PUT");
 447  
                 }
 448  
 
 449  4
                 out = connection.getOutputStream();
 450  4
                 save(out);
 451  
 
 452  
                 // check the response code for http URLs and throw an exception if an error occured
 453  4
                 if (connection instanceof HttpURLConnection)
 454  
                 {
 455  3
                     HttpURLConnection conn = (HttpURLConnection) connection;
 456  3
                     if (conn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST)
 457  
                     {
 458  2
                         throw new IOException("HTTP Error " + conn.getResponseCode() + " " + conn.getResponseMessage());
 459  
                     }
 460  
                 }
 461  
             }
 462  2
             catch (IOException e)
 463  
             {
 464  2
                 throw new ConfigurationException("Could not save to URL " + url, e);
 465  
             }
 466  
             finally
 467  
             {
 468  4
                 closeSilent(out);
 469  2
             }
 470  
         }
 471  25
     }
 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  93
         OutputStream out = null;
 485  
 
 486  
         try
 487  
         {
 488  
             // create the file if necessary
 489  93
             createPath(file);
 490  93
             out = new FileOutputStream(file);
 491  93
             save(out);
 492  
         }
 493  0
         catch (IOException e)
 494  
         {
 495  0
             throw new ConfigurationException("Unable to save the configuration to the file " + file, e);
 496  
         }
 497  
         finally
 498  
         {
 499  93
             closeSilent(out);
 500  92
         }
 501  92
     }
 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  98
         save(out, getEncoding());
 514  97
     }
 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  99
         Writer writer = null;
 527  
 
 528  99
         if (encoding != null)
 529  
         {
 530  
             try
 531  
             {
 532  36
                 writer = new OutputStreamWriter(out, encoding);
 533  
             }
 534  0
             catch (UnsupportedEncodingException e)
 535  
             {
 536  0
                 throw new ConfigurationException(
 537  
                         "The requested encoding is not supported, try the default encoding.", e);
 538  36
             }
 539  
         }
 540  
 
 541  99
         if (writer == null)
 542  
         {
 543  63
             writer = new OutputStreamWriter(out);
 544  
         }
 545  
 
 546  99
         save(writer);
 547  98
     }
 548  
 
 549  
     /**
 550  
      * Return the name of the file.
 551  
      *
 552  
      * @return the file name
 553  
      */
 554  
     public String getFileName()
 555  
     {
 556  912
         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  803
         sourceURL = null;
 571  803
         this.fileName = fileName;
 572  803
     }
 573  
 
 574  
     /**
 575  
      * Return the base path.
 576  
      *
 577  
      * @return the base path
 578  
      * @see FileConfiguration#getBasePath()
 579  
      */
 580  
     public String getBasePath()
 581  
     {
 582  5290
         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  817
         sourceURL = null;
 604  817
         this.basePath = basePath;
 605  817
     }
 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  18
         if (getFileName() == null && sourceURL == null)
 618  
         {
 619  3
             return null;
 620  
         }
 621  15
         else if (sourceURL != null)
 622  
         {
 623  6
             return ConfigurationUtils.fileFromURL(sourceURL);
 624  
         }
 625  
         else
 626  
         {
 627  9
             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  299
         sourceURL = null;
 641  299
         setFileName(file.getName());
 642  299
         setBasePath((file.getParentFile() != null) ? file.getParentFile()
 643  
                 .getAbsolutePath() : null);
 644  299
     }
 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  2
         String path = null;
 658  2
         File file = getFile();
 659  
         // if resource was loaded from jar file may be null
 660  2
         if (file != null)
 661  
         {
 662  2
             path = file.getAbsolutePath();
 663  
         }
 664  
 
 665  
         // try to see if file was loaded from a jar
 666  2
         if (path == null)
 667  
         {
 668  0
             if (sourceURL != null)
 669  
             {
 670  0
                 path = sourceURL.getPath();
 671  
             }
 672  
             else
 673  
             {
 674  
                 try
 675  
                 {
 676  0
                     path = ConfigurationUtils.getURL(getBasePath(), getFileName()).getPath();
 677  
                 }
 678  0
                 catch (MalformedURLException e)
 679  
                 {
 680  
                     // simply ignore it and return null
 681  
                     ;
 682  0
                 }
 683  
             }
 684  
         }
 685  
 
 686  2
         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  1
         setFile(new File(path));
 702  1
     }
 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  8683
         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  23
         setBasePath(ConfigurationUtils.getBasePath(url));
 726  23
         setFileName(ConfigurationUtils.getFileName(url));
 727  23
         sourceURL = url;
 728  23
     }
 729  
 
 730  
     public void setAutoSave(boolean autoSave)
 731  
     {
 732  17763
         this.autoSave = autoSave;
 733  17763
     }
 734  
 
 735  
     public boolean isAutoSave()
 736  
     {
 737  8878
         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  127340
         if (autoSave && fileName != null)
 747  
         {
 748  
             try
 749  
             {
 750  19
                 save();
 751  
             }
 752  0
             catch (ConfigurationException e)
 753  
             {
 754  0
                 throw new ConfigurationRuntimeException("Failed to auto-save", e);
 755  19
             }
 756  
         }
 757  127340
     }
 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  119050
         super.addProperty(key, value);
 769  119049
         possiblySave();
 770  119049
     }
 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  19
         super.setProperty(key, value);
 783  19
         possiblySave();
 784  19
     }
 785  
 
 786  
     public void clearProperty(String key)
 787  
     {
 788  65
         super.clearProperty(key);
 789  65
         possiblySave();
 790  65
     }
 791  
 
 792  
     public ReloadingStrategy getReloadingStrategy()
 793  
     {
 794  14
         return strategy;
 795  
     }
 796  
 
 797  
     public void setReloadingStrategy(ReloadingStrategy strategy)
 798  
     {
 799  1407
         this.strategy = strategy;
 800  1407
         strategy.setConfiguration(this);
 801  1407
         strategy.init();
 802  1407
     }
 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  145983
         synchronized (reloadLock)
 817  
         {
 818  145983
             if (noReload == 0)
 819  
             {
 820  
                 try
 821  
                 {
 822  27924
                     enterNoReload(); // avoid reentrant calls
 823  
 
 824  27924
                     if (strategy.reloadingRequired())
 825  
                     {
 826  2114
                         if (getLogger().isInfoEnabled())
 827  
                         {
 828  0
                             getLogger().info("Reloading configuration. URL is " + getURL());
 829  
                         }
 830  2114
                         fireEvent(EVENT_RELOAD, null, getURL(), true);
 831  2114
                         setDetailEvents(false);
 832  2114
                         boolean autoSaveBak = this.isAutoSave(); // save the current state
 833  2114
                         this.setAutoSave(false); // deactivate autoSave to prevent information loss
 834  
                         try
 835  
                         {
 836  2114
                             clear();
 837  2114
                             load();
 838  
                         }
 839  
                         finally
 840  
                         {
 841  2114
                             this.setAutoSave(autoSaveBak); // set autoSave to previous value
 842  2114
                             setDetailEvents(true);
 843  2113
                         }
 844  2113
                         fireEvent(EVENT_RELOAD, null, getURL(), false);
 845  
 
 846  
                         // notify the strategy
 847  2113
                         strategy.reloadingPerformed();
 848  
                     }
 849  
                 }
 850  1
                 catch (Exception e)
 851  
                 {
 852  1
                     fireError(EVENT_RELOAD, null, null, e);
 853  
                     // todo rollback the changes if the file can't be reloaded
 854  
                 }
 855  
                 finally
 856  
                 {
 857  27924
                     exitNoReload();
 858  27924
                 }
 859  
             }
 860  145983
         }
 861  145983
     }
 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  274697
         synchronized (reloadLock)
 876  
         {
 877  274697
             noReload++;
 878  274697
         }
 879  274697
     }
 880  
 
 881  
     /**
 882  
      * Leaves the &quot;No reloading mode&quot;.
 883  
      *
 884  
      * @see #enterNoReload()
 885  
      */
 886  
     protected void exitNoReload()
 887  
     {
 888  274697
         synchronized (reloadLock)
 889  
         {
 890  274697
             if (noReload > 0) // paranoia check
 891  
             {
 892  274697
                 noReload--;
 893  
             }
 894  274697
         }
 895  274697
     }
 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  246596
         enterNoReload();
 911  
         try
 912  
         {
 913  246596
             super.fireEvent(type, propName, propValue, before);
 914  
         }
 915  
         finally
 916  
         {
 917  246596
             exitNoReload();
 918  246596
         }
 919  246596
     }
 920  
 
 921  
     public Object getProperty(String key)
 922  
     {
 923  132070
         synchronized (reloadLock)
 924  
         {
 925  132070
             reload();
 926  132070
             return super.getProperty(key);
 927  0
         }
 928  
     }
 929  
 
 930  
     public boolean isEmpty()
 931  
     {
 932  10
         reload();
 933  10
         return super.isEmpty();
 934  
     }
 935  
 
 936  
     public boolean containsKey(String key)
 937  
     {
 938  1259
         reload();
 939  1259
         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  141
         reload();
 960  141
         List keyList = new LinkedList();
 961  141
         enterNoReload();
 962  
         try
 963  
         {
 964  141
             for (Iterator it = super.getKeys(); it.hasNext();)
 965  
             {
 966  2682
                 keyList.add(it.next());
 967  
             }
 968  
 
 969  141
             return keyList.iterator();
 970  
         }
 971  
         finally
 972  
         {
 973  141
             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  93
         if (file != null)
 985  
         {
 986  
             // create the path to the file if the file doesn't exist
 987  93
             if (!file.exists())
 988  
             {
 989  57
                 File parent = file.getParentFile();
 990  57
                 if (parent != null && !parent.exists())
 991  
                 {
 992  3
                     parent.mkdirs();
 993  
                 }
 994  
             }
 995  
         }
 996  93
     }
 997  
 
 998  
     public String getEncoding()
 999  
     {
 1000  7008
         return encoding;
 1001  
     }
 1002  
 
 1003  
     public void setEncoding(String encoding)
 1004  
     {
 1005  15
         this.encoding = encoding;
 1006  15
     }
 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  4
         AbstractFileConfiguration copy = (AbstractFileConfiguration) super.clone();
 1022  4
         copy.setBasePath(null);
 1023  4
         copy.setFileName(null);
 1024  4
         copy.initReloadingStrategy();
 1025  4
         return copy;
 1026  
     }
 1027  
 
 1028  
     /**
 1029  
      * Helper method for initializing the reloading strategy.
 1030  
      */
 1031  
     private void initReloadingStrategy()
 1032  
     {
 1033  1357
         setReloadingStrategy(new InvariantReloadingStrategy());
 1034  1357
     }
 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  97
             if (out != null)
 1048  
             {
 1049  97
                 out.close();
 1050  
             }
 1051  
         }
 1052  0
         catch (IOException e)
 1053  
         {
 1054  0
             getLogger().warn("Could not close output stream", e);
 1055  97
         }
 1056  97
     }
 1057  
 }