1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.configuration;
19
20 import java.io.File;
21 import java.io.FileOutputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.InputStreamReader;
25 import java.io.OutputStream;
26 import java.io.OutputStreamWriter;
27 import java.io.Reader;
28 import java.io.UnsupportedEncodingException;
29 import java.io.Writer;
30 import java.net.HttpURLConnection;
31 import java.net.MalformedURLException;
32 import java.net.URL;
33 import java.net.URLConnection;
34 import java.util.Iterator;
35
36 import org.apache.commons.configuration.reloading.InvariantReloadingStrategy;
37 import org.apache.commons.configuration.reloading.ReloadingStrategy;
38 import org.apache.commons.lang.StringUtils;
39 import org.apache.commons.logging.LogFactory;
40
41 /***
42 * <p>Partial implementation of the <code>FileConfiguration</code> interface.
43 * Developpers of file based configuration may want to extend this class,
44 * the two methods left to implement are <code>{@link FileConfiguration#load(Reader)}</code>
45 * and <code>{@link FileConfiguration#save(Writer)}</code>.</p>
46 * <p>This base class already implements a couple of ways to specify the location
47 * of the file this configuration is based on. The following possibilities
48 * exist:
49 * <ul><li>URLs: With the method <code>setURL()</code> a full URL to the
50 * configuration source can be specified. This is the most flexible way. Note
51 * that the <code>save()</code> methods support only <em>file:</em> URLs.</li>
52 * <li>Files: The <code>setFile()</code> method allows to specify the
53 * configuration source as a file. This can be either a relative or an
54 * absolute file. In the former case the file is resolved based on the current
55 * directory.</li>
56 * <li>As file paths in string form: With the <code>setPath()</code> method a
57 * full path to a configuration file can be provided as a string.</li>
58 * <li>Separated as base path and file name: This is the native form in which
59 * the location is stored. The base path is a string defining either a local
60 * directory or a URL. It can be set using the <code>setBasePath()</code>
61 * method. The file name, non surprisingly, defines the name of the configuration
62 * file.</li></ul></p>
63 * <p>Note that the <code>load()</code> methods do not wipe out the configuration's
64 * content before the new configuration file is loaded. Thus it is very easy to
65 * construct a union configuration by simply loading multiple configuration
66 * files, e.g.</p>
67 * <p><pre>
68 * config.load(configFile1);
69 * config.load(configFile2);
70 * </pre></p>
71 * <p>After executing this code fragment, the resulting configuration will
72 * contain both the properties of configFile1 and configFile2. On the other
73 * hand, if the current configuration file is to be reloaded, <code>clear()</code>
74 * should be called first. Otherwise the properties are doubled. This behavior
75 * is analogous to the behavior of the <code>load(InputStream)</code> method
76 * in <code>java.util.Properties</code>.</p>
77 *
78 * @author Emmanuel Bourg
79 * @version $Revision: 545904 $, $Date: 2007-06-10 18:10:17 +0200 (So, 10 Jun 2007) $
80 * @since 1.0-rc2
81 */
82 public abstract class AbstractFileConfiguration extends BaseConfiguration implements FileConfiguration
83 {
84 /*** Constant for the configuration reload event.*/
85 public static final int EVENT_RELOAD = 20;
86
87 /*** Stores the file name.*/
88 protected String fileName;
89
90 /*** Stores the base path.*/
91 protected String basePath;
92
93 /*** The auto save flag.*/
94 protected boolean autoSave;
95
96 /*** Holds a reference to the reloading strategy.*/
97 protected ReloadingStrategy strategy;
98
99 /*** A lock object for protecting reload operations.*/
100 private Object reloadLock = new Object();
101
102 /*** Stores the encoding of the configuration file.*/
103 private String encoding;
104
105 /*** Stores the URL from which the configuration file was loaded.*/
106 private URL sourceURL;
107
108 /*** A counter that prohibits reloading.*/
109 private int noReload;
110
111 /***
112 * Default constructor
113 *
114 * @since 1.1
115 */
116 public AbstractFileConfiguration()
117 {
118 initReloadingStrategy();
119 setLogger(LogFactory.getLog(getClass()));
120 addErrorLogListener();
121 }
122
123 /***
124 * Creates and loads the configuration from the specified file. The passed
125 * in string must be a valid file name, either absolute or relativ.
126 *
127 * @param fileName The name of the file to load.
128 *
129 * @throws ConfigurationException Error while loading the file
130 * @since 1.1
131 */
132 public AbstractFileConfiguration(String fileName) throws ConfigurationException
133 {
134 this();
135
136
137 setFileName(fileName);
138
139
140 load();
141 }
142
143 /***
144 * Creates and loads the configuration from the specified file.
145 *
146 * @param file The file to load.
147 * @throws ConfigurationException Error while loading the file
148 * @since 1.1
149 */
150 public AbstractFileConfiguration(File file) throws ConfigurationException
151 {
152 this();
153
154
155 setFile(file);
156
157
158 if (file.exists())
159 {
160 load();
161 }
162 }
163
164 /***
165 * Creates and loads the configuration from the specified URL.
166 *
167 * @param url The location of the file to load.
168 * @throws ConfigurationException Error while loading the file
169 * @since 1.1
170 */
171 public AbstractFileConfiguration(URL url) throws ConfigurationException
172 {
173 this();
174
175
176 setURL(url);
177
178
179 load();
180 }
181
182 /***
183 * Load the configuration from the underlying location.
184 *
185 * @throws ConfigurationException if loading of the configuration fails
186 */
187 public void load() throws ConfigurationException
188 {
189 if (sourceURL != null)
190 {
191 load(sourceURL);
192 }
193 else
194 {
195 load(getFileName());
196 }
197 }
198
199 /***
200 * Locate the specified file and load the configuration. This does not
201 * change the source of the configuration (i.e. the internally maintained file name).
202 * Use one of the setter methods for this purpose.
203 *
204 * @param fileName the name of the file to be loaded
205 * @throws ConfigurationException if an error occurs
206 */
207 public void load(String fileName) throws ConfigurationException
208 {
209 try
210 {
211 URL url = ConfigurationUtils.locate(basePath, fileName);
212
213 if (url == null)
214 {
215 throw new ConfigurationException("Cannot locate configuration source " + fileName);
216 }
217 load(url);
218 }
219 catch (ConfigurationException e)
220 {
221 throw e;
222 }
223 catch (Exception e)
224 {
225 throw new ConfigurationException(e.getMessage(), e);
226 }
227 }
228
229 /***
230 * Load the configuration from the specified file. This does not change
231 * the source of the configuration (i.e. the internally maintained file
232 * name). Use one of the setter methods for this purpose.
233 *
234 * @param file the file to load
235 * @throws ConfigurationException if an error occurs
236 */
237 public void load(File file) throws ConfigurationException
238 {
239 try
240 {
241 load(file.toURL());
242 }
243 catch (ConfigurationException e)
244 {
245 throw e;
246 }
247 catch (Exception e)
248 {
249 throw new ConfigurationException(e.getMessage(), e);
250 }
251 }
252
253 /***
254 * Load the configuration from the specified URL. This does not change the
255 * source of the configuration (i.e. the internally maintained file name).
256 * Use on of the setter methods for this purpose.
257 *
258 * @param url the URL of the file to be loaded
259 * @throws ConfigurationException if an error occurs
260 */
261 public void load(URL url) throws ConfigurationException
262 {
263 if (sourceURL == null)
264 {
265 if (StringUtils.isEmpty(getBasePath()))
266 {
267
268 setBasePath(url.toString());
269 }
270 sourceURL = url;
271 }
272
273
274 File file = ConfigurationUtils.fileFromURL(url);
275 if (file != null && file.isDirectory())
276 {
277 throw new ConfigurationException("Cannot load a configuration from a directory");
278 }
279
280 InputStream in = null;
281
282 try
283 {
284 in = url.openStream();
285 load(in);
286 }
287 catch (ConfigurationException e)
288 {
289 throw e;
290 }
291 catch (Exception e)
292 {
293 throw new ConfigurationException(e.getMessage(), e);
294 }
295 finally
296 {
297
298 try
299 {
300 if (in != null)
301 {
302 in.close();
303 }
304 }
305 catch (IOException e)
306 {
307 getLogger().warn("Could not close input stream", e);
308 }
309 }
310 }
311
312 /***
313 * Load the configuration from the specified stream, using the encoding
314 * returned by {@link #getEncoding()}.
315 *
316 * @param in the input stream
317 *
318 * @throws ConfigurationException if an error occurs during the load operation
319 */
320 public void load(InputStream in) throws ConfigurationException
321 {
322 load(in, getEncoding());
323 }
324
325 /***
326 * Load the configuration from the specified stream, using the specified
327 * encoding. If the encoding is null the default encoding is used.
328 *
329 * @param in the input stream
330 * @param encoding the encoding used. <code>null</code> to use the default encoding
331 *
332 * @throws ConfigurationException if an error occurs during the load operation
333 */
334 public void load(InputStream in, String encoding) throws ConfigurationException
335 {
336 Reader reader = null;
337
338 if (encoding != null)
339 {
340 try
341 {
342 reader = new InputStreamReader(in, encoding);
343 }
344 catch (UnsupportedEncodingException e)
345 {
346 throw new ConfigurationException(
347 "The requested encoding is not supported, try the default encoding.", e);
348 }
349 }
350
351 if (reader == null)
352 {
353 reader = new InputStreamReader(in);
354 }
355
356 load(reader);
357 }
358
359 /***
360 * Save the configuration. Before this method can be called a valid file
361 * name must have been set.
362 *
363 * @throws ConfigurationException if an error occurs or no file name has
364 * been set yet
365 */
366 public void save() throws ConfigurationException
367 {
368 if (getFileName() == null)
369 {
370 throw new ConfigurationException("No file name has been set!");
371 }
372
373 if (sourceURL != null)
374 {
375 save(sourceURL);
376 }
377 else
378 {
379 save(fileName);
380 }
381 strategy.init();
382 }
383
384 /***
385 * Save the configuration to the specified file. This doesn't change the
386 * source of the configuration, use setFileName() if you need it.
387 *
388 * @param fileName the file name
389 *
390 * @throws ConfigurationException if an error occurs during the save operation
391 */
392 public void save(String fileName) throws ConfigurationException
393 {
394 try
395 {
396 File file = ConfigurationUtils.getFile(basePath, fileName);
397 if (file == null)
398 {
399 throw new ConfigurationException("Invalid file name for save: " + fileName);
400 }
401 save(file);
402 }
403 catch (ConfigurationException e)
404 {
405 throw e;
406 }
407 catch (Exception e)
408 {
409 throw new ConfigurationException(e.getMessage(), e);
410 }
411 }
412
413 /***
414 * Save the configuration to the specified URL.
415 * This doesn't change the source of the configuration, use setURL()
416 * if you need it.
417 *
418 * @param url the URL
419 *
420 * @throws ConfigurationException if an error occurs during the save operation
421 */
422 public void save(URL url) throws ConfigurationException
423 {
424
425
426 File file = ConfigurationUtils.fileFromURL(url);
427 if (file != null)
428 {
429 save(file);
430 }
431 else
432 {
433
434 OutputStream out = null;
435 try
436 {
437 URLConnection connection = url.openConnection();
438 connection.setDoOutput(true);
439
440
441 if (connection instanceof HttpURLConnection)
442 {
443 HttpURLConnection conn = (HttpURLConnection) connection;
444 conn.setRequestMethod("PUT");
445 }
446
447 out = connection.getOutputStream();
448 save(out);
449
450
451 if (connection instanceof HttpURLConnection)
452 {
453 HttpURLConnection conn = (HttpURLConnection) connection;
454 if (conn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST)
455 {
456 throw new IOException("HTTP Error " + conn.getResponseCode() + " " + conn.getResponseMessage());
457 }
458 }
459 }
460 catch (IOException e)
461 {
462 throw new ConfigurationException("Could not save to URL " + url, e);
463 }
464 finally
465 {
466 closeSilent(out);
467 }
468 }
469 }
470
471 /***
472 * Save the configuration to the specified file. The file is created
473 * automatically if it doesn't exist. This doesn't change the source
474 * of the configuration, use {@link #setFile} if you need it.
475 *
476 * @param file the target file
477 *
478 * @throws ConfigurationException if an error occurs during the save operation
479 */
480 public void save(File file) throws ConfigurationException
481 {
482 OutputStream out = null;
483
484 try
485 {
486
487 createPath(file);
488 out = new FileOutputStream(file);
489 save(out);
490 }
491 catch (IOException e)
492 {
493 throw new ConfigurationException(e.getMessage(), e);
494 }
495 finally
496 {
497 closeSilent(out);
498 }
499 }
500
501 /***
502 * Save the configuration to the specified stream, using the encoding
503 * returned by {@link #getEncoding()}.
504 *
505 * @param out the output stream
506 *
507 * @throws ConfigurationException if an error occurs during the save operation
508 */
509 public void save(OutputStream out) throws ConfigurationException
510 {
511 save(out, getEncoding());
512 }
513
514 /***
515 * Save the configuration to the specified stream, using the specified
516 * encoding. If the encoding is null the default encoding is used.
517 *
518 * @param out the output stream
519 * @param encoding the encoding to use
520 * @throws ConfigurationException if an error occurs during the save operation
521 */
522 public void save(OutputStream out, String encoding) throws ConfigurationException
523 {
524 Writer writer = null;
525
526 if (encoding != null)
527 {
528 try
529 {
530 writer = new OutputStreamWriter(out, encoding);
531 }
532 catch (UnsupportedEncodingException e)
533 {
534 throw new ConfigurationException(
535 "The requested encoding is not supported, try the default encoding.", e);
536 }
537 }
538
539 if (writer == null)
540 {
541 writer = new OutputStreamWriter(out);
542 }
543
544 save(writer);
545 }
546
547 /***
548 * Return the name of the file.
549 *
550 * @return the file name
551 */
552 public String getFileName()
553 {
554 return fileName;
555 }
556
557 /***
558 * Set the name of the file. The passed in file name can contain a
559 * relative path.
560 * It must be used when referring files with relative paths from classpath.
561 * Use <code>{@link AbstractFileConfiguration#setPath(String)
562 * setPath()}</code> to set a full qualified file name.
563 *
564 * @param fileName the name of the file
565 */
566 public void setFileName(String fileName)
567 {
568 sourceURL = null;
569 this.fileName = fileName;
570 }
571
572 /***
573 * Return the base path.
574 *
575 * @return the base path
576 * @see FileConfiguration#getBasePath()
577 */
578 public String getBasePath()
579 {
580 return basePath;
581 }
582
583 /***
584 * Sets the base path. The base path is typically either a path to a
585 * directory or a URL. Together with the value passed to the
586 * <code>setFileName()</code> method it defines the location of the
587 * configuration file to be loaded. The strategies for locating the file are
588 * quite tolerant. For instance if the file name is already an absolute path
589 * or a fully defined URL, the base path will be ignored. The base path can
590 * also be a URL, in which case the file name is interpreted in this URL's
591 * context. Because the base path is used by some of the derived classes for
592 * resolving relative file names it should contain a meaningful value. If
593 * other methods are used for determining the location of the configuration
594 * file (e.g. <code>setFile()</code> or <code>setURL()</code>), the
595 * base path is automatically set.
596 *
597 * @param basePath the base path.
598 */
599 public void setBasePath(String basePath)
600 {
601 sourceURL = null;
602 this.basePath = basePath;
603 }
604
605 /***
606 * Return the file where the configuration is stored. If the base path is a
607 * URL with a protocol different than "file", or the configuration
608 * file is within a compressed archive, the return value
609 * will not point to a valid file object.
610 *
611 * @return the file where the configuration is stored; this can be <b>null</b>
612 */
613 public File getFile()
614 {
615 if (getFileName() == null && sourceURL == null)
616 {
617 return null;
618 }
619 else if (sourceURL != null)
620 {
621 return ConfigurationUtils.fileFromURL(sourceURL);
622 }
623 else
624 {
625 return ConfigurationUtils.getFile(getBasePath(), getFileName());
626 }
627 }
628
629 /***
630 * Set the file where the configuration is stored. The passed in file is
631 * made absolute if it is not yet. Then the file's path component becomes
632 * the base path and its name component becomes the file name.
633 *
634 * @param file the file where the configuration is stored
635 */
636 public void setFile(File file)
637 {
638 sourceURL = null;
639 setFileName(file.getName());
640 setBasePath((file.getParentFile() != null) ? file.getParentFile()
641 .getAbsolutePath() : null);
642 }
643
644 /***
645 * Returns the full path to the file this configuration is based on. The
646 * return value is a valid File path only if this configuration is based on
647 * a file on the local disk.
648 * If the configuration was loaded from a packed archive the returned value
649 * is the string form of the URL from which the configuration was loaded.
650 *
651 * @return the full path to the configuration file
652 */
653 public String getPath()
654 {
655 String path = null;
656 File file = getFile();
657
658 if (file != null)
659 {
660 path = file.getAbsolutePath();
661 }
662
663
664 if (path == null)
665 {
666 if (sourceURL != null)
667 {
668 path = sourceURL.getPath();
669 }
670 else
671 {
672 try
673 {
674 path = ConfigurationUtils.getURL(getBasePath(), getFileName()).getPath();
675 }
676 catch (MalformedURLException e)
677 {
678
679 ;
680 }
681 }
682 }
683
684 return path;
685 }
686
687 /***
688 * Sets the location of this configuration as a full or relative path name.
689 * The passed in path should represent a valid file name on the file system.
690 * It must not be used to specify relative paths for files that exist
691 * in classpath, either plain file system or compressed archive,
692 * because this method expands any relative path to an absolute one which
693 * may end in an invalid absolute path for classpath references.
694 *
695 * @param path the full path name of the configuration file
696 */
697 public void setPath(String path)
698 {
699 setFile(new File(path));
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(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 }
727
728 public void setAutoSave(boolean autoSave)
729 {
730 this.autoSave = autoSave;
731 }
732
733 public boolean isAutoSave()
734 {
735 return autoSave;
736 }
737
738 /***
739 * Save the configuration if the automatic persistence is enabled
740 * and if a file is specified.
741 */
742 protected void possiblySave()
743 {
744 if (autoSave && fileName != null)
745 {
746 try
747 {
748 save();
749 }
750 catch (ConfigurationException e)
751 {
752 throw new ConfigurationRuntimeException("Failed to auto-save", e);
753 }
754 }
755 }
756
757 /***
758 * Adds a new property to this configuration. This implementation checks if
759 * the auto save mode is enabled and saves the configuration if necessary.
760 *
761 * @param key the key of the new property
762 * @param value the value
763 */
764 public void addProperty(String key, Object value)
765 {
766 super.addProperty(key, value);
767 possiblySave();
768 }
769
770 /***
771 * Sets a new value for the specified property. This implementation checks
772 * if the auto save mode is enabled and saves the configuration if
773 * necessary.
774 *
775 * @param key the key of the affected property
776 * @param value the value
777 */
778 public void setProperty(String key, Object value)
779 {
780 super.setProperty(key, value);
781 possiblySave();
782 }
783
784 public void clearProperty(String key)
785 {
786 super.clearProperty(key);
787 possiblySave();
788 }
789
790 public ReloadingStrategy getReloadingStrategy()
791 {
792 return strategy;
793 }
794
795 public void setReloadingStrategy(ReloadingStrategy strategy)
796 {
797 this.strategy = strategy;
798 strategy.setConfiguration(this);
799 strategy.init();
800 }
801
802 /***
803 * Performs a reload operation if necessary. This method is called on each
804 * access of this configuration. It asks the associated reloading strategy
805 * whether a reload should be performed. If this is the case, the
806 * configuration is cleared and loaded again from its source. If this
807 * operation causes an exception, the registered error listeners will be
808 * notified. The error event passed to the listeners is of type
809 * <code>EVENT_RELOAD</code> and contains the exception that caused the
810 * event.
811 */
812 public void reload()
813 {
814 synchronized (reloadLock)
815 {
816 if (noReload == 0)
817 {
818 try
819 {
820 enterNoReload();
821
822 if (strategy.reloadingRequired())
823 {
824 if (getLogger().isInfoEnabled())
825 {
826 getLogger().info("Reloading configuration. URL is " + getURL());
827 }
828 fireEvent(EVENT_RELOAD, null, getURL(), true);
829 setDetailEvents(false);
830 boolean autoSaveBak = this.isAutoSave();
831 this.setAutoSave(false);
832 try
833 {
834 clear();
835 load();
836 }
837 finally
838 {
839 this.setAutoSave(autoSaveBak);
840 setDetailEvents(true);
841 }
842 fireEvent(EVENT_RELOAD, null, getURL(), false);
843
844
845 strategy.reloadingPerformed();
846 }
847 }
848 catch (Exception e)
849 {
850 fireError(EVENT_RELOAD, null, null, e);
851
852 }
853 finally
854 {
855 exitNoReload();
856 }
857 }
858 }
859 }
860
861 /***
862 * Enters the "No reloading mode". As long as this mode is active
863 * no reloading will be performed. This is necessary for some
864 * implementations of <code>save()</code> in derived classes, which may
865 * cause a reload while accessing the properties to save. This may cause the
866 * whole configuration to be erased. To avoid this, this method can be
867 * called first. After a call to this method there always must be a
868 * corresponding call of <code>{@link #exitNoReload()}</code> later! (If
869 * necessary, <code>finally</code> blocks must be used to ensure this.
870 */
871 protected void enterNoReload()
872 {
873 synchronized (reloadLock)
874 {
875 noReload++;
876 }
877 }
878
879 /***
880 * Leaves the "No reloading mode".
881 *
882 * @see #enterNoReload()
883 */
884 protected void exitNoReload()
885 {
886 synchronized (reloadLock)
887 {
888 if (noReload > 0)
889 {
890 noReload--;
891 }
892 }
893 }
894
895 /***
896 * Sends an event to all registered listeners. This implementation ensures
897 * that no reloads are performed while the listeners are invoked. So
898 * infinite loops can be avoided that can be caused by event listeners
899 * accessing the configuration's properties when they are invoked.
900 *
901 * @param type the event type
902 * @param propName the name of the property
903 * @param propValue the value of the property
904 * @param before the before update flag
905 */
906 protected void fireEvent(int type, String propName, Object propValue, boolean before)
907 {
908 enterNoReload();
909 try
910 {
911 super.fireEvent(type, propName, propValue, before);
912 }
913 finally
914 {
915 exitNoReload();
916 }
917 }
918
919 public Object getProperty(String key)
920 {
921 reload();
922 return super.getProperty(key);
923 }
924
925 public boolean isEmpty()
926 {
927 reload();
928 return super.isEmpty();
929 }
930
931 public boolean containsKey(String key)
932 {
933 reload();
934 return super.containsKey(key);
935 }
936
937 public Iterator getKeys()
938 {
939 reload();
940 return super.getKeys();
941 }
942
943 /***
944 * Create the path to the specified file.
945 *
946 * @param file the target file
947 */
948 private void createPath(File file)
949 {
950 if (file != null)
951 {
952
953 if (!file.exists())
954 {
955 File parent = file.getParentFile();
956 if (parent != null && !parent.exists())
957 {
958 parent.mkdirs();
959 }
960 }
961 }
962 }
963
964 public String getEncoding()
965 {
966 return encoding;
967 }
968
969 public void setEncoding(String encoding)
970 {
971 this.encoding = encoding;
972 }
973
974 /***
975 * Creates a copy of this configuration. The new configuration object will
976 * contain the same properties as the original, but it will lose any
977 * connection to a source file (if one exists); this includes setting the
978 * source URL, base path, and file name to <b>null</b>. This is done to
979 * avoid race conditions if both the original and the copy are modified and
980 * then saved.
981 *
982 * @return the copy
983 * @since 1.3
984 */
985 public Object clone()
986 {
987 AbstractFileConfiguration copy = (AbstractFileConfiguration) super.clone();
988 copy.setBasePath(null);
989 copy.setFileName(null);
990 copy.initReloadingStrategy();
991 return copy;
992 }
993
994 /***
995 * Helper method for initializing the reloading strategy.
996 */
997 private void initReloadingStrategy()
998 {
999 setReloadingStrategy(new InvariantReloadingStrategy());
1000 }
1001
1002 /***
1003 * A helper method for closing an output stream. Occurring exceptions will
1004 * be ignored.
1005 *
1006 * @param out the output stream to be closed (may be <b>null</b>)
1007 * @since 1.5
1008 */
1009 private void closeSilent(OutputStream out)
1010 {
1011 try
1012 {
1013 if (out != null)
1014 {
1015 out.close();
1016 }
1017 }
1018 catch (IOException e)
1019 {
1020 getLogger().warn("Could not close output stream", e);
1021 }
1022 }
1023 }