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    }