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
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
114 setPath(fileName);
115
116
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
132 setFile(file);
133
134
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
153 setURL(url);
154
155
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
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
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
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 "file", 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 "file" 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
633 strategy.reloadingPerformed();
634 }
635 catch (Exception e)
636 {
637 e.printStackTrace();
638
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
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 }