    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    }