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 1842194 2018-09-27 22:24:23Z ggregory $ 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(final FileHandler handler, final 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(final 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 final long now = System.currentTimeMillis(); 154 if (now >= lastChecked + getRefreshDelay()) 155 { 156 lastChecked = now; 157 158 final 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 * Tells this implementation that the internally stored state should be 192 * refreshed. This method is intended to be called after the creation 193 * of an instance. 194 */ 195 public void refresh() 196 { 197 updateLastModified(getLastModificationDate()); 198 } 199 200 /** 201 * Returns the date of the last modification of the monitored file. A return 202 * value of 0 indicates, that the monitored file does not exist. 203 * 204 * @return the last modification date 205 */ 206 protected long getLastModificationDate() 207 { 208 final File file = getExistingFile(); 209 return (file != null) ? file.lastModified() : 0; 210 } 211 212 /** 213 * Updates the last modification date of the monitored file. The need for a 214 * reload is detected only if the file's modification date is different from 215 * this value. 216 * 217 * @param time the new last modification date 218 */ 219 protected void updateLastModified(final long time) 220 { 221 lastModified = time; 222 } 223 224 /** 225 * Returns the {@code File} object which is monitored by this object. This 226 * method is called every time the file's last modification time is needed. 227 * If it returns <b>null</b>, no check is performed. This base 228 * implementation obtains the {@code File} from the associated 229 * {@code FileHandler}. It can also deal with URLs to jar files. 230 * 231 * @return the {@code File} to be monitored (can be <b>null</b>) 232 */ 233 protected File getFile() 234 { 235 final URL url = getFileHandler().getURL(); 236 return (url != null) ? fileFromURL(url) : getFileHandler().getFile(); 237 } 238 239 /** 240 * Returns the monitored {@code File} or <b>null</b> if it does not exist. 241 * 242 * @return the monitored {@code File} or <b>null</b> 243 */ 244 private File getExistingFile() 245 { 246 File file = getFile(); 247 if (file != null && !file.exists()) 248 { 249 file = null; 250 } 251 252 return file; 253 } 254 255 /** 256 * Helper method for transforming a URL into a file object. This method 257 * handles file: and jar: URLs. 258 * 259 * @param url the URL to be converted 260 * @return the resulting file or <b>null </b> 261 */ 262 private static File fileFromURL(final URL url) 263 { 264 if (JAR_PROTOCOL.equals(url.getProtocol())) 265 { 266 final String path = url.getPath(); 267 try 268 { 269 return FileLocatorUtils.fileFromURL(new URL(path.substring(0, 270 path.indexOf('!')))); 271 } 272 catch (final MalformedURLException mex) 273 { 274 return null; 275 } 276 } 277 return FileLocatorUtils.fileFromURL(url); 278 } 279}