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 }