| /* |
| * 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 |
| * |
| * https://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.configuration2.builder; |
| |
| import java.util.Map; |
| |
| import org.apache.commons.configuration2.FileBasedConfiguration; |
| import org.apache.commons.configuration2.ex.ConfigurationException; |
| import org.apache.commons.configuration2.io.FileHandler; |
| import org.apache.commons.configuration2.reloading.ReloadingController; |
| import org.apache.commons.configuration2.reloading.ReloadingControllerSupport; |
| import org.apache.commons.configuration2.reloading.ReloadingDetector; |
| |
| /** |
| * <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 |
| * configurable {@link ReloadingDetector} 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, for example a timer. |
| * </p> |
| * |
| * @param <T> the concrete type of {@code Configuration} objects created by this builder |
| * @since 2.0 |
| */ |
| public class ReloadingFileBasedConfigurationBuilder<T extends FileBasedConfiguration> extends FileBasedConfigurationBuilder<T> |
| implements ReloadingControllerSupport { |
| |
| /** The default factory for creating reloading detector objects. */ |
| private static final ReloadingDetectorFactory DEFAULT_DETECTOR_FACTORY = new DefaultReloadingDetectorFactory(); |
| |
| /** |
| * Returns a {@code ReloadingDetectorFactory} either from the passed in parameters or a default factory. |
| * |
| * @param params the current parameters object |
| * @return the {@code ReloadingDetectorFactory} to be used |
| */ |
| private static ReloadingDetectorFactory fetchDetectorFactory(final FileBasedBuilderParametersImpl params) { |
| final ReloadingDetectorFactory factory = params.getReloadingDetectorFactory(); |
| return factory != null ? factory : DEFAULT_DETECTOR_FACTORY; |
| } |
| |
| /** 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. |
| * |
| * @param resCls the result class (must not be <strong>null</strong> |
| * @throws IllegalArgumentException if the result class is <strong>null</strong> |
| */ |
| public ReloadingFileBasedConfigurationBuilder(final Class<? extends T> resCls) { |
| super(resCls); |
| reloadingController = createReloadingController(); |
| } |
| |
| /** |
| * 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 <strong>null</strong> |
| * @param params a map with initialization parameters |
| * @throws IllegalArgumentException if the result class is <strong>null</strong> |
| */ |
| public ReloadingFileBasedConfigurationBuilder(final Class<? extends T> resCls, final Map<String, Object> params) { |
| super(resCls, params); |
| reloadingController = createReloadingController(); |
| } |
| |
| /** |
| * Creates a new instance of {@code ReloadingFileBasedConfigurationBuilder} which produces result objects of the |
| * specified class and sets initialization parameters and the <em>allowFailOnInit</em> flag. |
| * |
| * @param resCls the result class (must not be <strong>null</strong> |
| * @param params a map with initialization parameters |
| * @param allowFailOnInit the <em>allowFailOnInit</em> flag |
| * @throws IllegalArgumentException if the result class is <strong>null</strong> |
| */ |
| public ReloadingFileBasedConfigurationBuilder(final Class<? extends T> resCls, final Map<String, Object> params, final boolean allowFailOnInit) { |
| super(resCls, params, allowFailOnInit); |
| reloadingController = createReloadingController(); |
| } |
| |
| /** |
| * {@inheritDoc} This method is overridden here to change the result type. |
| */ |
| @Override |
| public ReloadingFileBasedConfigurationBuilder<T> configure(final BuilderParameters... params) { |
| super.configure(params); |
| return this; |
| } |
| |
| /** |
| * 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() { |
| final ReloadingDetector ctrlDetector = createReloadingDetectorForController(); |
| final ReloadingController ctrl = new ReloadingController(ctrlDetector); |
| connectToReloadingController(ctrl); |
| return ctrl; |
| } |
| |
| /** |
| * 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}. This implementation checks whether a |
| * {@code ReloadingDetectorFactory} is specified in the current parameters. If this is the case, it is invoked. |
| * Otherwise, a default factory is used to create a {@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} |
| * @throws ConfigurationException if an error occurs |
| */ |
| protected ReloadingDetector createReloadingDetector(final FileHandler handler, final FileBasedBuilderParametersImpl fbparams) |
| throws ConfigurationException { |
| return fetchDetectorFactory(fbparams).createReloadingDetector(handler, fbparams); |
| } |
| |
| /** |
| * 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() { |
| @Override |
| public boolean isReloadingRequired() { |
| final ReloadingDetector detector = resultReloadingDetector; |
| return detector != null && detector.isReloadingRequired(); |
| } |
| |
| @Override |
| public void reloadingPerformed() { |
| final ReloadingDetector detector = resultReloadingDetector; |
| if (detector != null) { |
| detector.reloadingPerformed(); |
| } |
| } |
| }; |
| } |
| |
| /** |
| * Gets 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} |
| */ |
| @Override |
| public ReloadingController getReloadingController() { |
| return reloadingController; |
| } |
| |
| /** |
| * {@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(final FileHandler handler) throws ConfigurationException { |
| super.initFileHandler(handler); |
| |
| resultReloadingDetector = createReloadingDetector(handler, FileBasedBuilderParametersImpl.fromParameters(getParameters(), true)); |
| } |
| } |