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 */ 017package org.apache.commons.configuration2.reloading; 018 019import java.io.File; 020import java.net.MalformedURLException; 021import java.net.URL; 022 023import org.apache.commons.configuration2.io.FileHandler; 024import org.apache.commons.configuration2.io.FileLocatorUtils; 025 026/** 027 * <p> 028 * A specialized implementation of {@code ReloadingDetector} which monitors a 029 * file specified by a {@link FileHandler}. 030 * </p> 031 * <p> 032 * An instance of this class is passed a {@code FileHandler} at construction 033 * time. Each time the {@code isReloadingRequired()} method is called, it checks 034 * whether the {@code FileHandler} points to a valid location. If this is the 035 * case, the file's last modification time is obtained and compared with the 036 * last stored time. If it has changed, a reload operation should be performed. 037 * </p> 038 * <p> 039 * Because file I/O may be expensive it is possible to configure a refresh delay 040 * as a time in milliseconds. This is the minimum interval between two checks. 041 * If the {@code isReloadingRequired()} method is called in shorter intervals, 042 * it does not perform a check, but directly returns <b>false</b>. 043 * </p> 044 * <p> 045 * To initialize an instance either {@code isReloadingRequired()} or 046 * {@code reloadingPerformed()} can be called. The first call of 047 * {@code isReloadingRequired} does not perform a check, but obtains the initial 048 * modification date of the monitored file. {@code reloadingPerformed()} always 049 * obtains the file's modification date and stores it internally. 050 * </p> 051 * 052 * @since 2.0 053 */ 054public class FileHandlerReloadingDetector implements ReloadingDetector 055{ 056 /** Constant for the jar URL protocol. */ 057 private static final String JAR_PROTOCOL = "jar"; 058 059 /** Constant for the default refresh delay. */ 060 private static final int DEFAULT_REFRESH_DELAY = 5000; 061 062 /** The associated file handler. */ 063 private final FileHandler fileHandler; 064 065 /** The refresh delay. */ 066 private final long refreshDelay; 067 068 /** The last time the configuration file was modified. */ 069 private long lastModified; 070 071 /** The last time the file was checked for changes. */ 072 private long lastChecked; 073 074 /** 075 * Creates a new instance of {@code FileHandlerReloadingDetector} and 076 * initializes it with the {@code FileHandler} to monitor and the refresh 077 * delay. The handler is directly used, no copy is created. So it is 078 * possible to change the location monitored by manipulating the 079 * {@code FileHandler} object. 080 * 081 * @param handler the {@code FileHandler} associated with this detector (can 082 * be <b>null</b>) 083 * @param refreshDelay the refresh delay; a value of 0 means that a check is 084 * performed in all cases 085 */ 086 public FileHandlerReloadingDetector(final FileHandler handler, final long refreshDelay) 087 { 088 fileHandler = handler != null ? handler : new FileHandler(); 089 this.refreshDelay = refreshDelay; 090 } 091 092 /** 093 * Creates a new instance of {@code FileHandlerReloadingDetector} and 094 * initializes it with the {@code FileHandler} to monitor and a default 095 * refresh delay. 096 * 097 * @param handler the {@code FileHandler} associated with this detector (can 098 * be <b>null</b>) 099 */ 100 public FileHandlerReloadingDetector(final FileHandler handler) 101 { 102 this(handler, DEFAULT_REFRESH_DELAY); 103 } 104 105 /** 106 * Creates a new instance of {@code FileHandlerReloadingDetector} with an 107 * uninitialized {@code FileHandler} object. The file to be monitored has to 108 * be set later by manipulating the handler object returned by 109 * {@code getFileHandler()}. 110 */ 111 public FileHandlerReloadingDetector() 112 { 113 this(null); 114 } 115 116 /** 117 * Returns the {@code FileHandler} associated with this object. The 118 * underlying handler is directly returned, so changing its location also 119 * changes the file monitored by this detector. 120 * 121 * @return the associated {@code FileHandler} 122 */ 123 public FileHandler getFileHandler() 124 { 125 return fileHandler; 126 } 127 128 /** 129 * Returns the refresh delay. This is a time in milliseconds. The 130 * {@code isReloadingRequired()} method first checks whether the time since 131 * the previous check is more than this value in the past. Otherwise, no 132 * check is performed. This is a means to limit file I/O caused by this 133 * class. 134 * 135 * @return the refresh delay used by this object 136 */ 137 public long getRefreshDelay() 138 { 139 return refreshDelay; 140 } 141 142 /** 143 * {@inheritDoc} This implementation checks whether the associated 144 * {@link FileHandler} points to a valid file and whether the last 145 * modification time of this time has changed since the last check. The 146 * refresh delay is taken into account, too; a check is only performed if at 147 * least this time has passed since the last check. 148 */ 149 @Override 150 public boolean isReloadingRequired() 151 { 152 final long now = System.currentTimeMillis(); 153 if (now >= lastChecked + getRefreshDelay()) 154 { 155 lastChecked = now; 156 157 final long modified = getLastModificationDate(); 158 if (modified > 0) 159 { 160 if (lastModified == 0) 161 { 162 // initialization 163 updateLastModified(modified); 164 } 165 else 166 { 167 if (modified != lastModified) 168 { 169 return true; 170 } 171 } 172 } 173 } 174 175 return false; 176 } 177 178 /** 179 * {@inheritDoc} This implementation updates the internally stored last 180 * modification date with the current modification date of the monitored 181 * file. So the next change is detected when this file is changed again. 182 */ 183 @Override 184 public void reloadingPerformed() 185 { 186 updateLastModified(getLastModificationDate()); 187 } 188 189 /** 190 * Tells this implementation that the internally stored state should be 191 * refreshed. This method is intended to be called after the creation 192 * of an instance. 193 */ 194 public void refresh() 195 { 196 updateLastModified(getLastModificationDate()); 197 } 198 199 /** 200 * Returns the date of the last modification of the monitored file. A return 201 * value of 0 indicates, that the monitored file does not exist. 202 * 203 * @return the last modification date 204 */ 205 protected long getLastModificationDate() 206 { 207 final File file = getExistingFile(); 208 return file != null ? file.lastModified() : 0; 209 } 210 211 /** 212 * Updates the last modification date of the monitored file. The need for a 213 * reload is detected only if the file's modification date is different from 214 * this value. 215 * 216 * @param time the new last modification date 217 */ 218 protected void updateLastModified(final long time) 219 { 220 lastModified = time; 221 } 222 223 /** 224 * Returns the {@code File} object which is monitored by this object. This 225 * method is called every time the file's last modification time is needed. 226 * If it returns <b>null</b>, no check is performed. This base 227 * implementation obtains the {@code File} from the associated 228 * {@code FileHandler}. It can also deal with URLs to jar files. 229 * 230 * @return the {@code File} to be monitored (can be <b>null</b>) 231 */ 232 protected File getFile() 233 { 234 final URL url = getFileHandler().getURL(); 235 return url != null ? fileFromURL(url) : getFileHandler().getFile(); 236 } 237 238 /** 239 * Returns the monitored {@code File} or <b>null</b> if it does not exist. 240 * 241 * @return the monitored {@code File} or <b>null</b> 242 */ 243 private File getExistingFile() 244 { 245 File file = getFile(); 246 if (file != null && !file.exists()) 247 { 248 file = null; 249 } 250 251 return file; 252 } 253 254 /** 255 * Helper method for transforming a URL into a file object. This method 256 * handles file: and jar: URLs. 257 * 258 * @param url the URL to be converted 259 * @return the resulting file or <b>null </b> 260 */ 261 private static File fileFromURL(final URL url) 262 { 263 if (JAR_PROTOCOL.equals(url.getProtocol())) 264 { 265 final String path = url.getPath(); 266 try 267 { 268 return FileLocatorUtils.fileFromURL(new URL(path.substring(0, 269 path.indexOf('!')))); 270 } 271 catch (final MalformedURLException mex) 272 { 273 return null; 274 } 275 } 276 return FileLocatorUtils.fileFromURL(url); 277 } 278}