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 $Id: FileChangedReloadingStrategy.java 1210646 2011-12-05 21:25:01Z oheger $
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    }