ReloadingFileBasedConfigurationBuilder.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.configuration2.builder;

  18. import java.util.Map;

  19. import org.apache.commons.configuration2.FileBasedConfiguration;
  20. import org.apache.commons.configuration2.ex.ConfigurationException;
  21. import org.apache.commons.configuration2.io.FileHandler;
  22. import org.apache.commons.configuration2.reloading.ReloadingController;
  23. import org.apache.commons.configuration2.reloading.ReloadingControllerSupport;
  24. import org.apache.commons.configuration2.reloading.ReloadingDetector;

  25. /**
  26.  * <p>
  27.  * A specialized {@code ConfigurationBuilder} implementation which can handle configurations read from a
  28.  * {@link FileHandler} and supports reloading.
  29.  * </p>
  30.  * <p>
  31.  * This builder class exposes a {@link ReloadingController} object controlling reload operations on the file-based
  32.  * configuration produced as result object. For the {@code FileHandler} defining the location of the configuration a
  33.  * configurable {@link ReloadingDetector} is created and associated with the controller. So changes on the source file
  34.  * can be detected. When ever such a change occurs, the result object of this builder is reset. This means that the next
  35.  * time {@code getConfiguration()} is called a new {@code Configuration} object is created which is loaded from the
  36.  * modified file.
  37.  * </p>
  38.  * <p>
  39.  * Client code interested in notifications can register a listener at this builder to receive reset events. When such an
  40.  * event is received the new result object can be requested. This way client applications can be sure to work with an
  41.  * up-to-date configuration. It is also possible to register a listener directly at the {@code ReloadingController}.
  42.  * </p>
  43.  * <p>
  44.  * This builder does not actively trigger the {@code ReloadingController} to perform a reload check. This has to be done
  45.  * by an external component, for example a timer.
  46.  * </p>
  47.  *
  48.  * @param <T> the concrete type of {@code Configuration} objects created by this builder
  49.  * @since 2.0
  50.  */
  51. public class ReloadingFileBasedConfigurationBuilder<T extends FileBasedConfiguration> extends FileBasedConfigurationBuilder<T>
  52.     implements ReloadingControllerSupport {
  53.     /** The default factory for creating reloading detector objects. */
  54.     private static final ReloadingDetectorFactory DEFAULT_DETECTOR_FACTORY = new DefaultReloadingDetectorFactory();

  55.     /**
  56.      * Returns a {@code ReloadingDetectorFactory} either from the passed in parameters or a default factory.
  57.      *
  58.      * @param params the current parameters object
  59.      * @return the {@code ReloadingDetectorFactory} to be used
  60.      */
  61.     private static ReloadingDetectorFactory fetchDetectorFactory(final FileBasedBuilderParametersImpl params) {
  62.         final ReloadingDetectorFactory factory = params.getReloadingDetectorFactory();
  63.         return factory != null ? factory : DEFAULT_DETECTOR_FACTORY;
  64.     }

  65.     /** The reloading controller associated with this object. */
  66.     private final ReloadingController reloadingController;

  67.     /**
  68.      * The reloading detector which does the actual reload check for the current result object. A new instance is created
  69.      * whenever a new result object (and thus a new current file handler) becomes available. The field must be volatile
  70.      * because it is accessed by the reloading controller probably from within another thread.
  71.      */
  72.     private volatile ReloadingDetector resultReloadingDetector;

  73.     /**
  74.      * Creates a new instance of {@code ReloadingFileBasedConfigurationBuilder} which produces result objects of the
  75.      * specified class.
  76.      *
  77.      * @param resCls the result class (must not be <strong>null</strong>
  78.      * @throws IllegalArgumentException if the result class is <strong>null</strong>
  79.      */
  80.     public ReloadingFileBasedConfigurationBuilder(final Class<? extends T> resCls) {
  81.         super(resCls);
  82.         reloadingController = createReloadingController();
  83.     }

  84.     /**
  85.      * Creates a new instance of {@code ReloadingFileBasedConfigurationBuilder} which produces result objects of the
  86.      * specified class and sets initialization parameters.
  87.      *
  88.      * @param resCls the result class (must not be <strong>null</strong>
  89.      * @param params a map with initialization parameters
  90.      * @throws IllegalArgumentException if the result class is <strong>null</strong>
  91.      */
  92.     public ReloadingFileBasedConfigurationBuilder(final Class<? extends T> resCls, final Map<String, Object> params) {
  93.         super(resCls, params);
  94.         reloadingController = createReloadingController();
  95.     }

  96.     /**
  97.      * Creates a new instance of {@code ReloadingFileBasedConfigurationBuilder} which produces result objects of the
  98.      * specified class and sets initialization parameters and the <em>allowFailOnInit</em> flag.
  99.      *
  100.      * @param resCls the result class (must not be <strong>null</strong>
  101.      * @param params a map with initialization parameters
  102.      * @param allowFailOnInit the <em>allowFailOnInit</em> flag
  103.      * @throws IllegalArgumentException if the result class is <strong>null</strong>
  104.      */
  105.     public ReloadingFileBasedConfigurationBuilder(final Class<? extends T> resCls, final Map<String, Object> params, final boolean allowFailOnInit) {
  106.         super(resCls, params, allowFailOnInit);
  107.         reloadingController = createReloadingController();
  108.     }

  109.     /**
  110.      * {@inheritDoc} This method is overridden here to change the result type.
  111.      */
  112.     @Override
  113.     public ReloadingFileBasedConfigurationBuilder<T> configure(final BuilderParameters... params) {
  114.         super.configure(params);
  115.         return this;
  116.     }

  117.     /**
  118.      * Creates the {@code ReloadingController} associated with this object. The controller is assigned a specialized
  119.      * reloading detector which delegates to the detector for the current result object. (
  120.      * {@code FileHandlerReloadingDetector} does not support changing the file handler, and {@code ReloadingController} does
  121.      * not support changing the reloading detector; therefore, this level of indirection is needed to change the monitored
  122.      * file dynamically.)
  123.      *
  124.      * @return the new {@code ReloadingController}
  125.      */
  126.     private ReloadingController createReloadingController() {
  127.         final ReloadingDetector ctrlDetector = createReloadingDetectorForController();
  128.         final ReloadingController ctrl = new ReloadingController(ctrlDetector);
  129.         connectToReloadingController(ctrl);
  130.         return ctrl;
  131.     }

  132.     /**
  133.      * Creates a {@code ReloadingDetector} which monitors the passed in {@code FileHandler}. This method is called each time
  134.      * a new result object is created with the current {@code FileHandler}. This implementation checks whether a
  135.      * {@code ReloadingDetectorFactory} is specified in the current parameters. If this is the case, it is invoked.
  136.      * Otherwise, a default factory is used to create a {@code FileHandlerReloadingDetector} object. Note: This method is
  137.      * called from a synchronized block.
  138.      *
  139.      * @param handler the current {@code FileHandler}
  140.      * @param fbparams the object with parameters related to file-based builders
  141.      * @return a {@code ReloadingDetector} for this {@code FileHandler}
  142.      * @throws ConfigurationException if an error occurs
  143.      */
  144.     protected ReloadingDetector createReloadingDetector(final FileHandler handler, final FileBasedBuilderParametersImpl fbparams)
  145.         throws ConfigurationException {
  146.         return fetchDetectorFactory(fbparams).createReloadingDetector(handler, fbparams);
  147.     }

  148.     /**
  149.      * Creates a {@code ReloadingDetector} wrapper to be passed to the associated {@code ReloadingController}. This detector
  150.      * wrapper simply delegates to the current {@code ReloadingDetector} if it is available.
  151.      *
  152.      * @return the wrapper {@code ReloadingDetector}
  153.      */
  154.     private ReloadingDetector createReloadingDetectorForController() {
  155.         return new ReloadingDetector() {
  156.             @Override
  157.             public boolean isReloadingRequired() {
  158.                 final ReloadingDetector detector = resultReloadingDetector;
  159.                 return detector != null && detector.isReloadingRequired();
  160.             }

  161.             @Override
  162.             public void reloadingPerformed() {
  163.                 final ReloadingDetector detector = resultReloadingDetector;
  164.                 if (detector != null) {
  165.                     detector.reloadingPerformed();
  166.                 }
  167.             }
  168.         };
  169.     }

  170.     /**
  171.      * Gets the {@code ReloadingController} associated with this builder. This controller is directly created. However,
  172.      * it becomes active (i.e. associated with a meaningful reloading detector) not before a result object was created.
  173.      *
  174.      * @return the {@code ReloadingController}
  175.      */
  176.     @Override
  177.     public ReloadingController getReloadingController() {
  178.         return reloadingController;
  179.     }

  180.     /**
  181.      * {@inheritDoc} This implementation also takes care that a new {@code ReloadingDetector} for the new current
  182.      * {@code FileHandler} is created. Also, the reloading controller's reloading state has to be reset; after the creation
  183.      * of a new result object changes in the underlying configuration source have to be monitored again.
  184.      */
  185.     @Override
  186.     protected void initFileHandler(final FileHandler handler) throws ConfigurationException {
  187.         super.initFileHandler(handler);

  188.         resultReloadingDetector = createReloadingDetector(handler, FileBasedBuilderParametersImpl.fromParameters(getParameters(), true));
  189.     }
  190. }