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