Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
AbstractFileConfiguration |
|
| 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 "file", 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 "file" 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 "No reloading mode". 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 "No reloading mode". | |
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 | } |