1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
127 setPath(fileName);
128
129
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
145 setFile(file);
146
147
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
166 setURL(url);
167
168
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
258 setBasePath(url.toString());
259 }
260 sourceURL = url;
261 }
262
263
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
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
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
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 "file", 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 "file" 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();
709
710 if (strategy.reloadingRequired())
711 {
712 clear();
713 load();
714
715
716 strategy.reloadingPerformed();
717 }
718 }
719 catch (Exception e)
720 {
721 e.printStackTrace();
722
723 }
724 finally
725 {
726 exitNoReload();
727 }
728 }
729 }
730 }
731
732 /***
733 * Enters the "No reloading mode". 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 "No reloading mode".
752 *
753 * @see #enterNoReload()
754 */
755 protected void exitNoReload()
756 {
757 synchronized (reloadLock)
758 {
759 if (noReload > 0)
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
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 }