    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.reloading;
    019
    020    import java.io.File;
    021    import java.net.MalformedURLException;
    022    import java.net.URL;
    023
    024    import org.apache.commons.configuration.ConfigurationUtils;
    025    import org.apache.commons.configuration.FileConfiguration;
    026    import org.apache.commons.logging.Log;
    027    import org.apache.commons.logging.LogFactory;
    028
    029    /**
    030     * <p>A reloading strategy that will reload the configuration every time its
    031     * underlying file is changed.</p>
    032     * <p>This reloading strategy does not actively monitor a configuration file,
    033     * but is triggered by its associated configuration whenever properties are
    034     * accessed. It then checks the configuration file's last modification date
    035     * and causes a reload if this has changed.</p>
    036     * <p>To avoid permanent disc access on successive property lookups a refresh
    037     * delay can be specified. This has the effect that the configuration file's
    038     * last modification date is only checked once in this delay period. The default
    039     * value for this refresh delay is 5 seconds.</p>
    040     * <p>This strategy only works with FileConfiguration instances.</p>
    041     *
    042     * @author Emmanuel Bourg
    043     * @version $Revision: 823891 $, $Date: 2009-10-10 19:17:44 +0200 (Sa, 10. Okt 2009) $
    044     * @since 1.1
    045     */
    046    public class FileChangedReloadingStrategy implements ReloadingStrategy
    047    {
    048        /** Constant for the jar URL protocol.*/
    049        private static final String JAR_PROTOCOL = "jar";
    050
    051        /** Constant for the default refresh delay.*/
    052        private static final int DEFAULT_REFRESH_DELAY = 5000;
    053
    054        /** Stores a reference to the configuration to be monitored.*/
    055        protected FileConfiguration configuration;
    056
    057        /** The last time the configuration file was modified. */
    058        protected long lastModified;
    059
    060        /** The last time the file was checked for changes. */
    061        protected long lastChecked;
    062
    063        /** The minimum delay in milliseconds between checks. */
    064        protected long refreshDelay = DEFAULT_REFRESH_DELAY;
    065
    066        /** A flag whether a reload is required.*/
    067        private boolean reloading;
    068
    069        /** The Log to use for diagnostic messages */
    070        private Log logger = LogFactory.getLog(FileChangedReloadingStrategy.class);
    071
    072        public void setConfiguration(FileConfiguration configuration)
    073        {
    074            this.configuration = configuration;
    075        }
    076
    077        public void init()
    078        {
    079            updateLastModified();
    080        }
    081
    082        public boolean reloadingRequired()
    083        {
    084            if (!reloading)
    085            {
    086                long now = System.currentTimeMillis();
    087
    088                if (now > lastChecked + refreshDelay)
    089                {
    090                    lastChecked = now;
    091                    if (hasChanged())
    092                    {
    093                        if (logger.isDebugEnabled())
    094                        {
    095                            logger.debug("File change detected: " + getName());
    096                        }
    097                        reloading = true;
    098                    }
    099                }
    100            }
    101
    102            return reloading;
    103        }
    104
    105        public void reloadingPerformed()
    106        {
    107            updateLastModified();
    108        }
    109
    110        /**
    111         * Return the minimal time in milliseconds between two reloadings.
    112         *
    113         * @return the refresh delay (in milliseconds)
    114         */
    115        public long getRefreshDelay()
    116        {
    117            return refreshDelay;
    118        }
    119
    120        /**
    121         * Set the minimal time between two reloadings.
    122         *
    123         * @param refreshDelay refresh delay in milliseconds
    124         */
    125        public void setRefreshDelay(long refreshDelay)
    126        {
    127            this.refreshDelay = refreshDelay;
    128        }
    129
    130        /**
    131         * Update the last modified time.
    132         */
    133        protected void updateLastModified()
    134        {
    135            File file = getFile();
    136            if (file != null)
    137            {
    138                lastModified = file.lastModified();
    139            }
    140            reloading = false;
    141        }
    142
    143        /**
    144         * Check if the configuration has changed since the last time it was loaded.
    145         *
    146         * @return a flag whether the configuration has changed
    147         */
    148        protected boolean hasChanged()
    149        {
    150            File file = getFile();
    151            if (file == null || !file.exists())
    152            {
    153                if (logger.isWarnEnabled() && lastModified != 0)
    154                {
    155                    logger.warn("File was deleted: " + getName(file));
    156                    lastModified = 0;
    157                }
    158                return false;
    159            }
    160
    161            return file.lastModified() > lastModified;
    162        }
    163
    164        /**
    165         * Returns the file that is monitored by this strategy. Note that the return
    166         * value can be <b>null </b> under some circumstances.
    167         *
    168         * @return the monitored file
    169         */
    170        protected File getFile()
    171        {
    172            return (configuration.getURL() != null) ? fileFromURL(configuration
    173                    .getURL()) : configuration.getFile();
    174        }
    175
    176        /**
    177         * Helper method for transforming a URL into a file object. This method
    178         * handles file: and jar: URLs.
    179         *
    180         * @param url the URL to be converted
    181         * @return the resulting file or <b>null </b>
    182         */
    183        private File fileFromURL(URL url)
    184        {
    185            if (JAR_PROTOCOL.equals(url.getProtocol()))
    186            {
    187                String path = url.getPath();
    188                try
    189                {
    190                    return ConfigurationUtils.fileFromURL(new URL(path.substring(0,
    191                            path.indexOf('!'))));
    192                }
    193                catch (MalformedURLException mex)
    194                {
    195                    return null;
    196                }
    197            }
    198            else
    199            {
    200                return ConfigurationUtils.fileFromURL(url);
    201            }
    202        }
    203
    204        private String getName()
    205        {
    206            return getName(getFile());
    207        }
    208
    209        private String getName(File file)
    210        {
    211            String name = configuration.getURL().toString();
    212            if (name == null)
    213            {
    214                if (file != null)
    215                {
    216                    name = file.getAbsolutePath();
    217                }
    218                else
    219                {
    220                    name = "base: " + configuration.getBasePath()
    221                           + "file: " + configuration.getFileName();
    222                }
    223            }
    224            return name;
    225        }
    226    }