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 import org.apache.commons.configuration.tree.ConfigurationNode;
037
038 /**
039 * <p>Base class for implementing file based hierarchical configurations.</p>
040 * <p>This class serves an analogous purpose as the
041 * {@link AbstractFileConfiguration} class for non hierarchical
042 * configurations. It behaves in exactly the same way, so please refer to the
043 * documentation of {@code AbstractFileConfiguration} for further details.</p>
044 *
045 * @since 1.2
046 *
047 * @author Emmanuel Bourg
048 * @version $Id: AbstractHierarchicalFileConfiguration.java 1206575 2011-11-26 20:07:52Z oheger $
049 */
050 public abstract class AbstractHierarchicalFileConfiguration
051 extends HierarchicalConfiguration
052 implements FileConfiguration, ConfigurationListener, ConfigurationErrorListener, FileSystemBased,
053 Reloadable
054 {
055 /** Stores the delegate used for implementing functionality related to the
056 * {@code FileConfiguration} interface.
057 */
058 private FileConfigurationDelegate delegate;
059
060 /**
061 * Creates a new instance of {@code AbstractHierarchicalFileConfiguration}.
062 */
063 protected AbstractHierarchicalFileConfiguration()
064 {
065 initialize();
066 }
067
068 /**
069 * Creates a new instance of
070 * {@code AbstractHierarchicalFileConfiguration} 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 @Override
143 protected void addPropertyDirect(String key, Object obj)
144 {
145 synchronized (delegate.getReloadLock())
146 {
147 super.addPropertyDirect(key, obj);
148 delegate.possiblySave();
149 }
150 }
151
152 @Override
153 public void clearProperty(String key)
154 {
155 synchronized (delegate.getReloadLock())
156 {
157 super.clearProperty(key);
158 delegate.possiblySave();
159 }
160 }
161
162 @Override
163 public void clearTree(String key)
164 {
165 synchronized (delegate.getReloadLock())
166 {
167 super.clearTree(key);
168 delegate.possiblySave();
169 }
170 }
171
172 @Override
173 public void setProperty(String key, Object value)
174 {
175 synchronized (delegate.getReloadLock())
176 {
177 super.setProperty(key, value);
178 delegate.possiblySave();
179 }
180 }
181
182 public void load() throws ConfigurationException
183 {
184 delegate.load();
185 }
186
187 public void load(String fileName) throws ConfigurationException
188 {
189 delegate.load(fileName);
190 }
191
192 public void load(File file) throws ConfigurationException
193 {
194 delegate.load(file);
195 }
196
197 public void load(URL url) throws ConfigurationException
198 {
199 delegate.load(url);
200 }
201
202 public void load(InputStream in) throws ConfigurationException
203 {
204 delegate.load(in);
205 }
206
207 public void load(InputStream in, String encoding) throws ConfigurationException
208 {
209 delegate.load(in, encoding);
210 }
211
212 public void save() throws ConfigurationException
213 {
214 delegate.save();
215 }
216
217 public void save(String fileName) throws ConfigurationException
218 {
219 delegate.save(fileName);
220 }
221
222 public void save(File file) throws ConfigurationException
223 {
224 delegate.save(file);
225 }
226
227 public void save(URL url) throws ConfigurationException
228 {
229 delegate.save(url);
230 }
231
232 public void save(OutputStream out) throws ConfigurationException
233 {
234 delegate.save(out);
235 }
236
237 public void save(OutputStream out, String encoding) throws ConfigurationException
238 {
239 delegate.save(out, encoding);
240 }
241
242 public String getFileName()
243 {
244 return delegate.getFileName();
245 }
246
247 public void setFileName(String fileName)
248 {
249 delegate.setFileName(fileName);
250 }
251
252 public String getBasePath()
253 {
254 return delegate.getBasePath();
255 }
256
257 public void setBasePath(String basePath)
258 {
259 delegate.setBasePath(basePath);
260 }
261
262 public File getFile()
263 {
264 return delegate.getFile();
265 }
266
267 public void setFile(File file)
268 {
269 delegate.setFile(file);
270 }
271
272 public URL getURL()
273 {
274 return delegate.getURL();
275 }
276
277 public void setURL(URL url)
278 {
279 delegate.setURL(url);
280 }
281
282 public void setAutoSave(boolean autoSave)
283 {
284 delegate.setAutoSave(autoSave);
285 }
286
287 public boolean isAutoSave()
288 {
289 return delegate.isAutoSave();
290 }
291
292 public ReloadingStrategy getReloadingStrategy()
293 {
294 return delegate.getReloadingStrategy();
295 }
296
297 public void setReloadingStrategy(ReloadingStrategy strategy)
298 {
299 delegate.setReloadingStrategy(strategy);
300 }
301
302 public void reload()
303 {
304 reload(false);
305 }
306
307 private boolean reload(boolean checkReload)
308 {
309 synchronized (delegate.getReloadLock())
310 {
311 setDetailEvents(false);
312 try
313 {
314 return delegate.reload(checkReload);
315 }
316 finally
317 {
318 setDetailEvents(true);
319 }
320 }
321 }
322
323 /**
324 * Reloads the associated configuration file. This method first clears the
325 * content of this configuration, then the associated configuration file is
326 * loaded again. Updates on this configuration which have not yet been saved
327 * are lost. Calling this method is like invoking {@code reload()}
328 * without checking the reloading strategy.
329 *
330 * @throws ConfigurationException if an error occurs
331 * @since 1.7
332 */
333 public void refresh() throws ConfigurationException
334 {
335 delegate.refresh();
336 }
337
338 public String getEncoding()
339 {
340 return delegate.getEncoding();
341 }
342
343 public void setEncoding(String encoding)
344 {
345 delegate.setEncoding(encoding);
346 }
347
348 @Override
349 public Object getReloadLock()
350 {
351 return delegate.getReloadLock();
352 }
353
354 @Override
355 public boolean containsKey(String key)
356 {
357 reload();
358 synchronized (delegate.getReloadLock())
359 {
360 return super.containsKey(key);
361 }
362 }
363
364 @Override
365 public Iterator<String> getKeys()
366 {
367 reload();
368 synchronized (delegate.getReloadLock())
369 {
370 return super.getKeys();
371 }
372 }
373
374 @Override
375 public Iterator<String> getKeys(String prefix)
376 {
377 reload();
378 synchronized (delegate.getReloadLock())
379 {
380 return super.getKeys(prefix);
381 }
382 }
383
384 @Override
385 public Object getProperty(String key)
386 {
387 if (reload(true))
388 {
389 // Avoid reloading again and getting the same error.
390 synchronized (delegate.getReloadLock())
391 {
392 return super.getProperty(key);
393 }
394 }
395 return null;
396 }
397
398 @Override
399 public boolean isEmpty()
400 {
401 reload();
402 synchronized (delegate.getReloadLock())
403 {
404 return super.isEmpty();
405 }
406 }
407
408 /**
409 * Directly adds sub nodes to this configuration. This implementation checks
410 * whether auto save is necessary after executing the operation.
411 *
412 * @param key the key where the nodes are to be added
413 * @param nodes a collection with the nodes to be added
414 * @since 1.5
415 */
416 @Override
417 public void addNodes(String key, Collection<? extends ConfigurationNode> nodes)
418 {
419 synchronized (delegate.getReloadLock())
420 {
421 super.addNodes(key, nodes);
422 delegate.possiblySave();
423 }
424 }
425
426 /**
427 * Fetches a list of nodes, which are selected by the specified key. This
428 * implementation will perform a reload if necessary.
429 *
430 * @param key the key
431 * @return a list with the selected nodes
432 */
433 @Override
434 protected List<ConfigurationNode> fetchNodeList(String key)
435 {
436 reload();
437 synchronized (delegate.getReloadLock())
438 {
439 return super.fetchNodeList(key);
440 }
441 }
442
443 /**
444 * Reacts on changes of an associated subnode configuration. If the auto
445 * save mechanism is active, the configuration must be saved.
446 *
447 * @param event the event describing the change
448 * @since 1.5
449 */
450 @Override
451 protected void subnodeConfigurationChanged(ConfigurationEvent event)
452 {
453 delegate.possiblySave();
454 super.subnodeConfigurationChanged(event);
455 }
456
457 /**
458 * Creates the file configuration delegate, i.e. the object that implements
459 * functionality required by the {@code FileConfiguration} interface.
460 * This base implementation will return an instance of the
461 * {@code FileConfigurationDelegate} class. Derived classes may
462 * override it to create a different delegate object.
463 *
464 * @return the file configuration delegate
465 */
466 protected FileConfigurationDelegate createDelegate()
467 {
468 return new FileConfigurationDelegate();
469 }
470
471 /**
472 * Helper method for initializing the file configuration delegate.
473 *
474 * @param del the delegate
475 */
476 private void initDelegate(FileConfigurationDelegate del)
477 {
478 del.addConfigurationListener(this);
479 del.addErrorListener(this);
480 del.setLogger(getLogger());
481 }
482
483 /**
484 * Reacts on configuration change events triggered by the delegate. These
485 * events are passed to the registered configuration listeners.
486 *
487 * @param event the triggered event
488 * @since 1.3
489 */
490 public void configurationChanged(ConfigurationEvent event)
491 {
492 // deliver reload events to registered listeners
493 setDetailEvents(true);
494 try
495 {
496 fireEvent(event.getType(), event.getPropertyName(), event
497 .getPropertyValue(), event.isBeforeUpdate());
498 }
499 finally
500 {
501 setDetailEvents(false);
502 }
503 }
504
505 public void configurationError(ConfigurationErrorEvent event)
506 {
507 fireError(event.getType(), event.getPropertyName(), event.getPropertyValue(),
508 event.getCause());
509 }
510
511 /**
512 * Returns the file configuration delegate.
513 *
514 * @return the delegate
515 */
516 protected FileConfigurationDelegate getDelegate()
517 {
518 return delegate;
519 }
520
521 /**
522 * Allows to set the file configuration delegate.
523 * @param delegate the new delegate
524 */
525 protected void setDelegate(FileConfigurationDelegate delegate)
526 {
527 this.delegate = delegate;
528 }
529
530 /**
531 * Set the FileSystem to be used for this Configuration.
532 * @param fileSystem The FileSystem to use.
533 */
534 public void setFileSystem(FileSystem fileSystem)
535 {
536 delegate.setFileSystem(fileSystem);
537 }
538
539 /**
540 * Reset the FileSystem to the default;
541 */
542 public void resetFileSystem()
543 {
544 delegate.resetFileSystem();
545 }
546
547 /**
548 * Retrieve the FileSystem being used.
549 * @return The FileSystem.
550 */
551 public FileSystem getFileSystem()
552 {
553 return delegate.getFileSystem();
554 }
555
556 /**
557 * A special implementation of the {@code FileConfiguration} interface that is
558 * used internally to implement the {@code FileConfiguration} methods
559 * for hierarchical configurations.
560 */
561 protected class FileConfigurationDelegate extends AbstractFileConfiguration
562 {
563 public void load(Reader in) throws ConfigurationException
564 {
565 AbstractHierarchicalFileConfiguration.this.load(in);
566 }
567
568 public void save(Writer out) throws ConfigurationException
569 {
570 AbstractHierarchicalFileConfiguration.this.save(out);
571 }
572
573 @Override
574 public void clear()
575 {
576 AbstractHierarchicalFileConfiguration.this.clear();
577 }
578 }
579 }