FileHandlerReloadingDetector.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.configuration2.reloading;

  18. import java.io.File;
  19. import java.net.MalformedURLException;
  20. import java.net.URL;

  21. import org.apache.commons.configuration2.io.FileHandler;
  22. import org.apache.commons.configuration2.io.FileLocatorUtils;

  23. /**
  24.  * <p>
  25.  * A specialized implementation of {@code ReloadingDetector} which monitors a file specified by a {@link FileHandler}.
  26.  * </p>
  27.  * <p>
  28.  * An instance of this class is passed a {@code FileHandler} at construction time. Each time the
  29.  * {@code isReloadingRequired()} method is called, it checks whether the {@code FileHandler} points to a valid location.
  30.  * If this is the case, the file's last modification time is obtained and compared with the last stored time. If it has
  31.  * changed, a reload operation should be performed.
  32.  * </p>
  33.  * <p>
  34.  * Because file I/O may be expensive it is possible to configure a refresh delay as a time in milliseconds. This is the
  35.  * minimum interval between two checks. If the {@code isReloadingRequired()} method is called in shorter intervals, it
  36.  * does not perform a check, but directly returns <strong>false</strong>.
  37.  * </p>
  38.  * <p>
  39.  * To initialize an instance either {@code isReloadingRequired()} or {@code reloadingPerformed()} can be called. The
  40.  * first call of {@code isReloadingRequired} does not perform a check, but obtains the initial modification date of the
  41.  * monitored file. {@code reloadingPerformed()} always obtains the file's modification date and stores it internally.
  42.  * </p>
  43.  *
  44.  * @since 2.0
  45.  */
  46. public class FileHandlerReloadingDetector implements ReloadingDetector {
  47.     /** Constant for the jar URL protocol. */
  48.     private static final String JAR_PROTOCOL = "jar";

  49.     /** Constant for the default refresh delay. */
  50.     private static final int DEFAULT_REFRESH_DELAY_MILLIS = 5000;

  51.     /**
  52.      * Helper method for transforming a URL into a file object. This method handles file: and jar: URLs.
  53.      *
  54.      * @param url the URL to be converted
  55.      * @return the resulting file or <strong>null </strong>
  56.      */
  57.     private static File fileFromURL(final URL url) {
  58.         if (JAR_PROTOCOL.equals(url.getProtocol())) {
  59.             final String path = url.getPath();
  60.             try {
  61.                 return FileLocatorUtils.fileFromURL(new URL(path.substring(0, path.indexOf('!'))));
  62.             } catch (final MalformedURLException mex) {
  63.                 return null;
  64.             }
  65.         }
  66.         return FileLocatorUtils.fileFromURL(url);
  67.     }

  68.     /** The associated file handler. */
  69.     private final FileHandler fileHandler;

  70.     /** The refresh delay. */
  71.     private final long refreshDelayMillis;

  72.     /** The last time the configuration file was modified. */
  73.     private long lastModifiedMillis;

  74.     /** The last time the file was checked for changes. */
  75.     private long lastCheckedMillis;

  76.     /**
  77.      * Creates a new instance of {@code FileHandlerReloadingDetector} with an uninitialized {@code FileHandler} object. The
  78.      * file to be monitored has to be set later by manipulating the handler object returned by {@code getFileHandler()}.
  79.      */
  80.     public FileHandlerReloadingDetector() {
  81.         this(null);
  82.     }

  83.     /**
  84.      * Creates a new instance of {@code FileHandlerReloadingDetector} and initializes it with the {@code FileHandler} to
  85.      * monitor and a default refresh delay.
  86.      *
  87.      * @param handler the {@code FileHandler} associated with this detector (can be <strong>null</strong>)
  88.      */
  89.     public FileHandlerReloadingDetector(final FileHandler handler) {
  90.         this(handler, DEFAULT_REFRESH_DELAY_MILLIS);
  91.     }

  92.     /**
  93.      * Creates a new instance of {@code FileHandlerReloadingDetector} and initializes it with the {@code FileHandler} to
  94.      * monitor and the refresh delay. The handler is directly used, no copy is created. So it is possible to change the
  95.      * location monitored by manipulating the {@code FileHandler} object.
  96.      *
  97.      * @param handler the {@code FileHandler} associated with this detector (can be <strong>null</strong>)
  98.      * @param refreshDelayMillis the refresh delay; a value of 0 means that a check is performed in all cases
  99.      */
  100.     public FileHandlerReloadingDetector(final FileHandler handler, final long refreshDelayMillis) {
  101.         fileHandler = handler != null ? handler : new FileHandler();
  102.         this.refreshDelayMillis = refreshDelayMillis;
  103.     }

  104.     /**
  105.      * Gets the monitored {@code File} or <strong>null</strong> if it does not exist.
  106.      *
  107.      * @return the monitored {@code File} or <strong>null</strong>
  108.      */
  109.     private File getExistingFile() {
  110.         File file = getFile();
  111.         if (file != null && !file.exists()) {
  112.             file = null;
  113.         }

  114.         return file;
  115.     }

  116.     /**
  117.      * Gets the {@code File} object which is monitored by this object. This method is called every time the file's last
  118.      * modification time is needed. If it returns <strong>null</strong>, no check is performed. This base implementation obtains the
  119.      * {@code File} from the associated {@code FileHandler}. It can also deal with URLs to jar files.
  120.      *
  121.      * @return the {@code File} to be monitored (can be <strong>null</strong>)
  122.      */
  123.     protected File getFile() {
  124.         final URL url = getFileHandler().getURL();
  125.         return url != null ? fileFromURL(url) : getFileHandler().getFile();
  126.     }

  127.     /**
  128.      * Gets the {@code FileHandler} associated with this object. The underlying handler is directly returned, so changing
  129.      * its location also changes the file monitored by this detector.
  130.      *
  131.      * @return the associated {@code FileHandler}
  132.      */
  133.     public FileHandler getFileHandler() {
  134.         return fileHandler;
  135.     }

  136.     /**
  137.      * Gets the date of the last modification of the monitored file. A return value of 0 indicates, that the monitored
  138.      * file does not exist.
  139.      *
  140.      * @return the last modification date in milliseconds.
  141.      */
  142.     protected long getLastModificationDate() {
  143.         final File file = getExistingFile();
  144.         return file != null ? file.lastModified() : 0;
  145.     }

  146.     /**
  147.      * Gets the refresh delay. This is a time in milliseconds. The {@code isReloadingRequired()} method first checks
  148.      * whether the time since the previous check is more than this value in the past. Otherwise, no check is performed. This
  149.      * is a means to limit file I/O caused by this class.
  150.      *
  151.      * @return the refresh delay used by this object
  152.      */
  153.     public long getRefreshDelay() {
  154.         return refreshDelayMillis;
  155.     }

  156.     /**
  157.      * {@inheritDoc} This implementation checks whether the associated {@link FileHandler} points to a valid file and
  158.      * whether the last modification time of this time has changed since the last check. The refresh delay is taken into
  159.      * account, too; a check is only performed if at least this time has passed since the last check.
  160.      */
  161.     @Override
  162.     public boolean isReloadingRequired() {
  163.         final long nowMillis = System.currentTimeMillis();
  164.         if (nowMillis >= lastCheckedMillis + getRefreshDelay()) {
  165.             lastCheckedMillis = nowMillis;

  166.             final long modifiedMillis = getLastModificationDate();
  167.             if (modifiedMillis > 0) {
  168.                 if (lastModifiedMillis != 0) {
  169.                     return modifiedMillis != lastModifiedMillis;
  170.                 }
  171.                 // initialization
  172.                 updateLastModified(modifiedMillis);
  173.             }
  174.         }

  175.         return false;
  176.     }

  177.     /**
  178.      * Tells this implementation that the internally stored state should be refreshed. This method is intended to be called
  179.      * after the creation of an instance.
  180.      */
  181.     public void refresh() {
  182.         updateLastModified(getLastModificationDate());
  183.     }

  184.     /**
  185.      * {@inheritDoc} This implementation updates the internally stored last modification date with the current modification
  186.      * date of the monitored file. So the next change is detected when this file is changed again.
  187.      */
  188.     @Override
  189.     public void reloadingPerformed() {
  190.         updateLastModified(getLastModificationDate());
  191.     }

  192.     /**
  193.      * Updates the last modification date of the monitored file. The need for a reload is detected only if the file's
  194.      * modification date is different from this value.
  195.      *
  196.      * @param timeMillis the new last modification date
  197.      */
  198.     protected void updateLastModified(final long timeMillis) {
  199.         lastModifiedMillis = timeMillis;
  200.     }
  201. }