##### Coverage Report - org.apache.commons.configuration.AbstractFileConfiguration

Classes in this File | Line Coverage | Branch Coverage | Complexity
AbstractFileConfiguration |
90% | 238/265
97% | 36/37
| 2.4791666666666665;2,479

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