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