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