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