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