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    }