You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by oh...@apache.org on 2012/10/07 19:30:39 UTC
svn commit: r1395344 - in /commons/proper/configuration/trunk/src:
main/java/org/apache/commons/configuration/reloading/FileHandlerReloadingDetector.java
test/java/org/apache/commons/configuration/reloading/TestFileHandlerReloadingDetector.java
Author: oheger
Date: Sun Oct 7 17:30:39 2012
New Revision: 1395344
URL: http://svn.apache.org/viewvc?rev=1395344&view=rev
Log:
Added FileHandlerReloadingDetector class.
Added:
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/reloading/FileHandlerReloadingDetector.java (with props)
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/reloading/TestFileHandlerReloadingDetector.java (with props)
Added: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/reloading/FileHandlerReloadingDetector.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/reloading/FileHandlerReloadingDetector.java?rev=1395344&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/reloading/FileHandlerReloadingDetector.java (added)
+++ commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/reloading/FileHandlerReloadingDetector.java Sun Oct 7 17:30:39 2012
@@ -0,0 +1,260 @@
+/*
+ * 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.configuration.reloading;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.commons.configuration.ConfigurationUtils;
+import org.apache.commons.configuration.io.FileHandler;
+
+/**
+ * <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>
+ *
+ * @version $Id$
+ * @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 = 5000;
+
+ /** The associated file handler. */
+ private final FileHandler fileHandler;
+
+ /** The refresh delay. */
+ private final long refreshDelay;
+
+ /** The last time the configuration file was modified. */
+ private long lastModified;
+
+ /** The last time the file was checked for changes. */
+ private long lastChecked;
+
+ /**
+ * 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 refreshDelay the refresh delay; a value of 0 means that a check is
+ * performed in all cases
+ */
+ public FileHandlerReloadingDetector(FileHandler handler, long refreshDelay)
+ {
+ fileHandler = (handler != null) ? handler : new FileHandler();
+ this.refreshDelay = refreshDelay;
+ }
+
+ /**
+ * 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(FileHandler handler)
+ {
+ this(handler, DEFAULT_REFRESH_DELAY);
+ }
+
+ /**
+ * 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 refreshDelay;
+ }
+
+ /**
+ * {@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.
+ */
+ public boolean isReloadingRequired()
+ {
+ long now = System.currentTimeMillis();
+ if (now > lastChecked + getRefreshDelay())
+ {
+ lastChecked = now;
+
+ File file = getExistingFile();
+ if (file != null)
+ {
+ if (lastModified == 0)
+ {
+ // initialization
+ updateLastModified(file);
+ }
+ else
+ {
+ if (file.lastModified() != lastModified)
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ 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.
+ */
+ public void reloadingPerformed()
+ {
+ updateLastModified(getExistingFile());
+ }
+
+ /**
+ * 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()
+ {
+ 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;
+ }
+
+ /**
+ * Updates the last modified field based on the given {@code File} object.
+ * The file is checked for <b>null</b>.
+ *
+ * @param file the file to be monitored
+ */
+ private void updateLastModified(File file)
+ {
+ if (file != null)
+ {
+ lastModified = file.lastModified();
+ }
+ }
+
+ /**
+ * 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(URL url)
+ {
+ if (JAR_PROTOCOL.equals(url.getProtocol()))
+ {
+ String path = url.getPath();
+ try
+ {
+ return ConfigurationUtils.fileFromURL(new URL(path.substring(0,
+ path.indexOf('!'))));
+ }
+ catch (MalformedURLException mex)
+ {
+ return null;
+ }
+ }
+ else
+ {
+ return ConfigurationUtils.fileFromURL(url);
+ }
+ }
+}
Propchange: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/reloading/FileHandlerReloadingDetector.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/reloading/FileHandlerReloadingDetector.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Propchange: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/reloading/FileHandlerReloadingDetector.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/reloading/TestFileHandlerReloadingDetector.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/reloading/TestFileHandlerReloadingDetector.java?rev=1395344&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/reloading/TestFileHandlerReloadingDetector.java (added)
+++ commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/reloading/TestFileHandlerReloadingDetector.java Sun Oct 7 17:30:39 2012
@@ -0,0 +1,231 @@
+/*
+ * 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.configuration.reloading;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.net.URL;
+
+import org.apache.commons.configuration.io.FileHandler;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Test class for {@code FileHandlerReloadingDetector}.
+ *
+ * @version $Id$
+ */
+public class TestFileHandlerReloadingDetector
+{
+ /** The content of a test file. */
+ private static final String CONTENT = "Test file content ";
+
+ /** Constant for a sleep interval. */
+ private static final long SLEEP_TIME = 200;
+
+ /** Helper object for managing temporary files. */
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+
+ /** A counter for generating test file content. */
+ private int count;
+
+ /** The detector to be tested. */
+ private FileHandlerReloadingDetector detector;
+
+ @Before
+ public void setUp() throws Exception
+ {
+ detector = new FileHandlerReloadingDetector(null, 0);
+ }
+
+ /**
+ * Writes a test file at the specified location.
+ *
+ * @param f points to the file to be written
+ */
+ private void writeTestFile(File f)
+ {
+ Writer out = null;
+ try
+ {
+ out = new FileWriter(f);
+ out.write(CONTENT);
+ out.write(String.valueOf(count++));
+ }
+ catch (IOException ioex)
+ {
+ fail("Could not create test file: " + ioex);
+ }
+ finally
+ {
+ if (out != null)
+ {
+ try
+ {
+ out.close();
+ }
+ catch (IOException ioex)
+ {
+ // ignore
+ }
+ }
+ }
+ }
+
+ /**
+ * Tests whether an instance can be created with a file handler.
+ */
+ @Test
+ public void testInitWithFileHandler()
+ {
+ FileHandler handler = new FileHandler();
+ detector = new FileHandlerReloadingDetector(handler);
+ assertSame("Different file handler", handler, detector.getFileHandler());
+ }
+
+ /**
+ * Tests the default refresh delay.
+ */
+ @Test
+ public void testDefaultRefreshDelay()
+ {
+ detector = new FileHandlerReloadingDetector();
+ assertEquals("Wrong delay", 5000, detector.getRefreshDelay());
+ }
+
+ /**
+ * Tests that a newly created instance does not have a location.
+ */
+ @Test
+ public void testLocationAfterInit()
+ {
+ assertFalse("Got a location", detector.getFileHandler()
+ .isLocationDefined());
+ }
+
+ /**
+ * Tests isReloadingRequired() if no location has been set.
+ */
+ @Test
+ public void testIsReloadingRequiredNoLocation()
+ {
+ assertFalse("Reloading", detector.isReloadingRequired());
+ }
+
+ /**
+ * Helper method for testing whether the need for a reload operation is
+ * detected.
+ *
+ * @return the test file used by this method
+ */
+ private File checkReloadingDetect() throws IOException,
+ InterruptedException
+ {
+ File f = folder.newFile();
+ detector.getFileHandler().setFile(f);
+ writeTestFile(f);
+ assertFalse("Reloading required", detector.isReloadingRequired());
+ Thread.sleep(SLEEP_TIME);
+ writeTestFile(f);
+ assertTrue("Reloading not detected", detector.isReloadingRequired());
+ return f;
+ }
+
+ /**
+ * Tests whether a changed file is detected.
+ */
+ @Test
+ public void testIsReloadingRequiredTrue() throws Exception
+ {
+ checkReloadingDetect();
+ }
+
+ /**
+ * Tests a cycle with a detected reload operation and a notification that
+ * reloading was performed.
+ */
+ @Test
+ public void testReloadingAndReset() throws Exception
+ {
+ File f = checkReloadingDetect();
+ detector.reloadingPerformed();
+ assertFalse("Still reloading required", detector.isReloadingRequired());
+ Thread.sleep(SLEEP_TIME);
+ writeTestFile(f);
+ assertTrue("Next reloading not detected",
+ detector.isReloadingRequired());
+ }
+
+ /**
+ * Tests whether the refresh delay is taken into account.
+ */
+ @Test
+ public void testRefreshDelay() throws Exception
+ {
+ FileHandler handler = new FileHandler();
+ detector = new FileHandlerReloadingDetector(handler, 60 * 60 * 1000L);
+ File f = folder.newFile();
+ handler.setFile(f);
+ writeTestFile(f);
+ detector.reloadingPerformed();
+ assertFalse("Reloading initially required",
+ detector.isReloadingRequired());
+ Thread.sleep(SLEEP_TIME);
+ writeTestFile(f);
+ assertFalse("Reloading required", detector.isReloadingRequired());
+ }
+
+ /**
+ * Tests whether a non-existing file is handled correctly.
+ */
+ @Test
+ public void testIsReloadingRequiredFileDoesNotExist()
+ {
+ detector.getFileHandler().setFile(new File("NonExistingFile.txt"));
+ detector.reloadingPerformed();
+ assertFalse("Reloading required", detector.isReloadingRequired());
+ }
+
+ /**
+ * Tests whether a jar URL is handled correctly.
+ */
+ @Test
+ public void testGetFileJarURL() throws Exception
+ {
+ URL url =
+ new URL("jar:"
+ + new File("conf/resources.jar").getAbsoluteFile()
+ .toURI().toURL() + "!/test-jar.xml");
+ detector.getFileHandler().setURL(url);
+ File file = detector.getFile();
+ assertNotNull("Detector's file is null", file);
+ assertEquals("Detector does not monitor the jar file", "resources.jar",
+ file.getName());
+ }
+}
Propchange: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/reloading/TestFileHandlerReloadingDetector.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/reloading/TestFileHandlerReloadingDetector.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Propchange: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/reloading/TestFileHandlerReloadingDetector.java
------------------------------------------------------------------------------
svn:mime-type = text/plain