001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.configuration;
019
020 import java.io.File;
021 import java.io.IOException;
022 import java.io.InputStream;
023 import java.io.InputStreamReader;
024 import java.io.OutputStream;
025 import java.io.OutputStreamWriter;
026 import java.io.Reader;
027 import java.io.UnsupportedEncodingException;
028 import java.io.Writer;
029 import java.net.URL;
030 import java.util.Iterator;
031 import java.util.LinkedList;
032 import java.util.List;
033
034 import org.apache.commons.configuration.reloading.InvariantReloadingStrategy;
035 import org.apache.commons.configuration.reloading.ReloadingStrategy;
036 import org.apache.commons.lang.StringUtils;
037 import org.apache.commons.logging.LogFactory;
038
039 /**
040 * <p>Partial implementation of the <code>FileConfiguration</code> interface.
041 * Developers of file based configuration may want to extend this class,
042 * the two methods left to implement are <code>{@link FileConfiguration#load(Reader)}</code>
043 * and <code>{@link FileConfiguration#save(Writer)}</code>.</p>
044 * <p>This base class already implements a couple of ways to specify the location
045 * of the file this configuration is based on. The following possibilities
046 * exist:
047 * <ul><li>URLs: With the method <code>setURL()</code> a full URL to the
048 * configuration source can be specified. This is the most flexible way. Note
049 * that the <code>save()</code> methods support only <em>file:</em> URLs.</li>
050 * <li>Files: The <code>setFile()</code> method allows to specify the
051 * configuration source as a file. This can be either a relative or an
052 * absolute file. In the former case the file is resolved based on the current
053 * directory.</li>
054 * <li>As file paths in string form: With the <code>setPath()</code> method a
055 * full path to a configuration file can be provided as a string.</li>
056 * <li>Separated as base path and file name: This is the native form in which
057 * the location is stored. The base path is a string defining either a local
058 * directory or a URL. It can be set using the <code>setBasePath()</code>
059 * method. The file name, non surprisingly, defines the name of the configuration
060 * file.</li></ul></p>
061 * <p>Note that the <code>load()</code> methods do not wipe out the configuration's
062 * content before the new configuration file is loaded. Thus it is very easy to
063 * construct a union configuration by simply loading multiple configuration
064 * files, e.g.</p>
065 * <p><pre>
066 * config.load(configFile1);
067 * config.load(configFile2);
068 * </pre></p>
069 * <p>After executing this code fragment, the resulting configuration will
070 * contain both the properties of configFile1 and configFile2. On the other
071 * hand, if the current configuration file is to be reloaded, <code>clear()</code>
072 * should be called first. Otherwise the properties are doubled. This behavior
073 * is analogous to the behavior of the <code>load(InputStream)</code> method
074 * in <code>java.util.Properties</code>.</p>
075 *
076 * @author Emmanuel Bourg
077 * @version $Id: AbstractFileConfiguration.java 1158113 2011-08-16 05:59:54Z oheger $
078 * @since 1.0-rc2
079 */
080 public abstract class AbstractFileConfiguration
081 extends BaseConfiguration
082 implements FileConfiguration, FileSystemBased
083 {
084 /** Constant for the configuration reload event.*/
085 public static final int EVENT_RELOAD = 20;
086
087 /** Constant fro the configuration changed event. */
088 public static final int EVENT_CONFIG_CHANGED = 21;
089
090 /** The root of the file scheme */
091 private static final String FILE_SCHEME = "file:";
092
093 /** Stores the file name.*/
094 protected String fileName;
095
096 /** Stores the base path.*/
097 protected String basePath;
098
099 /** The auto save flag.*/
100 protected boolean autoSave;
101
102 /** Holds a reference to the reloading strategy.*/
103 protected ReloadingStrategy strategy;
104
105 /** A lock object for protecting reload operations.*/
106 protected Object reloadLock = new Lock("AbstractFileConfiguration");
107
108 /** Stores the encoding of the configuration file.*/
109 private String encoding;
110
111 /** Stores the URL from which the configuration file was loaded.*/
112 private URL sourceURL;
113
114 /** A counter that prohibits reloading.*/
115 private int noReload;
116
117 /** The FileSystem being used for this Configuration */
118 private FileSystem fileSystem = FileSystem.getDefaultFileSystem();
119
120 /**
121 * Default constructor
122 *
123 * @since 1.1
124 */
125 public AbstractFileConfiguration()
126 {
127 initReloadingStrategy();
128 setLogger(LogFactory.getLog(getClass()));
129 addErrorLogListener();
130 }
131
132 /**
133 * Creates and loads the configuration from the specified file. The passed
134 * in string must be a valid file name, either absolute or relativ.
135 *
136 * @param fileName The name of the file to load.
137 *
138 * @throws ConfigurationException Error while loading the file
139 * @since 1.1
140 */
141 public AbstractFileConfiguration(String fileName) throws ConfigurationException
142 {
143 this();
144
145 // store the file name
146 setFileName(fileName);
147
148 // load the file
149 load();
150 }
151
152 /**
153 * Creates and loads the configuration from the specified file.
154 *
155 * @param file The file to load.
156 * @throws ConfigurationException Error while loading the file
157 * @since 1.1
158 */
159 public AbstractFileConfiguration(File file) throws ConfigurationException
160 {
161 this();
162
163 // set the file and update the url, the base path and the file name
164 setFile(file);
165
166 // load the file
167 if (file.exists())
168 {
169 load();
170 }
171 }
172
173 /**
174 * Creates and loads the configuration from the specified URL.
175 *
176 * @param url The location of the file to load.
177 * @throws ConfigurationException Error while loading the file
178 * @since 1.1
179 */
180 public AbstractFileConfiguration(URL url) throws ConfigurationException
181 {
182 this();
183
184 // set the URL and update the base path and the file name
185 setURL(url);
186
187 // load the file
188 load();
189 }
190
191 public void setFileSystem(FileSystem fileSystem)
192 {
193 if (fileSystem == null)
194 {
195 throw new NullPointerException("A valid FileSystem must be specified");
196 }
197 this.fileSystem = fileSystem;
198 }
199
200 public void resetFileSystem()
201 {
202 this.fileSystem = FileSystem.getDefaultFileSystem();
203 }
204
205 public FileSystem getFileSystem()
206 {
207 return this.fileSystem;
208 }
209
210 public Object getReloadLock()
211 {
212 return reloadLock;
213 }
214
215
216 /**
217 * Load the configuration from the underlying location.
218 *
219 * @throws ConfigurationException if loading of the configuration fails
220 */
221 public void load() throws ConfigurationException
222 {
223 if (sourceURL != null)
224 {
225 load(sourceURL);
226 }
227 else
228 {
229 load(getFileName());
230 }
231 }
232
233 /**
234 * Locate the specified file and load the configuration. This does not
235 * change the source of the configuration (i.e. the internally maintained file name).
236 * Use one of the setter methods for this purpose.
237 *
238 * @param fileName the name of the file to be loaded
239 * @throws ConfigurationException if an error occurs
240 */
241 public void load(String fileName) throws ConfigurationException
242 {
243 try
244 {
245 URL url = ConfigurationUtils.locate(this.fileSystem, basePath, fileName);
246
247 if (url == null)
248 {
249 throw new ConfigurationException("Cannot locate configuration source " + fileName);
250 }
251 load(url);
252 }
253 catch (ConfigurationException e)
254 {
255 throw e;
256 }
257 catch (Exception e)
258 {
259 throw new ConfigurationException("Unable to load the configuration file " + fileName, e);
260 }
261 }
262
263 /**
264 * Load the configuration from the specified file. This does not change
265 * the source of the configuration (i.e. the internally maintained file
266 * name). Use one of the setter methods for this purpose.
267 *
268 * @param file the file to load
269 * @throws ConfigurationException if an error occurs
270 */
271 public void load(File file) throws ConfigurationException
272 {
273 try
274 {
275 load(ConfigurationUtils.toURL(file));
276 }
277 catch (ConfigurationException e)
278 {
279 throw e;
280 }
281 catch (Exception e)
282 {
283 throw new ConfigurationException("Unable to load the configuration file " + file, e);
284 }
285 }
286
287 /**
288 * Load the configuration from the specified URL. This does not change the
289 * source of the configuration (i.e. the internally maintained file name).
290 * Use on of the setter methods for this purpose.
291 *
292 * @param url the URL of the file to be loaded
293 * @throws ConfigurationException if an error occurs
294 */
295 public void load(URL url) throws ConfigurationException
296 {
297 if (sourceURL == null)
298 {
299 if (StringUtils.isEmpty(getBasePath()))
300 {
301 // ensure that we have a valid base path
302 setBasePath(url.toString());
303 }
304 sourceURL = url;
305 }
306
307 InputStream in = null;
308
309 try
310 {
311 in = fileSystem.getInputStream(url);
312 load(in);
313 }
314 catch (ConfigurationException e)
315 {
316 throw e;
317 }
318 catch (Exception e)
319 {
320 throw new ConfigurationException("Unable to load the configuration from the URL " + url, e);
321 }
322 finally
323 {
324 // close the input stream
325 try
326 {
327 if (in != null)
328 {
329 in.close();
330 }
331 }
332 catch (IOException e)
333 {
334 getLogger().warn("Could not close input stream", e);
335 }
336 }
337 }
338
339 /**
340 * Load the configuration from the specified stream, using the encoding
341 * returned by {@link #getEncoding()}.
342 *
343 * @param in the input stream
344 *
345 * @throws ConfigurationException if an error occurs during the load operation
346 */
347 public void load(InputStream in) throws ConfigurationException
348 {
349 load(in, getEncoding());
350 }
351
352 /**
353 * Load the configuration from the specified stream, using the specified
354 * encoding. If the encoding is null the default encoding is used.
355 *
356 * @param in the input stream
357 * @param encoding the encoding used. <code>null</code> to use the default encoding
358 *
359 * @throws ConfigurationException if an error occurs during the load operation
360 */
361 public void load(InputStream in, String encoding) throws ConfigurationException
362 {
363 Reader reader = null;
364
365 if (encoding != null)
366 {
367 try
368 {
369 reader = new InputStreamReader(in, encoding);
370 }
371 catch (UnsupportedEncodingException e)
372 {
373 throw new ConfigurationException(
374 "The requested encoding is not supported, try the default encoding.", e);
375 }
376 }
377
378 if (reader == null)
379 {
380 reader = new InputStreamReader(in);
381 }
382
383 load(reader);
384 }
385
386 /**
387 * Save the configuration. Before this method can be called a valid file
388 * name must have been set.
389 *
390 * @throws ConfigurationException if an error occurs or no file name has
391 * been set yet
392 */
393 public void save() throws ConfigurationException
394 {
395 if (getFileName() == null)
396 {
397 throw new ConfigurationException("No file name has been set!");
398 }
399
400 if (sourceURL != null)
401 {
402 save(sourceURL);
403 }
404 else
405 {
406 save(fileName);
407 }
408 strategy.init();
409 }
410
411 /**
412 * Save the configuration to the specified file. This doesn't change the
413 * source of the configuration, use setFileName() if you need it.
414 *
415 * @param fileName the file name
416 *
417 * @throws ConfigurationException if an error occurs during the save operation
418 */
419 public void save(String fileName) throws ConfigurationException
420 {
421 try
422 {
423 URL url = this.fileSystem.getURL(basePath, fileName);
424
425 if (url == null)
426 {
427 throw new ConfigurationException("Cannot locate configuration source " + fileName);
428 }
429 save(url);
430 /*File file = ConfigurationUtils.getFile(basePath, fileName);
431 if (file == null)
432 {
433 throw new ConfigurationException("Invalid file name for save: " + fileName);
434 }
435 save(file); */
436 }
437 catch (ConfigurationException e)
438 {
439 throw e;
440 }
441 catch (Exception e)
442 {
443 throw new ConfigurationException("Unable to save the configuration to the file " + fileName, e);
444 }
445 }
446
447 /**
448 * Save the configuration to the specified URL.
449 * This doesn't change the source of the configuration, use setURL()
450 * if you need it.
451 *
452 * @param url the URL
453 *
454 * @throws ConfigurationException if an error occurs during the save operation
455 */
456 public void save(URL url) throws ConfigurationException
457 {
458 OutputStream out = null;
459 try
460 {
461 out = fileSystem.getOutputStream(url);
462 save(out);
463 if (out instanceof VerifiableOutputStream)
464 {
465 ((VerifiableOutputStream) out).verify();
466 }
467 }
468 catch (IOException e)
469 {
470 throw new ConfigurationException("Could not save to URL " + url, e);
471 }
472 finally
473 {
474 closeSilent(out);
475 }
476 }
477
478 /**
479 * Save the configuration to the specified file. The file is created
480 * automatically if it doesn't exist. This doesn't change the source
481 * of the configuration, use {@link #setFile} if you need it.
482 *
483 * @param file the target file
484 *
485 * @throws ConfigurationException if an error occurs during the save operation
486 */
487 public void save(File file) throws ConfigurationException
488 {
489 OutputStream out = null;
490
491 try
492 {
493 out = fileSystem.getOutputStream(file);
494 save(out);
495 }
496 finally
497 {
498 closeSilent(out);
499 }
500 }
501
502 /**
503 * Save the configuration to the specified stream, using the encoding
504 * returned by {@link #getEncoding()}.
505 *
506 * @param out the output stream
507 *
508 * @throws ConfigurationException if an error occurs during the save operation
509 */
510 public void save(OutputStream out) throws ConfigurationException
511 {
512 save(out, getEncoding());
513 }
514
515 /**
516 * Save the configuration to the specified stream, using the specified
517 * encoding. If the encoding is null the default encoding is used.
518 *
519 * @param out the output stream
520 * @param encoding the encoding to use
521 * @throws ConfigurationException if an error occurs during the save operation
522 */
523 public void save(OutputStream out, String encoding) throws ConfigurationException
524 {
525 Writer writer = null;
526
527 if (encoding != null)
528 {
529 try
530 {
531 writer = new OutputStreamWriter(out, encoding);
532 }
533 catch (UnsupportedEncodingException e)
534 {
535 throw new ConfigurationException(
536 "The requested encoding is not supported, try the default encoding.", e);
537 }
538 }
539
540 if (writer == null)
541 {
542 writer = new OutputStreamWriter(out);
543 }
544
545 save(writer);
546 }
547
548 /**
549 * Return the name of the file.
550 *
551 * @return the file name
552 */
553 public String getFileName()
554 {
555 return fileName;
556 }
557
558 /**
559 * Set the name of the file. The passed in file name can contain a
560 * relative path.
561 * It must be used when referring files with relative paths from classpath.
562 * Use <code>{@link AbstractFileConfiguration#setPath(String)
563 * setPath()}</code> to set a full qualified file name.
564 *
565 * @param fileName the name of the file
566 */
567 public void setFileName(String fileName)
568 {
569 if (fileName != null && fileName.startsWith(FILE_SCHEME) && !fileName.startsWith("file://"))
570 {
571 fileName = "file://" + fileName.substring(FILE_SCHEME.length());
572 }
573
574 sourceURL = null;
575 this.fileName = fileName;
576 getLogger().debug("FileName set to " + fileName);
577 }
578
579 /**
580 * Return the base path.
581 *
582 * @return the base path
583 * @see FileConfiguration#getBasePath()
584 */
585 public String getBasePath()
586 {
587 return basePath;
588 }
589
590 /**
591 * Sets the base path. The base path is typically either a path to a
592 * directory or a URL. Together with the value passed to the
593 * <code>setFileName()</code> method it defines the location of the
594 * configuration file to be loaded. The strategies for locating the file are
595 * quite tolerant. For instance if the file name is already an absolute path
596 * or a fully defined URL, the base path will be ignored. The base path can
597 * also be a URL, in which case the file name is interpreted in this URL's
598 * context. Because the base path is used by some of the derived classes for
599 * resolving relative file names it should contain a meaningful value. If
600 * other methods are used for determining the location of the configuration
601 * file (e.g. <code>setFile()</code> or <code>setURL()</code>), the
602 * base path is automatically set.
603 *
604 * @param basePath the base path.
605 */
606 public void setBasePath(String basePath)
607 {
608 if (basePath != null && basePath.startsWith(FILE_SCHEME) && !basePath.startsWith("file://"))
609 {
610 basePath = "file://" + basePath.substring(FILE_SCHEME.length());
611 }
612 sourceURL = null;
613 this.basePath = basePath;
614 getLogger().debug("Base path set to " + basePath);
615 }
616
617 /**
618 * Return the file where the configuration is stored. If the base path is a
619 * URL with a protocol different than "file", or the configuration
620 * file is within a compressed archive, the return value
621 * will not point to a valid file object.
622 *
623 * @return the file where the configuration is stored; this can be <b>null</b>
624 */
625 public File getFile()
626 {
627 if (getFileName() == null && sourceURL == null)
628 {
629 return null;
630 }
631 else if (sourceURL != null)
632 {
633 return ConfigurationUtils.fileFromURL(sourceURL);
634 }
635 else
636 {
637 return ConfigurationUtils.getFile(getBasePath(), getFileName());
638 }
639 }
640
641 /**
642 * Set the file where the configuration is stored. The passed in file is
643 * made absolute if it is not yet. Then the file's path component becomes
644 * the base path and its name component becomes the file name.
645 *
646 * @param file the file where the configuration is stored
647 */
648 public void setFile(File file)
649 {
650 sourceURL = null;
651 setFileName(file.getName());
652 setBasePath((file.getParentFile() != null) ? file.getParentFile()
653 .getAbsolutePath() : null);
654 }
655
656 /**
657 * Returns the full path to the file this configuration is based on. The
658 * return value is a valid File path only if this configuration is based on
659 * a file on the local disk.
660 * If the configuration was loaded from a packed archive the returned value
661 * is the string form of the URL from which the configuration was loaded.
662 *
663 * @return the full path to the configuration file
664 */
665 public String getPath()
666 {
667 return fileSystem.getPath(getFile(), sourceURL, getBasePath(), getFileName());
668 }
669
670 /**
671 * Sets the location of this configuration as a full or relative path name.
672 * The passed in path should represent a valid file name on the file system.
673 * It must not be used to specify relative paths for files that exist
674 * in classpath, either plain file system or compressed archive,
675 * because this method expands any relative path to an absolute one which
676 * may end in an invalid absolute path for classpath references.
677 *
678 * @param path the full path name of the configuration file
679 */
680 public void setPath(String path)
681 {
682 setFile(new File(path));
683 }
684
685 URL getSourceURL()
686 {
687 return sourceURL;
688 }
689
690 /**
691 * Return the URL where the configuration is stored.
692 *
693 * @return the configuration's location as URL
694 */
695 public URL getURL()
696 {
697 return (sourceURL != null) ? sourceURL
698 : ConfigurationUtils.locate(this.fileSystem, getBasePath(), getFileName());
699 }
700
701 /**
702 * Set the location of this configuration as a URL. For loading this can be
703 * an arbitrary URL with a supported protocol. If the configuration is to
704 * be saved, too, a URL with the "file" protocol should be
705 * provided.
706 *
707 * @param url the location of this configuration as URL
708 */
709 public void setURL(URL url)
710 {
711 setBasePath(ConfigurationUtils.getBasePath(url));
712 setFileName(ConfigurationUtils.getFileName(url));
713 sourceURL = url;
714 getLogger().debug("URL set to " + url);
715 }
716
717 public void setAutoSave(boolean autoSave)
718 {
719 this.autoSave = autoSave;
720 }
721
722 public boolean isAutoSave()
723 {
724 return autoSave;
725 }
726
727 /**
728 * Save the configuration if the automatic persistence is enabled
729 * and if a file is specified.
730 */
731 protected void possiblySave()
732 {
733 if (autoSave && fileName != null)
734 {
735 try
736 {
737 save();
738 }
739 catch (ConfigurationException e)
740 {
741 throw new ConfigurationRuntimeException("Failed to auto-save", e);
742 }
743 }
744 }
745
746 /**
747 * Adds a new property to this configuration. This implementation checks if
748 * the auto save mode is enabled and saves the configuration if necessary.
749 *
750 * @param key the key of the new property
751 * @param value the value
752 */
753 public void addProperty(String key, Object value)
754 {
755 synchronized (reloadLock)
756 {
757 super.addProperty(key, value);
758 possiblySave();
759 }
760 }
761
762 /**
763 * Sets a new value for the specified property. This implementation checks
764 * if the auto save mode is enabled and saves the configuration if
765 * necessary.
766 *
767 * @param key the key of the affected property
768 * @param value the value
769 */
770 public void setProperty(String key, Object value)
771 {
772 synchronized (reloadLock)
773 {
774 super.setProperty(key, value);
775 possiblySave();
776 }
777 }
778
779 public void clearProperty(String key)
780 {
781 synchronized (reloadLock)
782 {
783 super.clearProperty(key);
784 possiblySave();
785 }
786 }
787
788 public ReloadingStrategy getReloadingStrategy()
789 {
790 return strategy;
791 }
792
793 public void setReloadingStrategy(ReloadingStrategy strategy)
794 {
795 this.strategy = strategy;
796 strategy.setConfiguration(this);
797 strategy.init();
798 }
799
800 /**
801 * Performs a reload operation if necessary. This method is called on each
802 * access of this configuration. It asks the associated reloading strategy
803 * whether a reload should be performed. If this is the case, the
804 * configuration is cleared and loaded again from its source. If this
805 * operation causes an exception, the registered error listeners will be
806 * notified. The error event passed to the listeners is of type
807 * <code>EVENT_RELOAD</code> and contains the exception that caused the
808 * event.
809 */
810 public void reload()
811 {
812 reload(false);
813 }
814
815 public boolean reload(boolean checkReload)
816 {
817 synchronized (reloadLock)
818 {
819 if (noReload == 0)
820 {
821 try
822 {
823 enterNoReload(); // avoid reentrant calls
824
825 if (strategy.reloadingRequired())
826 {
827 if (getLogger().isInfoEnabled())
828 {
829 getLogger().info("Reloading configuration. URL is " + getURL());
830 }
831 refresh();
832
833 // notify the strategy
834 strategy.reloadingPerformed();
835 }
836 }
837 catch (Exception e)
838 {
839 fireError(EVENT_RELOAD, null, null, e);
840 // todo rollback the changes if the file can't be reloaded
841 if (checkReload)
842 {
843 return false;
844 }
845 }
846 finally
847 {
848 exitNoReload();
849 }
850 }
851 }
852 return true;
853 }
854
855 /**
856 * Reloads the associated configuration file. This method first clears the
857 * content of this configuration, then the associated configuration file is
858 * loaded again. Updates on this configuration which have not yet been saved
859 * are lost. Calling this method is like invoking <code>reload()</code>
860 * without checking the reloading strategy.
861 *
862 * @throws ConfigurationException if an error occurs
863 * @since 1.7
864 */
865 public void refresh() throws ConfigurationException
866 {
867 fireEvent(EVENT_RELOAD, null, getURL(), true);
868 setDetailEvents(false);
869 boolean autoSaveBak = this.isAutoSave(); // save the current state
870 this.setAutoSave(false); // deactivate autoSave to prevent information loss
871 try
872 {
873 clear();
874 load();
875 }
876 finally
877 {
878 this.setAutoSave(autoSaveBak); // set autoSave to previous value
879 setDetailEvents(true);
880 }
881 fireEvent(EVENT_RELOAD, null, getURL(), false);
882 }
883
884 /**
885 * Send notification that the configuration has changed.
886 */
887 public void configurationChanged()
888 {
889 fireEvent(EVENT_CONFIG_CHANGED, null, getURL(), true);
890 }
891
892 /**
893 * Enters the "No reloading mode". As long as this mode is active
894 * no reloading will be performed. This is necessary for some
895 * implementations of <code>save()</code> in derived classes, which may
896 * cause a reload while accessing the properties to save. This may cause the
897 * whole configuration to be erased. To avoid this, this method can be
898 * called first. After a call to this method there always must be a
899 * corresponding call of <code>{@link #exitNoReload()}</code> later! (If
900 * necessary, <code>finally</code> blocks must be used to ensure this.
901 */
902 protected void enterNoReload()
903 {
904 synchronized (reloadLock)
905 {
906 noReload++;
907 }
908 }
909
910 /**
911 * Leaves the "No reloading mode".
912 *
913 * @see #enterNoReload()
914 */
915 protected void exitNoReload()
916 {
917 synchronized (reloadLock)
918 {
919 if (noReload > 0) // paranoia check
920 {
921 noReload--;
922 }
923 }
924 }
925
926 /**
927 * Sends an event to all registered listeners. This implementation ensures
928 * that no reloads are performed while the listeners are invoked. So
929 * infinite loops can be avoided that can be caused by event listeners
930 * accessing the configuration's properties when they are invoked.
931 *
932 * @param type the event type
933 * @param propName the name of the property
934 * @param propValue the value of the property
935 * @param before the before update flag
936 */
937 protected void fireEvent(int type, String propName, Object propValue, boolean before)
938 {
939 enterNoReload();
940 try
941 {
942 super.fireEvent(type, propName, propValue, before);
943 }
944 finally
945 {
946 exitNoReload();
947 }
948 }
949
950 public Object getProperty(String key)
951 {
952 synchronized (reloadLock)
953 {
954 reload();
955 return super.getProperty(key);
956 }
957 }
958
959 public boolean isEmpty()
960 {
961 reload();
962 synchronized (reloadLock)
963 {
964 return super.isEmpty();
965 }
966 }
967
968 public boolean containsKey(String key)
969 {
970 reload();
971 synchronized (reloadLock)
972 {
973 return super.containsKey(key);
974 }
975 }
976
977 /**
978 * Returns an <code>Iterator</code> with the keys contained in this
979 * configuration. This implementation performs a reload if necessary before
980 * obtaining the keys. The <code>Iterator</code> returned by this method
981 * points to a snapshot taken when this method was called. Later changes at
982 * the set of keys (including those caused by a reload) won't be visible.
983 * This is because a reload can happen at any time during iteration, and it
984 * is impossible to determine how this reload affects the current iteration.
985 * When using the iterator a client has to be aware that changes of the
986 * configuration are possible at any time. For instance, if after a reload
987 * operation some keys are no longer present, the iterator will still return
988 * those keys because they were found when it was created.
989 *
990 * @return an <code>Iterator</code> with the keys of this configuration
991 */
992 public Iterator getKeys()
993 {
994 reload();
995 List keyList = new LinkedList();
996 enterNoReload();
997 try
998 {
999 for (Iterator it = super.getKeys(); it.hasNext();)
1000 {
1001 keyList.add(it.next());
1002 }
1003
1004 return keyList.iterator();
1005 }
1006 finally
1007 {
1008 exitNoReload();
1009 }
1010 }
1011
1012 public String getEncoding()
1013 {
1014 return encoding;
1015 }
1016
1017 public void setEncoding(String encoding)
1018 {
1019 this.encoding = encoding;
1020 }
1021
1022 /**
1023 * Creates a copy of this configuration. The new configuration object will
1024 * contain the same properties as the original, but it will lose any
1025 * connection to a source file (if one exists); this includes setting the
1026 * source URL, base path, and file name to <b>null</b>. This is done to
1027 * avoid race conditions if both the original and the copy are modified and
1028 * then saved.
1029 *
1030 * @return the copy
1031 * @since 1.3
1032 */
1033 public Object clone()
1034 {
1035 AbstractFileConfiguration copy = (AbstractFileConfiguration) super.clone();
1036 copy.setBasePath(null);
1037 copy.setFileName(null);
1038 copy.initReloadingStrategy();
1039 return copy;
1040 }
1041
1042 /**
1043 * Helper method for initializing the reloading strategy.
1044 */
1045 private void initReloadingStrategy()
1046 {
1047 setReloadingStrategy(new InvariantReloadingStrategy());
1048 }
1049
1050 /**
1051 * A helper method for closing an output stream. Occurring exceptions will
1052 * be ignored.
1053 *
1054 * @param out the output stream to be closed (may be <b>null</b>)
1055 * @since 1.5
1056 */
1057 protected void closeSilent(OutputStream out)
1058 {
1059 try
1060 {
1061 if (out != null)
1062 {
1063 out.close();
1064 }
1065 }
1066 catch (IOException e)
1067 {
1068 getLogger().warn("Could not close output stream", e);
1069 }
1070 }
1071 }