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  import org.apache.commons.lang.StringUtils;
35  
36  /***
37   * <p>Partial implementation of the <code>FileConfiguration</code> interface.
38   * Developpers of file based configuration may want to extend this class,
39   * the two methods left to implement are <code>{@link FileConfiguration#load(Reader)}</code>
40   * and <code>{@link FileConfiguration#save(Writer)}.</p>
41   * <p>This base class already implements a couple of ways to specify the location
42   * of the file this configuration is based on. The following possibilities
43   * exist:
44   * <ul><li>URLs: With the method <code>setURL()</code> a full URL to the
45   * configuration source can be specified. This is the most flexible way. Note
46   * that the <code>save()</code> methods support only <em>file:</em> URLs.</li>
47   * <li>Files: The <code>setFile()</code> method allows to specify the
48   * configuration source as a file. This can be either a relative or an
49   * absolute file. In the former case the file is resolved based on the current
50   * directory.</li>
51   * <li>As file paths in string form: With the <code>setPath()</code> method a
52   * full path to a configuration file can be provided as a string.</li>
53   * <li>Separated as base path and file name: This is the native form in which
54   * the location is stored. The base path is a string defining either a local
55   * directory or a URL. It can be set using the <code>setBasePath()</code>
56   * method. The file name, non surprisingly, defines the name of the configuration
57   * file.</li></ul></p>
58   * <p>Note that the <code>load()</code> methods do not wipe out the configuration's
59   * content before the new configuration file is loaded. Thus it is very easy to
60   * construct a union configuration by simply loading multiple configuration
61   * files, e.g.</p>
62   * <p><pre>
63   * config.load(configFile1);
64   * config.load(configFile2);
65   * </pre></p>
66   * <p>After executing this code fragment, the resulting configuration will
67   * contain both the properties of configFile1 and configFile2. On the other
68   * hand, if the current configuration file is to be reloaded, <code>clear()</code>
69   * should be called first. Otherwise the properties are doubled. This behavior
70   * is analogous to the behavior of the <code>load(InputStream)</code> method
71   * in <code>java.util.Properties</code>.</p>
72   *
73   * @author Emmanuel Bourg
74   * @version $Revision$, $Date: 2005-12-14 20:59:07 +0100 (Wed, 14 Dec 2005) $
75   * @since 1.0-rc2
76   */
77  public abstract class AbstractFileConfiguration extends BaseConfiguration implements FileConfiguration
78  {
79      /*** Stores the file name.*/
80      protected String fileName;
81  
82      /*** Stores the base path.*/
83      protected String basePath;
84  
85      /*** The auto save flag.*/
86      protected boolean autoSave;
87  
88      /*** Holds a reference to the reloading strategy.*/
89      protected ReloadingStrategy strategy;
90  
91      /*** A lock object for protecting reload operations.*/
92      private Object reloadLock = new Object();
93  
94      /*** Stores the encoding of the configuration file.*/
95      private String encoding;
96  
97      /*** Stores the URL from which the configuration file was loaded.*/
98      private URL sourceURL;
99  
100     /*** A counter that prohibits reloading.*/
101     private int noReload;
102 
103     /***
104      * Default constructor
105      *
106      * @since 1.1
107      */
108     public AbstractFileConfiguration()
109     {
110         setReloadingStrategy(new InvariantReloadingStrategy());
111     }
112 
113     /***
114      * Creates and loads the configuration from the specified file. The passed
115      * in string must be a valid file name, either absolute or relativ.
116      *
117      * @param fileName The name of the file to load.
118      *
119      * @throws ConfigurationException Error while loading the file
120      * @since 1.1
121      */
122     public AbstractFileConfiguration(String fileName) throws ConfigurationException
123     {
124         this();
125 
126         // store the file name
127         setPath(fileName);
128 
129         // load the file
130         load();
131     }
132 
133     /***
134      * Creates and loads the configuration from the specified file.
135      *
136      * @param file The file to load.
137      * @throws ConfigurationException Error while loading the file
138      * @since 1.1
139      */
140     public AbstractFileConfiguration(File file) throws ConfigurationException
141     {
142         this();
143 
144         // set the file and update the url, the base path and the file name
145         setFile(file);
146 
147         // load the file
148         if (file.exists())
149         {
150             load();
151         }
152     }
153 
154     /***
155      * Creates and loads the configuration from the specified URL.
156      *
157      * @param url The location of the file to load.
158      * @throws ConfigurationException Error while loading the file
159      * @since 1.1
160      */
161     public AbstractFileConfiguration(URL url) throws ConfigurationException
162     {
163         this();
164 
165         // set the URL and update the base path and the file name
166         setURL(url);
167 
168         // load the file
169         load();
170     }
171 
172     /***
173      * Load the configuration from the underlying location.
174      *
175      * @throws ConfigurationException if loading of the configuration fails
176      */
177     public void load() throws ConfigurationException
178     {
179         if (sourceURL != null)
180         {
181             load(sourceURL);
182         }
183         else
184         {
185             load(getFileName());
186         }
187     }
188 
189     /***
190      * Locate the specified file and load the configuration. This does not
191      * change the source of the configuration (i.e. the internally maintained file name).
192      * Use one of the setter methods for this purpose.
193      *
194      * @param fileName the name of the file to be loaded
195      * @throws ConfigurationException if an error occurs
196      */
197     public void load(String fileName) throws ConfigurationException
198     {
199         try
200         {
201             URL url = ConfigurationUtils.locate(basePath, fileName);
202 
203             if (url == null)
204             {
205                 throw new ConfigurationException("Cannot locate configuration source " + fileName);
206             }
207             load(url);
208         }
209         catch (ConfigurationException e)
210         {
211             throw e;
212         }
213         catch (Exception e)
214         {
215             throw new ConfigurationException(e.getMessage(), e);
216         }
217     }
218 
219     /***
220      * Load the configuration from the specified file. This does not change
221      * the source of the configuration (i.e. the internally maintained file
222      * name). Use one of the setter methods for this purpose.
223      *
224      * @param file the file to load
225      * @throws ConfigurationException if an error occurs
226      */
227     public void load(File file) throws ConfigurationException
228     {
229         try
230         {
231             load(file.toURL());
232         }
233         catch (ConfigurationException e)
234         {
235             throw e;
236         }
237         catch (Exception e)
238         {
239             throw new ConfigurationException(e.getMessage(), e);
240         }
241     }
242 
243     /***
244      * Load the configuration from the specified URL. This does not change the
245      * source of the configuration (i.e. the internally maintained file name).
246      * Use on of the setter methods for this purpose.
247      *
248      * @param url the URL of the file to be loaded
249      * @throws ConfigurationException if an error occurs
250      */
251     public void load(URL url) throws ConfigurationException
252     {
253         if (sourceURL == null)
254         {
255             if (StringUtils.isEmpty(getBasePath()))
256             {
257                 // ensure that we have a valid base path
258                 setBasePath(url.toString());
259             }
260             sourceURL = url;
261         }
262 
263         // throw an exception if the target URL is a directory
264         File file = ConfigurationUtils.fileFromURL(url);
265         if (file != null && file.isDirectory())
266         {
267             throw new ConfigurationException("Cannot load a configuration from a directory");
268         }
269 
270         InputStream in = null;
271 
272         try
273         {
274             in = url.openStream();
275             load(in);
276         }
277         catch (ConfigurationException e)
278         {
279             throw e;
280         }
281         catch (Exception e)
282         {
283             throw new ConfigurationException(e.getMessage(), e);
284         }
285         finally
286         {
287             // close the input stream
288             try
289             {
290                 if (in != null)
291                 {
292                     in.close();
293                 }
294             }
295             catch (IOException e)
296             {
297                 e.printStackTrace();
298             }
299         }
300     }
301 
302     /***
303      * Load the configuration from the specified stream, using the encoding
304      * returned by {@link #getEncoding()}.
305      *
306      * @param in the input stream
307      *
308      * @throws ConfigurationException if an error occurs during the load operation
309      */
310     public void load(InputStream in) throws ConfigurationException
311     {
312         load(in, getEncoding());
313     }
314 
315     /***
316      * Load the configuration from the specified stream, using the specified
317      * encoding. If the encoding is null the default encoding is used.
318      *
319      * @param in the input stream
320      * @param encoding the encoding used. <code>null</code> to use the default encoding
321      *
322      * @throws ConfigurationException if an error occurs during the load operation
323      */
324     public void load(InputStream in, String encoding) throws ConfigurationException
325     {
326         Reader reader = null;
327 
328         if (encoding != null)
329         {
330             try
331             {
332                 reader = new InputStreamReader(in, encoding);
333             }
334             catch (UnsupportedEncodingException e)
335             {
336                 throw new ConfigurationException(
337                         "The requested encoding is not supported, try the default encoding.", e);
338             }
339         }
340 
341         if (reader == null)
342         {
343             reader = new InputStreamReader(in);
344         }
345 
346         load(reader);
347     }
348 
349     /***
350      * Save the configuration. Before this method can be called a valid file
351      * name must have been set.
352      *
353      * @throws ConfigurationException if an error occurs or no file name has
354      * been set yet
355      */
356     public void save() throws ConfigurationException
357     {
358         if (getFileName() == null)
359         {
360             throw new ConfigurationException("No file name has been set!");
361         }
362 
363         if (sourceURL != null)
364         {
365             save(sourceURL);
366         }
367         else
368         {
369             save(fileName);
370         }
371         strategy.init();
372     }
373 
374     /***
375      * Save the configuration to the specified file. This doesn't change the
376      * source of the configuration, use setFileName() if you need it.
377      *
378      * @param fileName the file name
379      *
380      * @throws ConfigurationException if an error occurs during the save operation
381      */
382     public void save(String fileName) throws ConfigurationException
383     {
384         try
385         {
386             File file = ConfigurationUtils.getFile(basePath, fileName);
387             if (file == null)
388             {
389                 throw new ConfigurationException("Invalid file name for save: " + fileName);
390             }
391             save(file);
392         }
393         catch (ConfigurationException e)
394         {
395             throw e;
396         }
397         catch (Exception e)
398         {
399             throw new ConfigurationException(e.getMessage(), e);
400         }
401     }
402 
403     /***
404      * Save the configuration to the specified URL if it's a file URL.
405      * This doesn't change the source of the configuration, use setURL()
406      * if you need it.
407      *
408      * @param url the URL
409      *
410      * @throws ConfigurationException if an error occurs during the save operation
411      */
412     public void save(URL url) throws ConfigurationException
413     {
414         File file = ConfigurationUtils.fileFromURL(url);
415         if (file != null)
416         {
417             save(file);
418         }
419         else
420         {
421             throw new ConfigurationException("Could not save to URL " + url);
422         }
423     }
424 
425     /***
426      * Save the configuration to the specified file. The file is created
427      * automatically if it doesn't exist. This doesn't change the source
428      * of the configuration, use {@link #setFile} if you need it.
429      *
430      * @param file the target file
431      *
432      * @throws ConfigurationException if an error occurs during the save operation
433      */
434     public void save(File file) throws ConfigurationException
435     {
436         OutputStream out = null;
437 
438         try
439         {
440             // create the file if necessary
441             createPath(file);
442             out = new FileOutputStream(file);
443             save(out);
444         }
445         catch (IOException e)
446         {
447             throw new ConfigurationException(e.getMessage(), e);
448         }
449         finally
450         {
451             // close the output stream
452             try
453             {
454                 if (out != null)
455                 {
456                     out.close();
457                 }
458             }
459             catch (IOException e)
460             {
461                 e.printStackTrace();
462             }
463         }
464     }
465 
466     /***
467      * Save the configuration to the specified stream, using the encoding
468      * returned by {@link #getEncoding()}.
469      *
470      * @param out the output stream
471      *
472      * @throws ConfigurationException if an error occurs during the save operation
473      */
474     public void save(OutputStream out) throws ConfigurationException
475     {
476         save(out, getEncoding());
477     }
478 
479     /***
480      * Save the configuration to the specified stream, using the specified
481      * encoding. If the encoding is null the default encoding is used.
482      *
483      * @param out the output stream
484      * @param encoding the encoding to use
485      * @throws ConfigurationException if an error occurs during the save operation
486      */
487     public void save(OutputStream out, String encoding) throws ConfigurationException
488     {
489         Writer writer = null;
490 
491         if (encoding != null)
492         {
493             try
494             {
495                 writer = new OutputStreamWriter(out, encoding);
496             }
497             catch (UnsupportedEncodingException e)
498             {
499                 throw new ConfigurationException(
500                         "The requested encoding is not supported, try the default encoding.", e);
501             }
502         }
503 
504         if (writer == null)
505         {
506             writer = new OutputStreamWriter(out);
507         }
508 
509         save(writer);
510     }
511 
512     /***
513      * Return the name of the file.
514      *
515      * @return the file name
516      */
517     public String getFileName()
518     {
519         return fileName;
520     }
521 
522     /***
523      * Set the name of the file. The passed in file name should not contain a
524      * path. Use <code>{@link AbstractFileConfiguration#setPath(String)
525      * setPath()}</code> to set a full qualified file name.
526      *
527      * @param fileName the name of the file
528      */
529     public void setFileName(String fileName)
530     {
531         sourceURL = null;
532         this.fileName = fileName;
533     }
534 
535     /***
536      * Return the base path.
537      *
538      * @return the base path
539      */
540     public String getBasePath()
541     {
542         return basePath;
543     }
544 
545     /***
546      * Set the base path. Relative configurations are loaded from this path. The
547      * base path can be either a path to a directory or a URL.
548      *
549      * @param basePath the base path.
550      */
551     public void setBasePath(String basePath)
552     {
553         sourceURL = null;
554         this.basePath = basePath;
555     }
556 
557     /***
558      * Return the file where the configuration is stored. If the base path is a
559      * URL with a protocol different than &quot;file&quot;, the return value
560      * will not point to a valid file object.
561      *
562      * @return the file where the configuration is stored; this can be <b>null</b>
563      */
564     public File getFile()
565     {
566         if (getFileName() == null)
567         {
568             return null;
569         }
570         else
571         {
572             if (sourceURL != null)
573             {
574                 return ConfigurationUtils.fileFromURL(sourceURL);
575             }
576             else
577             {
578                 return ConfigurationUtils.getFile(getBasePath(), getFileName());
579             }
580         }
581     }
582 
583     /***
584      * Set the file where the configuration is stored. The passed in file is
585      * made absolute if it is not yet. Then the file's path component becomes
586      * the base path and its name component becomes the file name.
587      *
588      * @param file the file where the configuration is stored
589      */
590     public void setFile(File file)
591     {
592         sourceURL = null;
593         setFileName(file.getName());
594         setBasePath((file.getParentFile() != null) ? file.getParentFile()
595                 .getAbsolutePath() : null);
596     }
597 
598     /***
599      * Returns the full path to the file this configuration is based on. The
600      * return value is valid only if this configuration is based on a file on
601      * the local disk.
602      *
603      * @return the full path to the configuration file
604      */
605     public String getPath()
606     {
607         return getFile().getAbsolutePath();
608     }
609 
610     /***
611      * Sets the location of this configuration as a full path name. The passed
612      * in path should represent a valid file name.
613      *
614      * @param path the full path name of the configuration file
615      */
616     public void setPath(String path)
617     {
618         setFile(new File(path));
619     }
620 
621     /***
622      * Return the URL where the configuration is stored.
623      *
624      * @return the configuration's location as URL
625      */
626     public URL getURL()
627     {
628         return (sourceURL != null) ? sourceURL
629                 : ConfigurationUtils.locate(getBasePath(), getFileName());
630     }
631 
632     /***
633      * Set the location of this configuration as a URL. For loading this can be
634      * an arbitrary URL with a supported protocol. If the configuration is to
635      * be saved, too, a URL with the &quot;file&quot; protocol should be
636      * provided.
637      *
638      * @param url the location of this configuration as URL
639      */
640     public void setURL(URL url)
641     {
642         setBasePath(ConfigurationUtils.getBasePath(url));
643         setFileName(ConfigurationUtils.getFileName(url));
644         sourceURL = url;
645     }
646 
647     public void setAutoSave(boolean autoSave)
648     {
649         this.autoSave = autoSave;
650     }
651 
652     public boolean isAutoSave()
653     {
654         return autoSave;
655     }
656 
657     /***
658      * Save the configuration if the automatic persistence is enabled
659      * and if a file is specified.
660      */
661     protected void possiblySave()
662     {
663         if (autoSave && fileName != null)
664         {
665             try
666             {
667                 save();
668             }
669             catch (ConfigurationException e)
670             {
671                 throw new ConfigurationRuntimeException("Failed to auto-save", e);
672             }
673         }
674     }
675 
676     protected void addPropertyDirect(String key, Object obj)
677     {
678         super.addPropertyDirect(key, obj);
679         possiblySave();
680     }
681 
682     public void clearProperty(String key)
683     {
684         super.clearProperty(key);
685         possiblySave();
686     }
687 
688     public ReloadingStrategy getReloadingStrategy()
689     {
690         return strategy;
691     }
692 
693     public void setReloadingStrategy(ReloadingStrategy strategy)
694     {
695         this.strategy = strategy;
696         strategy.setConfiguration(this);
697         strategy.init();
698     }
699 
700     public void reload()
701     {
702         synchronized (reloadLock)
703         {
704             if (noReload == 0)
705             {
706                 try
707                 {
708                     enterNoReload(); // avoid reentrant calls
709 
710                     if (strategy.reloadingRequired())
711                     {
712                         clear();
713                         load();
714 
715                         // notify the strategy
716                         strategy.reloadingPerformed();
717                     }
718                 }
719                 catch (Exception e)
720                 {
721                     e.printStackTrace();
722                     // todo rollback the changes if the file can't be reloaded
723                 }
724                 finally
725                 {
726                     exitNoReload();
727                 }
728             }
729         }
730     }
731 
732     /***
733      * Enters the &quot;No reloading mode&quot;. As long as this mode is active
734      * no reloading will be performed. This is necessary for some
735      * implementations of <code>save()</code> in derived classes, which may
736      * cause a reload while accessing the properties to save. This may cause the
737      * whole configuration to be erased. To avoid this, this method can be
738      * called first. After a call to this method there always must be a
739      * corresponding call of <code>{@link #exitNoReload()}</code> later! (If
740      * necessary, <code>finally</code> blocks must be used to ensure this.
741      */
742     protected void enterNoReload()
743     {
744         synchronized (reloadLock)
745         {
746             noReload++;
747         }
748     }
749 
750     /***
751      * Leaves the &quot;No reloading mode&quot;.
752      *
753      * @see #enterNoReload()
754      */
755     protected void exitNoReload()
756     {
757         synchronized (reloadLock)
758         {
759             if (noReload > 0) // paranoia check
760             {
761                 noReload--;
762             }
763         }
764     }
765 
766     public Object getProperty(String key)
767     {
768         reload();
769         return super.getProperty(key);
770     }
771 
772     public boolean isEmpty()
773     {
774         reload();
775         return super.isEmpty();
776     }
777 
778     public boolean containsKey(String key)
779     {
780         reload();
781         return super.containsKey(key);
782     }
783 
784     public Iterator getKeys()
785     {
786         reload();
787         return super.getKeys();
788     }
789 
790     /***
791      * Create the path to the specified file.
792      *
793      * @param file the target file
794      */
795     private void createPath(File file)
796     {
797         if (file != null)
798         {
799             // create the path to the file if the file doesn't exist
800             if (!file.exists())
801             {
802                 File parent = file.getParentFile();
803                 if (parent != null && !parent.exists())
804                 {
805                     parent.mkdirs();
806                 }
807             }
808         }
809     }
810 
811     public String getEncoding()
812     {
813         return encoding;
814     }
815 
816     public void setEncoding(String encoding)
817     {
818         this.encoding = encoding;
819     }
820 }