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