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/11 21:45:49 UTC

svn commit: r1397263 - in /commons/proper/configuration/trunk/src: main/java/org/apache/commons/configuration/builder/ test/java/org/apache/commons/configuration/builder/

Author: oheger
Date: Thu Oct 11 19:45:48 2012
New Revision: 1397263

URL: http://svn.apache.org/viewvc?rev=1397263&view=rev
Log:
Added ReloadingFileBasedConfigurationBuilder.

Added:
    commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/builder/ReloadingFileBasedConfigurationBuilder.java   (with props)
    commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/builder/TestReloadingFileBasedConfigurationBuilder.java   (with props)

Added: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/builder/ReloadingFileBasedConfigurationBuilder.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/builder/ReloadingFileBasedConfigurationBuilder.java?rev=1397263&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/builder/ReloadingFileBasedConfigurationBuilder.java (added)
+++ commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/builder/ReloadingFileBasedConfigurationBuilder.java Thu Oct 11 19:45:48 2012
@@ -0,0 +1,228 @@
+/*
+ * 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.builder;
+
+import java.util.Map;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.FileBasedConfiguration;
+import org.apache.commons.configuration.io.FileHandler;
+import org.apache.commons.configuration.reloading.FileHandlerReloadingDetector;
+import org.apache.commons.configuration.reloading.ReloadingController;
+import org.apache.commons.configuration.reloading.ReloadingDetector;
+import org.apache.commons.configuration.reloading.ReloadingEvent;
+import org.apache.commons.configuration.reloading.ReloadingListener;
+
+/**
+ * <p>
+ * A specialized {@code ConfigurationBuilder} implementation which can handle
+ * configurations read from a {@link FileHandler} and supports reloading.
+ * </p>
+ * <p>
+ * This builder class exposes a {@link ReloadingController} object controlling
+ * reload operations on the file-based configuration produced as result object.
+ * For the {@code FileHandler} defining the location of the configuration a
+ * {@link FileHandlerReloadingDetector} is created and associated with the
+ * controller. So changes on the source file can be detected. When ever such a
+ * change occurs, the result object of this builder is reset. This means that
+ * the next time {@code getConfiguration()} is called a new
+ * {@code Configuration} object is created which is loaded from the modified
+ * file.
+ * </p>
+ * <p>
+ * Client code interested in notifications can register a listener at this
+ * builder to receive reset events. When such an event is received the new
+ * result object can be requested. This way client applications can be sure to
+ * work with an up-to-date configuration. It is also possible to register a
+ * listener directly at the {@code ReloadingController}.
+ * </p>
+ * <p>
+ * This builder does not actively trigger the {@code ReloadingController} to
+ * perform a reload check. This has to be done by an external component, e.g. a
+ * timer.
+ * </p>
+ *
+ * @version $Id$
+ * @since 2.0
+ * @param <T> the concrete type of {@code Configuration} objects created by this
+ *        builder
+ */
+public class ReloadingFileBasedConfigurationBuilder<T extends FileBasedConfiguration>
+        extends FileBasedConfigurationBuilder<T>
+{
+    /** The reloading controller associated with this object. */
+    private final ReloadingController reloadingController;
+
+    /**
+     * The reloading detector which does the actual reload check for the current
+     * result object. A new instance is created whenever a new result object
+     * (and thus a new current file handler) becomes available. The field must
+     * be volatile because it is accessed by the reloading controller probably
+     * from within another thread.
+     */
+    private volatile ReloadingDetector resultReloadingDetector;
+
+    /**
+     * Creates a new instance of {@code ReloadingFileBasedConfigurationBuilder}
+     * which produces result objects of the specified class and sets
+     * initialization parameters.
+     *
+     * @param resCls the result class (must not be <b>null</b>
+     * @param params a map with initialization parameters
+     * @throws IllegalArgumentException if the result class is <b>null</b>
+     */
+    public ReloadingFileBasedConfigurationBuilder(Class<T> resCls,
+            Map<String, Object> params)
+    {
+        super(resCls, params);
+        reloadingController = createReloadingController();
+    }
+
+    /**
+     * Creates a new instance of {@code ReloadingFileBasedConfigurationBuilder}
+     * which produces result objects of the specified class.
+     *
+     * @param resCls the result class (must not be <b>null</b>
+     * @throws IllegalArgumentException if the result class is <b>null</b>
+     */
+    public ReloadingFileBasedConfigurationBuilder(Class<T> resCls)
+    {
+        super(resCls);
+        reloadingController = createReloadingController();
+    }
+
+    /**
+     * Returns the {@code ReloadingController} associated with this builder.
+     * This controller is directly created. However, it becomes active (i.e.
+     * associated with a meaningful reloading detector) not before a result
+     * object was created.
+     *
+     * @return the {@code ReloadingController}
+     */
+    public ReloadingController getReloadingController()
+    {
+        return reloadingController;
+    }
+
+    /**
+     * Creates a {@code ReloadingDetector} which monitors the passed in
+     * {@code FileHandler}. This method is called each time a new result object
+     * is created with the current {@code FileHandler}. The
+     * {@code ReloadingDetector} associated with this builder's
+     * {@link ReloadingController} delegates to this object. This implementation
+     * returns a new {@code FileHandlerReloadingDetector} object. Note: This
+     * method is called from a synchronized block.
+     *
+     * @param handler the current {@code FileHandler}
+     * @param fbparams the object with parameters related to file-based builders
+     * @return a {@code ReloadingDetector} for this {@code FileHandler}
+     */
+    protected ReloadingDetector createReloadingDetector(FileHandler handler,
+            FileBasedBuilderParameters fbparams)
+    {
+        return new FileHandlerReloadingDetector(handler,
+                fbparams.getReloadingRefreshDelay());
+    }
+
+    /**
+     * {@inheritDoc} This implementation also takes care that a new
+     * {@code ReloadingDetector} for the new current {@code FileHandler} is
+     * created. Also, the reloading controller's reloading state has to be
+     * reset; after the creation of a new result object changes in the
+     * underlying configuration source have to be monitored again.
+     */
+    @Override
+    protected void initFileHandler(FileHandler handler)
+            throws ConfigurationException
+    {
+        super.initFileHandler(handler);
+
+        resultReloadingDetector =
+                createReloadingDetector(handler,
+                        FileBasedBuilderParameters.fromParameters(
+                                getParameters(), true));
+        getReloadingController().resetReloadingState();
+    }
+
+    /**
+     * Creates the {@code ReloadingController} associated with this object. The
+     * controller is assigned a specialized reloading detector which delegates
+     * to the detector for the current result object. (
+     * {@code FileHandlerReloadingDetector} does not support changing the file
+     * handler, and {@code ReloadingController} does not support changing the
+     * reloading detector; therefore, this level of indirection is needed to
+     * change the monitored file dynamically.)
+     *
+     * @return the new {@code ReloadingController}
+     */
+    private ReloadingController createReloadingController()
+    {
+        ReloadingDetector ctrlDetector = createReloadingDetectorForController();
+        ReloadingController ctrl = new ReloadingController(ctrlDetector);
+        ctrl.addReloadingListener(createReloadingListener());
+        return ctrl;
+    }
+
+    /**
+     * Creates a listener object to be registered at the associated reloading
+     * controller. This listener resets the builder's result object whenever a
+     * change in the monitored file is detected. This will trigger a builder
+     * reset event, and the next time {@code getConfiguration()} is called, a
+     * new result object is created.
+     *
+     * @return the listener for the associated {@code ReloadingController}
+     */
+    private ReloadingListener createReloadingListener()
+    {
+        return new ReloadingListener()
+        {
+            public void reloadingRequired(ReloadingEvent event)
+            {
+                resetResult();
+            }
+        };
+    }
+
+    /**
+     * Creates a {@code ReloadingDetector} wrapper to be passed to the
+     * associated {@code ReloadingController}. This detector wrapper simply
+     * delegates to the current {@code ReloadingDetector} if it is available.
+     *
+     * @return the wrapper {@code ReloadingDetector}
+     */
+    private ReloadingDetector createReloadingDetectorForController()
+    {
+        return new ReloadingDetector()
+        {
+            public void reloadingPerformed()
+            {
+                ReloadingDetector detector = resultReloadingDetector;
+                if (detector != null)
+                {
+                    detector.reloadingPerformed();
+                }
+            }
+
+            public boolean isReloadingRequired()
+            {
+                ReloadingDetector detector = resultReloadingDetector;
+                return (detector != null) ? detector.isReloadingRequired()
+                        : false;
+            }
+        };
+    }
+}

Propchange: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/builder/ReloadingFileBasedConfigurationBuilder.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/builder/ReloadingFileBasedConfigurationBuilder.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/builder/ReloadingFileBasedConfigurationBuilder.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/builder/TestReloadingFileBasedConfigurationBuilder.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/builder/TestReloadingFileBasedConfigurationBuilder.java?rev=1397263&view=auto
==============================================================================
--- commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/builder/TestReloadingFileBasedConfigurationBuilder.java (added)
+++ commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/builder/TestReloadingFileBasedConfigurationBuilder.java Thu Oct 11 19:45:48 2012
@@ -0,0 +1,238 @@
+/*
+ * 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.builder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.commons.configuration.io.FileHandler;
+import org.apache.commons.configuration.reloading.FileHandlerReloadingDetector;
+import org.apache.commons.configuration.reloading.ReloadingDetector;
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+/**
+ * Test class for {@code ReloadingFileBasedConfigurationBuilder}.
+ *
+ * @version $Id$
+ */
+public class TestReloadingFileBasedConfigurationBuilder
+{
+    /**
+     * Tests whether a configuration can be created if no location is set. This
+     * tests also ensures that the super constructor is called correctly.
+     */
+    @Test
+    public void testGetConfigurationNoLocation() throws ConfigurationException
+    {
+        Map<String, Object> params = new HashMap<String, Object>();
+        params.put("throwExceptionOnMissing", Boolean.TRUE);
+        ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration> builder =
+                new ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration>(
+                        PropertiesConfiguration.class, params);
+        PropertiesConfiguration conf = builder.getConfiguration();
+        assertTrue("Property not set", conf.isThrowExceptionOnMissing());
+        assertTrue("Not empty", conf.isEmpty());
+    }
+
+    /**
+     * Tests whether a correct reloading detector is created.
+     */
+    @Test
+    public void testCreateReloadingDetector()
+    {
+        ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration> builder =
+                new ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration>(
+                        PropertiesConfiguration.class);
+        FileHandler handler = new FileHandler();
+        FileBasedBuilderParameters params = new FileBasedBuilderParameters();
+        long refreshDelay = 60000L;
+        params.setReloadingRefreshDelay(refreshDelay);
+        FileHandlerReloadingDetector detector =
+                (FileHandlerReloadingDetector) builder.createReloadingDetector(
+                        handler, params);
+        assertSame("Wrong file handler", handler, detector.getFileHandler());
+        assertEquals("Wrong refresh delay", refreshDelay,
+                detector.getRefreshDelay());
+    }
+
+    /**
+     * Tests the isReloadingRequired() implementation of the detector associated
+     * with the reloading controller.
+     */
+    @Test
+    public void testReloadingDetectorIsReloadingRequired()
+            throws ConfigurationException
+    {
+        ReloadingDetector detector =
+                EasyMock.createMock(ReloadingDetector.class);
+        EasyMock.expect(detector.isReloadingRequired()).andReturn(Boolean.TRUE);
+        EasyMock.expect(detector.isReloadingRequired())
+                .andReturn(Boolean.FALSE);
+        EasyMock.replay(detector);
+        ReloadingFileBasedConfigurationBuilderTestImpl builder =
+                new ReloadingFileBasedConfigurationBuilderTestImpl(detector);
+        builder.getConfiguration();
+        ReloadingDetector ctrlDetector =
+                builder.getReloadingController().getDetector();
+        assertTrue("Wrong result (1)", ctrlDetector.isReloadingRequired());
+        assertFalse("Wrong result (2)", ctrlDetector.isReloadingRequired());
+        assertSame("Wrong file handler", builder.getFileHandler(),
+                builder.getHandlerForDetector());
+        EasyMock.verify(detector);
+    }
+
+    /**
+     * Tests the reloadingPerformed() implementation of the detector associated
+     * with the reloading controller.
+     */
+    @Test
+    public void testReloadingDetectorReloadingPerformed()
+            throws ConfigurationException
+    {
+        ReloadingDetector detector =
+                EasyMock.createMock(ReloadingDetector.class);
+        detector.reloadingPerformed();
+        EasyMock.replay(detector);
+        ReloadingFileBasedConfigurationBuilderTestImpl builder =
+                new ReloadingFileBasedConfigurationBuilderTestImpl(detector);
+        builder.getConfiguration();
+        ReloadingDetector ctrlDetector =
+                builder.getReloadingController().getDetector();
+        ctrlDetector.reloadingPerformed();
+        EasyMock.verify(detector);
+    }
+
+    /**
+     * Tests the behavior of the reloading detector if no underlying detector is
+     * available.
+     */
+    @Test
+    public void testReloadingDetectorNoFileHandler()
+    {
+        ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration> builder =
+                new ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration>(
+                        PropertiesConfiguration.class);
+        ReloadingDetector ctrlDetector =
+                builder.getReloadingController().getDetector();
+        ctrlDetector.reloadingPerformed();
+        assertFalse("Wrong result", ctrlDetector.isReloadingRequired());
+    }
+
+    /**
+     * Tests whether the controller's reloading state is reset when a new result
+     * configuration is created.
+     */
+    @Test
+    public void testResetReloadingStateInGetConfiguration()
+            throws ConfigurationException
+    {
+        ReloadingDetector detector =
+                EasyMock.createMock(ReloadingDetector.class);
+        EasyMock.expect(detector.isReloadingRequired()).andReturn(Boolean.TRUE);
+        detector.reloadingPerformed();
+        EasyMock.replay(detector);
+        ReloadingFileBasedConfigurationBuilderTestImpl builder =
+                new ReloadingFileBasedConfigurationBuilderTestImpl(detector);
+        PropertiesConfiguration config1 = builder.getConfiguration();
+        builder.getReloadingController().checkForReloading(null);
+        PropertiesConfiguration config2 = builder.getConfiguration();
+        assertNotSame("No new configuration instance", config1, config2);
+        assertFalse("Still in reloading state", builder
+                .getReloadingController().isInReloadingState());
+        EasyMock.verify(detector);
+    }
+
+    /**
+     * Tests whether this builder reacts on events fired by the reloading
+     * controller.
+     */
+    @Test
+    public void testReloadingControllerEvents() throws ConfigurationException
+    {
+        ReloadingDetector detector =
+                EasyMock.createMock(ReloadingDetector.class);
+        BuilderListener listener = EasyMock.createMock(BuilderListener.class);
+        EasyMock.expect(detector.isReloadingRequired()).andReturn(Boolean.TRUE);
+        ReloadingFileBasedConfigurationBuilderTestImpl builder =
+                new ReloadingFileBasedConfigurationBuilderTestImpl(detector);
+        listener.builderReset(builder);
+        EasyMock.replay(detector, listener);
+        builder.addBuilderListener(listener);
+        builder.getConfiguration();
+        builder.getReloadingController().checkForReloading(null);
+        EasyMock.verify(detector, listener);
+    }
+
+    /**
+     * A test builder implementation which allows mocking the underlying
+     * reloading detector.
+     */
+    private static class ReloadingFileBasedConfigurationBuilderTestImpl extends
+            ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration>
+    {
+        /** The mock for the reloading detector. */
+        private final ReloadingDetector mockDetector;
+
+        /** Stores the file handler passed to createReloadingDetector(). */
+        private FileHandler handlerForDetector;
+
+        /**
+         * Creates a new instance of
+         * {@code ReloadingFileBasedConfigurationBuilderTestImpl} and
+         * initializes it with a mock reloading detector.
+         *
+         * @param detector the mock detector
+         */
+        public ReloadingFileBasedConfigurationBuilderTestImpl(
+                ReloadingDetector detector)
+        {
+            super(PropertiesConfiguration.class);
+            mockDetector = detector;
+        }
+
+        /**
+         * Returns the file handler that was passed to
+         * createReloadingDetector().
+         *
+         * @return the file handler
+         */
+        public FileHandler getHandlerForDetector()
+        {
+            return handlerForDetector;
+        }
+
+        /**
+         * Returns the mock file handler.
+         */
+        @Override
+        protected ReloadingDetector createReloadingDetector(
+                FileHandler handler, FileBasedBuilderParameters fbparams)
+        {
+            handlerForDetector = handler;
+            return mockDetector;
+        }
+    }
+}

Propchange: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/builder/TestReloadingFileBasedConfigurationBuilder.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/builder/TestReloadingFileBasedConfigurationBuilder.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/builder/TestReloadingFileBasedConfigurationBuilder.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain