View Javadoc

1   /*
2    * Copyright 2004-2005 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.apache.commons.configuration;
18  
19  import java.io.File;
20  import java.io.FileOutputStream;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.InputStreamReader;
24  import java.io.OutputStream;
25  import java.io.OutputStreamWriter;
26  import java.io.Reader;
27  import java.io.UnsupportedEncodingException;
28  import java.io.Writer;
29  import java.net.URL;
30  import java.util.Iterator;
31  
32  import org.apache.commons.configuration.reloading.InvariantReloadingStrategy;
33  import org.apache.commons.configuration.reloading.ReloadingStrategy;
34  
35  /***
36   * <p>Partial implementation of the <code>FileConfiguration</code> interface.
37   * Developpers of file based configuration may want to extend this class,
38   * the two methods left to implement are {@see AbstractFileConfiguration#load(Reader)}
39   * and {@see AbstractFileConfiguration#save(Reader)}.</p>
40   * <p>This base class already implements a couple of ways to specify the location
41   * of the file this configuration is based on. The following possibilities
42   * exist:
43   * <ul><li>URLs: With the method <code>setURL()</code> a full URL to the
44   * configuration source can be specified. This is the most flexible way. Note
45   * that the <code>save()</code> methods support only <em>file:</em> URLs.</li>
46   * <li>Files: The <code>setFile()</code> method allows to specify the
47   * configuration source as a file. This can be either a relative or an
48   * absolute file. In the former case the file is resolved based on the current
49   * directory.</li>
50   * <li>As file paths in string form: With the <code>setPath()</code> method a
51   * full path to a configuration file can be provided as a string.</li>
52   * <li>Separated as base path and file name: This is the native form in which
53   * the location is stored. The base path is a string defining either a local
54   * directory or a URL. It can be set using the <code>setBasePath()</code>
55   * method. The file name, non surprisingly, defines the name of the configuration
56   * file.</li></ul></p>
57   * <p>Note that the <code>load()</code> methods do not wipe out the configuration's
58   * content before the new configuration file is loaded. Thus it is very easy to
59   * construct a union configuration by simply loading multiple configuration
60   * files, e.g.</p>
61   * <p><pre>
62   * config.load(configFile1);
63   * config.load(configFile2);
64   * </pre></p>
65   * <p>After executing this code fragment, the resulting configuration will
66   * contain both the properties of configFile1 and configFile2. On the other
67   * hand, if the current configuration file is to be reloaded, <code>clear()</code>
68   * should be called first. Otherwise the properties are doubled. This behavior
69   * is analogous to the behavior of the <code>load(InputStream)</code> method
70   * in <code>java.util.Properties</code>.</p>
71   *
72   * @author Emmanuel Bourg
73   * @version $Revision: 156237 $, $Date: 2005-03-05 11:26:22 +0100 (Sa, 05 Mrz 2005) $
74   * @since 1.0-rc2
75   */
76  public abstract class AbstractFileConfiguration extends BaseConfiguration implements FileConfiguration
77  {
78      protected String fileName;
79  
80      protected String basePath;
81  
82      protected boolean autoSave;
83  
84      protected ReloadingStrategy strategy;
85  
86      private Object reloadLock = new Object();
87  
88      private String encoding;
89  
90      /***
91       * Default constructor
92       *
93       * @since 1.1
94       */
95      public AbstractFileConfiguration()
96      {
97          setReloadingStrategy(new InvariantReloadingStrategy());
98      }
99  
100     /***
101      * Creates and loads the configuration from the specified file. The passed
102      * in string must be a valid file name, either absolute or relativ.
103      *
104      * @param fileName The name of the file to load.
105      *
106      * @throws ConfigurationException Error while loading the file
107      * @since 1.1
108      */
109     public AbstractFileConfiguration(String fileName) throws ConfigurationException
110     {
111         this();
112 
113         // store the file name
114         setPath(fileName);
115 
116         // load the file
117         load();
118     }
119 
120     /***
121      * Creates and loads the configuration from the specified file.
122      *
123      * @param file The file to load.
124      * @throws ConfigurationException Error while loading the file
125      * @since 1.1
126      */
127     public AbstractFileConfiguration(File file) throws ConfigurationException
128     {
129         this();
130 
131         // set the file and update the url, the base path and the file name
132         setFile(file);
133 
134         // load the file
135         if (file.exists())
136         {
137             load();
138         }
139     }
140 
141     /***
142      * Creates and loads the configuration from the specified URL.
143      *
144      * @param url The location of the file to load.
145      * @throws ConfigurationException Error while loading the file
146      * @since 1.1
147      */
148     public AbstractFileConfiguration(URL url) throws ConfigurationException
149     {
150         this();
151 
152         // set the URL and update the base path and the file name
153         setURL(url);
154 
155         // load the file
156         load();
157     }
158 
159     /***
160      * Load the configuration from the underlying location.
161      *
162      * @throws ConfigurationException if loading of the configuration fails
163      */
164     public void load() throws ConfigurationException
165     {
166         load(getFileName());
167     }
168 
169     /***
170      * Locate the specified file and load the configuration.
171      *
172      * @param fileName the name of the file loaded
173      *
174      * @throws ConfigurationException
175      */
176     public void load(String fileName) throws ConfigurationException
177     {
178         try
179         {
180             URL url = ConfigurationUtils.locate(basePath, fileName);
181             if (url == null)
182             {
183                 throw new ConfigurationException("Cannot locate configuration source " + fileName);
184             }
185             load(url);
186         }
187         catch (ConfigurationException e)
188         {
189             throw e;
190         }
191         catch (Exception e)
192         {
193             throw new ConfigurationException(e.getMessage(), e);
194         }
195     }
196 
197     /***
198      * Load the configuration from the specified file.
199      *
200      * @param file the loaded file
201      *
202      * @throws ConfigurationException
203      */
204     public void load(File file) throws ConfigurationException
205     {
206         try
207         {
208             load(file.toURL());
209         }
210         catch (ConfigurationException e)
211         {
212             throw e;
213         }
214         catch (Exception e)
215         {
216             throw new ConfigurationException(e.getMessage(), e);
217         }
218     }
219 
220     /***
221      * Load the configuration from the specified URL.
222      *
223      * @param url the URL of the file loaded
224      *
225      * @throws ConfigurationException
226      */
227     public void load(URL url) throws ConfigurationException
228     {
229         InputStream in = null;
230 
231         try
232         {
233             in = url.openStream();
234             load(in);
235         }
236         catch (ConfigurationException e)
237         {
238             throw e;
239         }
240         catch (Exception e)
241         {
242             throw new ConfigurationException(e.getMessage(), e);
243         }
244         finally
245         {
246             // close the input stream
247             try
248             {
249                 if (in != null)
250                 {
251                     in.close();
252                 }
253             }
254             catch (IOException e)
255             {
256                 e.printStackTrace();
257             }
258         }
259     }
260 
261     /***
262      * Load the configuration from the specified stream, using the encoding
263      * returned by {@link #getEncoding()}.
264      *
265      * @param in the input stream
266      *
267      * @throws ConfigurationException
268      */
269     public void load(InputStream in) throws ConfigurationException
270     {
271         load(in, getEncoding());
272     }
273 
274     /***
275      * Load the configuration from the specified stream, using the specified
276      * encoding. If the encoding is null the default encoding is used.
277      *
278      * @param in the input stream
279      * @param encoding the encoding used. <code>null</code> to use the default encoding
280      *
281      * @throws ConfigurationException
282      */
283     public void load(InputStream in, String encoding) throws ConfigurationException
284     {
285         Reader reader = null;
286 
287         if (encoding != null)
288         {
289             try
290             {
291                 reader = new InputStreamReader(in, encoding);
292             }
293             catch (UnsupportedEncodingException e)
294             {
295                 throw new ConfigurationException(
296                         "The requested encoding is not supported, try the default encoding.", e);
297             }
298         }
299 
300         if (reader == null)
301         {
302             reader = new InputStreamReader(in);
303         }
304 
305         load(reader);
306     }
307 
308     /***
309      * Save the configuration.
310      *
311      * @throws ConfigurationException
312      */
313     public void save() throws ConfigurationException
314     {
315         save(fileName);
316         strategy.init();
317     }
318 
319     /***
320      * Save the configuration to the specified file. This doesn't change the
321      * source of the configuration, use setFileName() if you need it.
322      *
323      * @param fileName
324      *
325      * @throws ConfigurationException
326      */
327     public void save(String fileName) throws ConfigurationException
328     {
329         try
330         {
331             File file = ConfigurationUtils.getFile(basePath, fileName);
332             if (file == null)
333             {
334                 throw new ConfigurationException("Invalid file name for save: " + fileName);
335             }
336             save(file);
337         }
338         catch (ConfigurationException e)
339         {
340             throw e;
341         }
342         catch (Exception e)
343         {
344             throw new ConfigurationException(e.getMessage(), e);
345         }
346     }
347 
348     /***
349      * Save the configuration to the specified URL if it's a file URL.
350      * This doesn't change the source of the configuration, use setURL()
351      * if you need it.
352      *
353      * @param url
354      *
355      * @throws ConfigurationException
356      */
357     public void save(URL url) throws ConfigurationException
358     {
359         File file = ConfigurationUtils.fileFromURL(url);
360         if (file != null)
361         {
362             save(file);
363         }
364         else
365         {
366             throw new ConfigurationException("Could not save to URL " + url);
367         }
368     }
369 
370     /***
371      * Save the configuration to the specified file. The file is created
372      * automatically if it doesn't exist. This doesn't change the source
373      * of the configuration, use {@link #setFile} if you need it.
374      *
375      * @param file
376      *
377      * @throws ConfigurationException
378      */
379     public void save(File file) throws ConfigurationException
380     {
381         OutputStream out = null;
382 
383         try
384         {
385             // create the file if necessary
386             createPath(file);
387             out = new FileOutputStream(file);
388             save(out);
389         }
390         catch (IOException e)
391         {
392             throw new ConfigurationException(e.getMessage(), e);
393         }
394         finally
395         {
396             // close the output stream
397             try
398             {
399                 if (out != null)
400                 {
401                     out.close();
402                 }
403             }
404             catch (IOException e)
405             {
406                 e.printStackTrace();
407             }
408         }
409     }
410 
411     /***
412      * Save the configuration to the specified stream, using the encoding
413      * returned by {@link #getEncoding()}.
414      *
415      * @param out
416      *
417      * @throws ConfigurationException
418      */
419     public void save(OutputStream out) throws ConfigurationException
420     {
421         save(out, getEncoding());
422     }
423 
424     /***
425      * Save the configuration to the specified stream, using the specified
426      * encoding. If the encoding is null the default encoding is used.
427      *
428      * @param out
429      * @param encoding
430      * @throws ConfigurationException
431      */
432     public void save(OutputStream out, String encoding) throws ConfigurationException
433     {
434         Writer writer = null;
435 
436         if (encoding != null)
437         {
438             try
439             {
440                 writer = new OutputStreamWriter(out, encoding);
441             }
442             catch (UnsupportedEncodingException e)
443             {
444                 throw new ConfigurationException(
445                         "The requested encoding is not supported, try the default encoding.", e);
446             }
447         }
448 
449         if (writer == null)
450         {
451             writer = new OutputStreamWriter(out);
452         }
453 
454         save(writer);
455     }
456 
457     /***
458      * Return the name of the file.
459      */
460     public String getFileName()
461     {
462         return fileName;
463     }
464 
465     /***
466      * Set the name of the file. The passed in file name should not contain a
467      * path. Use <code>{@link AbstractFileConfiguration#setPath(String)
468      * setPath()}</code> to set a full qualified file name.
469      *
470      * @param fileName the name of the file
471      */
472     public void setFileName(String fileName)
473     {
474         this.fileName = fileName;
475     }
476 
477     /***
478      * Return the base path.
479      */
480     public String getBasePath()
481     {
482         return basePath;
483     }
484 
485     /***
486      * Set the base path. Relative configurations are loaded from this path.
487      * The base path can be either a path to a directory or a URL.
488      *
489      * @param basePath the base path.
490      */
491     public void setBasePath(String basePath)
492     {
493         this.basePath = basePath;
494     }
495 
496     /***
497      * Return the file where the configuration is stored. If the base path is
498      * a URL with a protocol different than &quot;file&quot;, the return value
499      * will not point to a valid file object.
500      * 
501      * @return the file where the configuration is stored
502      */
503     public File getFile()
504     {
505         return ConfigurationUtils.getFile(getBasePath(), getFileName());
506     }
507 
508     /***
509      * Set the file where the configuration is stored. The passed in file is
510      * made absolute if it is not yet. Then the file's path component becomes
511      * the base path and its name component becomes the file name.
512      *
513      * @param file the file where the configuration is stored
514      */
515     public void setFile(File file)
516     {
517         setFileName(file.getName());
518         setBasePath((file.getParentFile() != null) ? file.getParentFile().getAbsolutePath() : null);
519     }
520 
521     /***
522      * Returns the full path to the file this configuration is based on. The
523      * return value is valid only if this configuration is based on a file on
524      * the local disk.
525      * 
526      * @return the full path to the configuration file
527      */
528     public String getPath()
529     {
530         return getFile().getAbsolutePath();
531     }
532 
533     /***
534      * Sets the location of this configuration as a full path name. The passed
535      * in path should represent a valid file name.
536      * 
537      * @param path the full path name of the configuration file
538      */
539     public void setPath(String path)
540     {
541         setFile(new File(path));
542     }
543 
544     /***
545      * Return the URL where the configuration is stored.
546      * 
547      * @return the configuration's location as URL
548      */
549     public URL getURL()
550     {
551         return ConfigurationUtils.locate(getBasePath(), getFileName());
552     }
553 
554     /***
555      * Set the location of this configuration as a URL. For loading this can be
556      * an arbitrary URL with a supported protocol. If the configuration is to
557      * be saved, too, a URL with the &quot;file&quot; protocol should be
558      * provided.
559      *
560      * @param url the location of this configuration as URL
561      */
562     public void setURL(URL url)
563     {
564         setBasePath(ConfigurationUtils.getBasePath(url));
565         setFileName(ConfigurationUtils.getFileName(url));
566     }
567 
568     public void setAutoSave(boolean autoSave)
569     {
570         this.autoSave = autoSave;
571     }
572 
573     public boolean isAutoSave()
574     {
575         return autoSave;
576     }
577 
578     /***
579      * Save the configuration if the automatic persistence is enabled
580      * and if a file is specified.
581      */
582     protected void possiblySave()
583     {
584         if (autoSave && fileName != null)
585         {
586             try
587             {
588                 save();
589             }
590             catch (ConfigurationException e)
591             {
592                 throw new ConfigurationRuntimeException("Failed to auto-save", e);
593             }
594         }
595     }
596 
597     protected void addPropertyDirect(String key, Object obj)
598     {
599         super.addPropertyDirect(key, obj);
600         possiblySave();
601     }
602 
603     public void clearProperty(String key)
604     {
605         super.clearProperty(key);
606         possiblySave();
607     }
608 
609     public ReloadingStrategy getReloadingStrategy()
610     {
611         return strategy;
612     }
613 
614     public void setReloadingStrategy(ReloadingStrategy strategy)
615     {
616         this.strategy = strategy;
617         strategy.setConfiguration(this);
618         strategy.init();
619     }
620 
621     public void reload()
622     {
623         synchronized (reloadLock)
624         {
625             if (strategy.reloadingRequired())
626             {
627                 try
628                 {
629                     clear();
630                     load();
631 
632                     // notify the strategy
633                     strategy.reloadingPerformed();
634                 }
635                 catch (Exception e)
636                 {
637                     e.printStackTrace();
638                     // todo rollback the changes if the file can't be reloaded
639                 }
640             }
641         }
642     }
643 
644     public Object getProperty(String key)
645     {
646         reload();
647         return super.getProperty(key);
648     }
649 
650     public boolean isEmpty()
651     {
652         reload();
653         return super.isEmpty();
654     }
655 
656     public boolean containsKey(String key)
657     {
658         reload();
659         return super.containsKey(key);
660     }
661 
662     public Iterator getKeys()
663     {
664         reload();
665         return super.getKeys();
666     }
667 
668     /***
669      * Create the path to the specified file.
670      */
671     private void createPath(File file)
672     {
673         if (file != null)
674         {
675             // create the path to the file if the file doesn't exist
676             if (!file.exists())
677             {
678                 File parent = file.getParentFile();
679                 if (parent != null && !parent.exists())
680                 {
681                     parent.mkdirs();
682                 }
683             }
684         }
685     }
686 
687     public String getEncoding()
688     {
689         return encoding;
690     }
691 
692     public void setEncoding(String encoding)
693     {
694         this.encoding = encoding;
695     }
696 }