| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.configuration2.reloading; |
| |
| import java.io.File; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| |
| import org.apache.commons.configuration2.io.FileHandler; |
| import org.apache.commons.configuration2.io.FileLocatorUtils; |
| |
| /** |
| * <p> |
| * A specialized implementation of {@code ReloadingDetector} which monitors a |
| * file specified by a {@link FileHandler}. |
| * </p> |
| * <p> |
| * An instance of this class is passed a {@code FileHandler} at construction |
| * time. Each time the {@code isReloadingRequired()} method is called, it checks |
| * whether the {@code FileHandler} points to a valid location. If this is the |
| * case, the file's last modification time is obtained and compared with the |
| * last stored time. If it has changed, a reload operation should be performed. |
| * </p> |
| * <p> |
| * Because file I/O may be expensive it is possible to configure a refresh delay |
| * as a time in milliseconds. This is the minimum interval between two checks. |
| * If the {@code isReloadingRequired()} method is called in shorter intervals, |
| * it does not perform a check, but directly returns <b>false</b>. |
| * </p> |
| * <p> |
| * To initialize an instance either {@code isReloadingRequired()} or |
| * {@code reloadingPerformed()} can be called. The first call of |
| * {@code isReloadingRequired} does not perform a check, but obtains the initial |
| * modification date of the monitored file. {@code reloadingPerformed()} always |
| * obtains the file's modification date and stores it internally. |
| * </p> |
| * |
| * @since 2.0 |
| */ |
| public class FileHandlerReloadingDetector implements ReloadingDetector |
| { |
| /** Constant for the jar URL protocol. */ |
| private static final String JAR_PROTOCOL = "jar"; |
| |
| /** Constant for the default refresh delay. */ |
| private static final int DEFAULT_REFRESH_DELAY_MILLIS = 5000; |
| |
| /** The associated file handler. */ |
| private final FileHandler fileHandler; |
| |
| /** The refresh delay. */ |
| private final long refreshDelayMillis; |
| |
| /** The last time the configuration file was modified. */ |
| private long lastModifiedMillis; |
| |
| /** The last time the file was checked for changes. */ |
| private long lastCheckedMillis; |
| |
| /** |
| * Creates a new instance of {@code FileHandlerReloadingDetector} and |
| * initializes it with the {@code FileHandler} to monitor and the refresh |
| * delay. The handler is directly used, no copy is created. So it is |
| * possible to change the location monitored by manipulating the |
| * {@code FileHandler} object. |
| * |
| * @param handler the {@code FileHandler} associated with this detector (can |
| * be <b>null</b>) |
| * @param refreshDelayMillis the refresh delay; a value of 0 means that a check is |
| * performed in all cases |
| */ |
| public FileHandlerReloadingDetector(final FileHandler handler, final long refreshDelayMillis) |
| { |
| fileHandler = handler != null ? handler : new FileHandler(); |
| this.refreshDelayMillis = refreshDelayMillis; |
| } |
| |
| /** |
| * Creates a new instance of {@code FileHandlerReloadingDetector} and |
| * initializes it with the {@code FileHandler} to monitor and a default |
| * refresh delay. |
| * |
| * @param handler the {@code FileHandler} associated with this detector (can |
| * be <b>null</b>) |
| */ |
| public FileHandlerReloadingDetector(final FileHandler handler) |
| { |
| this(handler, DEFAULT_REFRESH_DELAY_MILLIS); |
| } |
| |
| /** |
| * Creates a new instance of {@code FileHandlerReloadingDetector} with an |
| * uninitialized {@code FileHandler} object. The file to be monitored has to |
| * be set later by manipulating the handler object returned by |
| * {@code getFileHandler()}. |
| */ |
| public FileHandlerReloadingDetector() |
| { |
| this(null); |
| } |
| |
| /** |
| * Returns the {@code FileHandler} associated with this object. The |
| * underlying handler is directly returned, so changing its location also |
| * changes the file monitored by this detector. |
| * |
| * @return the associated {@code FileHandler} |
| */ |
| public FileHandler getFileHandler() |
| { |
| return fileHandler; |
| } |
| |
| /** |
| * Returns the refresh delay. This is a time in milliseconds. The |
| * {@code isReloadingRequired()} method first checks whether the time since |
| * the previous check is more than this value in the past. Otherwise, no |
| * check is performed. This is a means to limit file I/O caused by this |
| * class. |
| * |
| * @return the refresh delay used by this object |
| */ |
| public long getRefreshDelay() |
| { |
| return refreshDelayMillis; |
| } |
| |
| /** |
| * {@inheritDoc} This implementation checks whether the associated |
| * {@link FileHandler} points to a valid file and whether the last |
| * modification time of this time has changed since the last check. The |
| * refresh delay is taken into account, too; a check is only performed if at |
| * least this time has passed since the last check. |
| */ |
| @Override |
| public boolean isReloadingRequired() |
| { |
| final long nowMillis = System.currentTimeMillis(); |
| if (nowMillis >= lastCheckedMillis + getRefreshDelay()) |
| { |
| lastCheckedMillis = nowMillis; |
| |
| final long modifiedMillis = getLastModificationDate(); |
| if (modifiedMillis > 0) |
| { |
| if (lastModifiedMillis != 0) { |
| return modifiedMillis != lastModifiedMillis; |
| } |
| // initialization |
| updateLastModified(modifiedMillis); |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * {@inheritDoc} This implementation updates the internally stored last |
| * modification date with the current modification date of the monitored |
| * file. So the next change is detected when this file is changed again. |
| */ |
| @Override |
| public void reloadingPerformed() |
| { |
| updateLastModified(getLastModificationDate()); |
| } |
| |
| /** |
| * Tells this implementation that the internally stored state should be |
| * refreshed. This method is intended to be called after the creation |
| * of an instance. |
| */ |
| public void refresh() |
| { |
| updateLastModified(getLastModificationDate()); |
| } |
| |
| /** |
| * Returns the date of the last modification of the monitored file. A return |
| * value of 0 indicates, that the monitored file does not exist. |
| * |
| * @return the last modification date in milliseconds. |
| */ |
| protected long getLastModificationDate() |
| { |
| final File file = getExistingFile(); |
| return file != null ? file.lastModified() : 0; |
| } |
| |
| /** |
| * Updates the last modification date of the monitored file. The need for a |
| * reload is detected only if the file's modification date is different from |
| * this value. |
| * |
| * @param timeMillis the new last modification date |
| */ |
| protected void updateLastModified(final long timeMillis) |
| { |
| lastModifiedMillis = timeMillis; |
| } |
| |
| /** |
| * Returns the {@code File} object which is monitored by this object. This |
| * method is called every time the file's last modification time is needed. |
| * If it returns <b>null</b>, no check is performed. This base |
| * implementation obtains the {@code File} from the associated |
| * {@code FileHandler}. It can also deal with URLs to jar files. |
| * |
| * @return the {@code File} to be monitored (can be <b>null</b>) |
| */ |
| protected File getFile() |
| { |
| final URL url = getFileHandler().getURL(); |
| return url != null ? fileFromURL(url) : getFileHandler().getFile(); |
| } |
| |
| /** |
| * Returns the monitored {@code File} or <b>null</b> if it does not exist. |
| * |
| * @return the monitored {@code File} or <b>null</b> |
| */ |
| private File getExistingFile() |
| { |
| File file = getFile(); |
| if (file != null && !file.exists()) |
| { |
| file = null; |
| } |
| |
| return file; |
| } |
| |
| /** |
| * Helper method for transforming a URL into a file object. This method |
| * handles file: and jar: URLs. |
| * |
| * @param url the URL to be converted |
| * @return the resulting file or <b>null </b> |
| */ |
| private static File fileFromURL(final URL url) |
| { |
| if (JAR_PROTOCOL.equals(url.getProtocol())) |
| { |
| final String path = url.getPath(); |
| try |
| { |
| return FileLocatorUtils.fileFromURL(new URL(path.substring(0, |
| path.indexOf('!')))); |
| } |
| catch (final MalformedURLException mex) |
| { |
| return null; |
| } |
| } |
| return FileLocatorUtils.fileFromURL(url); |
| } |
| } |