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