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