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