001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.configuration;
019
020 import java.io.File;
021 import java.io.InputStream;
022 import java.io.OutputStream;
023 import java.io.Reader;
024 import java.io.Writer;
025 import java.net.URL;
026 import java.util.Collection;
027 import java.util.Iterator;
028 import java.util.List;
029
030 import org.apache.commons.configuration.event.ConfigurationErrorEvent;
031 import org.apache.commons.configuration.event.ConfigurationErrorListener;
032 import org.apache.commons.configuration.event.ConfigurationEvent;
033 import org.apache.commons.configuration.event.ConfigurationListener;
034 import org.apache.commons.configuration.reloading.Reloadable;
035 import org.apache.commons.configuration.reloading.ReloadingStrategy;
036
037 /**
038 * <p>Base class for implementing file based hierarchical configurations.</p>
039 * <p>This class serves an analogous purpose as the
040 * <code>{@link AbstractFileConfiguration}</code> class for non hierarchical
041 * configurations. It behaves in exactly the same way, so please refer to the
042 * documentation of <code>AbstractFileConfiguration</code> for further details.</p>
043 *
044 * @since 1.2
045 *
046 * @author Emmanuel Bourg
047 * @version $Id: AbstractHierarchicalFileConfiguration.java 1158114 2011-08-16 06:04:18Z oheger $
048 */
049 public abstract class AbstractHierarchicalFileConfiguration
050 extends HierarchicalConfiguration
051 implements FileConfiguration, ConfigurationListener, ConfigurationErrorListener, FileSystemBased,
052 Reloadable
053 {
054 /** Stores the delegate used for implementing functionality related to the
055 * <code>FileConfiguration</code> interface.
056 */
057 private FileConfigurationDelegate delegate;
058
059 /**
060 * Creates a new instance of
061 * <code>AbstractHierarchicalFileConfiguration</code>.
062 */
063 protected AbstractHierarchicalFileConfiguration()
064 {
065 initialize();
066 }
067
068 /**
069 * Creates a new instance of
070 * <code>AbstractHierarchicalFileConfiguration</code> and copies the
071 * content of the specified configuration into this object.
072 *
073 * @param c the configuration to copy
074 * @since 1.4
075 */
076 protected AbstractHierarchicalFileConfiguration(HierarchicalConfiguration c)
077 {
078 super(c);
079 initialize();
080 }
081
082 /**
083 * Creates and loads the configuration from the specified file.
084 *
085 * @param fileName The name of the plist file to load.
086 * @throws ConfigurationException Error while loading the file
087 */
088 public AbstractHierarchicalFileConfiguration(String fileName) throws ConfigurationException
089 {
090 this();
091 // store the file name
092 delegate.setFileName(fileName);
093
094 // load the file
095 load();
096 }
097
098 /**
099 * Creates and loads the configuration from the specified file.
100 *
101 * @param file The configuration file to load.
102 * @throws ConfigurationException Error while loading the file
103 */
104 public AbstractHierarchicalFileConfiguration(File file) throws ConfigurationException
105 {
106 this();
107 // set the file and update the url, the base path and the file name
108 setFile(file);
109
110 // load the file
111 if (file.exists())
112 {
113 load();
114 }
115 }
116
117 /**
118 * Creates and loads the configuration from the specified URL.
119 *
120 * @param url The location of the configuration file to load.
121 * @throws ConfigurationException Error while loading the file
122 */
123 public AbstractHierarchicalFileConfiguration(URL url) throws ConfigurationException
124 {
125 this();
126 // set the URL and update the base path and the file name
127 setURL(url);
128
129 // load the file
130 load();
131 }
132
133 /**
134 * Initializes this instance, mainly the internally used delegate object.
135 */
136 private void initialize()
137 {
138 delegate = createDelegate();
139 initDelegate(delegate);
140 }
141
142 protected void addPropertyDirect(String key, Object obj)
143 {
144 synchronized (delegate.getReloadLock())
145 {
146 super.addPropertyDirect(key, obj);
147 delegate.possiblySave();
148 }
149 }
150
151 public void clearProperty(String key)
152 {
153 synchronized (delegate.getReloadLock())
154 {
155 super.clearProperty(key);
156 delegate.possiblySave();
157 }
158 }
159
160 public void clearTree(String key)
161 {
162 synchronized (delegate.getReloadLock())
163 {
164 super.clearTree(key);
165 delegate.possiblySave();
166 }
167 }
168
169 public void setProperty(String key, Object value)
170 {
171 synchronized (delegate.getReloadLock())
172 {
173 super.setProperty(key, value);
174 delegate.possiblySave();
175 }
176 }
177
178 public void load() throws ConfigurationException
179 {
180 delegate.load();
181 }
182
183 public void load(String fileName) throws ConfigurationException
184 {
185 delegate.load(fileName);
186 }
187
188 public void load(File file) throws ConfigurationException
189 {
190 delegate.load(file);
191 }
192
193 public void load(URL url) throws ConfigurationException
194 {
195 delegate.load(url);
196 }
197
198 public void load(InputStream in) throws ConfigurationException
199 {
200 delegate.load(in);
201 }
202
203 public void load(InputStream in, String encoding) throws ConfigurationException
204 {
205 delegate.load(in, encoding);
206 }
207
208 public void save() throws ConfigurationException
209 {
210 delegate.save();
211 }
212
213 public void save(String fileName) throws ConfigurationException
214 {
215 delegate.save(fileName);
216 }
217
218 public void save(File file) throws ConfigurationException
219 {
220 delegate.save(file);
221 }
222
223 public void save(URL url) throws ConfigurationException
224 {
225 delegate.save(url);
226 }
227
228 public void save(OutputStream out) throws ConfigurationException
229 {
230 delegate.save(out);
231 }
232
233 public void save(OutputStream out, String encoding) throws ConfigurationException
234 {
235 delegate.save(out, encoding);
236 }
237
238 public String getFileName()
239 {
240 return delegate.getFileName();
241 }
242
243 public void setFileName(String fileName)
244 {
245 delegate.setFileName(fileName);
246 }
247
248 public String getBasePath()
249 {
250 return delegate.getBasePath();
251 }
252
253 public void setBasePath(String basePath)
254 {
255 delegate.setBasePath(basePath);
256 }
257
258 public File getFile()
259 {
260 return delegate.getFile();
261 }
262
263 public void setFile(File file)
264 {
265 delegate.setFile(file);
266 }
267
268 public URL getURL()
269 {
270 return delegate.getURL();
271 }
272
273 public void setURL(URL url)
274 {
275 delegate.setURL(url);
276 }
277
278 public void setAutoSave(boolean autoSave)
279 {
280 delegate.setAutoSave(autoSave);
281 }
282
283 public boolean isAutoSave()
284 {
285 return delegate.isAutoSave();
286 }
287
288 public ReloadingStrategy getReloadingStrategy()
289 {
290 return delegate.getReloadingStrategy();
291 }
292
293 public void setReloadingStrategy(ReloadingStrategy strategy)
294 {
295 delegate.setReloadingStrategy(strategy);
296 }
297
298 public void reload()
299 {
300 reload(false);
301 }
302
303 private boolean reload(boolean checkReload)
304 {
305 synchronized (delegate.getReloadLock())
306 {
307 setDetailEvents(false);
308 try
309 {
310 return delegate.reload(checkReload);
311 }
312 finally
313 {
314 setDetailEvents(true);
315 }
316 }
317 }
318
319 /**
320 * Reloads the associated configuration file. This method first clears the
321 * content of this configuration, then the associated configuration file is
322 * loaded again. Updates on this configuration which have not yet been saved
323 * are lost. Calling this method is like invoking <code>reload()</code>
324 * without checking the reloading strategy.
325 *
326 * @throws ConfigurationException if an error occurs
327 * @since 1.7
328 */
329 public void refresh() throws ConfigurationException
330 {
331 delegate.refresh();
332 }
333
334 public String getEncoding()
335 {
336 return delegate.getEncoding();
337 }
338
339 public void setEncoding(String encoding)
340 {
341 delegate.setEncoding(encoding);
342 }
343
344 public Object getReloadLock()
345 {
346 return delegate.getReloadLock();
347 }
348
349 public boolean containsKey(String key)
350 {
351 reload();
352 synchronized (delegate.getReloadLock())
353 {
354 return super.containsKey(key);
355 }
356 }
357
358 public Iterator getKeys()
359 {
360 reload();
361 synchronized (delegate.getReloadLock())
362 {
363 return super.getKeys();
364 }
365 }
366
367 public Iterator getKeys(String prefix)
368 {
369 reload();
370 synchronized (delegate.getReloadLock())
371 {
372 return super.getKeys(prefix);
373 }
374 }
375
376 public Object getProperty(String key)
377 {
378 if (reload(true))
379 {
380 // Avoid reloading again and getting the same error.
381 synchronized (delegate.getReloadLock())
382 {
383 return super.getProperty(key);
384 }
385 }
386 return null;
387 }
388
389 public boolean isEmpty()
390 {
391 reload();
392 synchronized (delegate.getReloadLock())
393 {
394 return super.isEmpty();
395 }
396 }
397
398 /**
399 * Directly adds sub nodes to this configuration. This implementation checks
400 * whether auto save is necessary after executing the operation.
401 *
402 * @param key the key where the nodes are to be added
403 * @param nodes a collection with the nodes to be added
404 * @since 1.5
405 */
406 public void addNodes(String key, Collection nodes)
407 {
408 synchronized (delegate.getReloadLock())
409 {
410 super.addNodes(key, nodes);
411 delegate.possiblySave();
412 }
413 }
414
415 /**
416 * Fetches a list of nodes, which are selected by the specified key. This
417 * implementation will perform a reload if necessary.
418 *
419 * @param key the key
420 * @return a list with the selected nodes
421 */
422 protected List fetchNodeList(String key)
423 {
424 reload();
425 synchronized (delegate.getReloadLock())
426 {
427 return super.fetchNodeList(key);
428 }
429 }
430
431 /**
432 * Reacts on changes of an associated subnode configuration. If the auto
433 * save mechanism is active, the configuration must be saved.
434 *
435 * @param event the event describing the change
436 * @since 1.5
437 */
438 protected void subnodeConfigurationChanged(ConfigurationEvent event)
439 {
440 delegate.possiblySave();
441 super.subnodeConfigurationChanged(event);
442 }
443
444 /**
445 * Creates the file configuration delegate, i.e. the object that implements
446 * functionality required by the <code>FileConfiguration</code> interface.
447 * This base implementation will return an instance of the
448 * <code>FileConfigurationDelegate</code> class. Derived classes may
449 * override it to create a different delegate object.
450 *
451 * @return the file configuration delegate
452 */
453 protected FileConfigurationDelegate createDelegate()
454 {
455 return new FileConfigurationDelegate();
456 }
457
458 /**
459 * Helper method for initializing the file configuration delegate.
460 *
461 * @param del the delegate
462 */
463 private void initDelegate(FileConfigurationDelegate del)
464 {
465 del.addConfigurationListener(this);
466 del.addErrorListener(this);
467 del.setLogger(getLogger());
468 }
469
470 /**
471 * Reacts on configuration change events triggered by the delegate. These
472 * events are passed to the registered configuration listeners.
473 *
474 * @param event the triggered event
475 * @since 1.3
476 */
477 public void configurationChanged(ConfigurationEvent event)
478 {
479 // deliver reload events to registered listeners
480 setDetailEvents(true);
481 try
482 {
483 fireEvent(event.getType(), event.getPropertyName(), event
484 .getPropertyValue(), event.isBeforeUpdate());
485 }
486 finally
487 {
488 setDetailEvents(false);
489 }
490 }
491
492 public void configurationError(ConfigurationErrorEvent event)
493 {
494 fireError(event.getType(), event.getPropertyName(), event.getPropertyValue(),
495 event.getCause());
496 }
497
498 /**
499 * Returns the file configuration delegate.
500 *
501 * @return the delegate
502 */
503 protected FileConfigurationDelegate getDelegate()
504 {
505 return delegate;
506 }
507
508 /**
509 * Allows to set the file configuration delegate.
510 * @param delegate the new delegate
511 */
512 protected void setDelegate(FileConfigurationDelegate delegate)
513 {
514 this.delegate = delegate;
515 }
516
517 /**
518 * Set the FileSystem to be used for this Configuration.
519 * @param fileSystem The FileSystem to use.
520 */
521 public void setFileSystem(FileSystem fileSystem)
522 {
523 delegate.setFileSystem(fileSystem);
524 }
525
526 /**
527 * Reset the FileSystem to the default;
528 */
529 public void resetFileSystem()
530 {
531 delegate.resetFileSystem();
532 }
533
534 /**
535 * Retrieve the FileSystem being used.
536 * @return The FileSystem.
537 */
538 public FileSystem getFileSystem()
539 {
540 return delegate.getFileSystem();
541 }
542
543 /**
544 * A special implementation of the <code>FileConfiguration</code> interface that is
545 * used internally to implement the <code>FileConfiguration</code> methods
546 * for hierarchical configurations.
547 */
548 protected class FileConfigurationDelegate extends AbstractFileConfiguration
549 {
550 public void load(Reader in) throws ConfigurationException
551 {
552 AbstractHierarchicalFileConfiguration.this.load(in);
553 }
554
555 public void save(Writer out) throws ConfigurationException
556 {
557 AbstractHierarchicalFileConfiguration.this.save(out);
558 }
559
560 public void clear()
561 {
562 AbstractHierarchicalFileConfiguration.this.clear();
563 }
564 }
565 }