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
018package org.apache.commons.configuration.reloading;
019
020import java.io.File;
021import java.net.MalformedURLException;
022import java.net.URL;
023
024import org.apache.commons.configuration.ConfigurationUtils;
025import org.apache.commons.configuration.FileConfiguration;
026import org.apache.commons.logging.Log;
027import 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 $Id: FileChangedReloadingStrategy.html 901729 2014-03-15 20:24:09Z oheger $
044 * @since 1.1
045 */
046public 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}