    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     https://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.configuration2.reloading;
    018
    019import java.util.ArrayList;
    020import java.util.Collection;
    021import java.util.Collections;
    022import java.util.Objects;
    023
    024/**
    025 * <p>
    026 * A specialized {@code ReloadingController} implementation which manages an arbitrary number of other
    027 * {@code ReloadingController} objects.
    028 * </p>
    029 * <p>
    030 * This class can be used to handle multiple simple controllers for reload operations as a single object. As a usage
    031 * example consider a combined configuration containing a number of configuration sources of which some support
    032 * reloading. In this scenario all {@code ReloadingController} instances for the reloading-enabled sources can be added
    033 * to a {@code CombinedReloadingController}. Then by triggering the combined controller a reload check is performed on
    034 * all child sources.
    035 * </p>
    036 * <p>
    037 * This class is a typical implementation of the <em>composite pattern</em>. An instance is constructed with a
    038 * collection of sub {@code ReloadingController} objects. Its operations are implemented by delegating to all child
    039 * controllers.
    040 * </p>
    041 * <p>
    042 * This class expects the managed controller objects to be passed to the constructor. From this list a defensive copy is
    043 * created so that it cannot be changed later on. Derived classes can override the {@link #getSubControllers()} method
    044 * if they need another way to handle child controllers (for example a more dynamic way). However, they are then responsible to
    045 * ensure a safe access to this list in a multi-threaded environment.
    046 * </p>
    047 *
    048 * @since 2.0
    049 */
    050public class CombinedReloadingController extends ReloadingController {
    051
    052    /**
    053     * A specialized implementation of the {@code ReloadingDetector} interface which operates on a collection of
    054     * {@code ReloadingController} objects. The methods defined by the {@code ReloadingDetector} interface are delegated to
    055     * the managed controllers.
    056     */
    057    private static final class MultiReloadingControllerDetector implements ReloadingDetector {
    058
    059        /** A reference to the owning combined reloading controller. */
    060        private final CombinedReloadingController owner;
    061
    062        /**
    063         * Creates a new instance of {@code MultiReloadingControllerDetector}.
    064         *
    065         * @param owner the owner
    066         */
    067        public MultiReloadingControllerDetector(final CombinedReloadingController owner) {
    068            this.owner = owner;
    069        }
    070
    071        /**
    072         * {@inheritDoc} This implementation delegates to the managed controllers. For all of them the
    073         * {@code checkForReloading()} method is called, giving them the chance to trigger a reload if necessary. If one of
    074         * these calls returns <strong>true</strong>, the result of this method is <strong>true</strong>, otherwise <strong>false</strong>.
    075         */
    076        @Override
    077        public boolean isReloadingRequired() {
    078            return owner.getSubControllers().stream().reduce(false, (b, rc) -> b | rc.checkForReloading(null), (t, u) -> t | u);
    079        }
    080
    081        /**
    082         * {@inheritDoc} This implementation resets the reloading state on all managed controllers.
    083         */
    084        @Override
    085        public void reloadingPerformed() {
    086            owner.getSubControllers().forEach(ReloadingController::resetReloadingState);
    087        }
    088    }
    089
    090    /** Constant for a dummy reloading detector. */
    091    private static final ReloadingDetector DUMMY = new MultiReloadingControllerDetector(null);
    092
    093    /**
    094     * Checks the collection with the passed in sub controllers and creates a defensive copy.
    095     *
    096     * @param subCtrls the collection with sub controllers
    097     * @return a copy of the collection to be stored in the newly created instance
    098     * @throws IllegalArgumentException if the passed in collection is <strong>null</strong> or contains <strong>null</strong> entries
    099     */
    100    private static Collection<ReloadingController> checkManagedControllers(final Collection<? extends ReloadingController> subCtrls) {
    101        if (subCtrls == null) {
    102            throw new IllegalArgumentException("Collection with sub controllers must not be null!");
    103        }
    104        final Collection<ReloadingController> ctrls = new ArrayList<>(subCtrls);
    105        if (ctrls.stream().anyMatch(Objects::isNull)) {
    106            throw new IllegalArgumentException("Collection with sub controllers contains a null entry!");
    107        }
    108
    109        return Collections.unmodifiableCollection(ctrls);
    110    }
    111
    112    /** The collection with managed reloading controllers. */
    113    private final Collection<ReloadingController> controllers;
    114
    115    /** The reloading detector used by this instance. */
    116    private final ReloadingDetector detector;
    117
    118    /**
    119     * Creates a new instance of {@code CombinedReloadingController} and initializes it with the {@code ReloadingController}
    120     * objects to be managed.
    121     *
    122     * @param subCtrls the collection with sub {@code ReloadingController}s (must not be <strong>null</strong> or contain <strong>null</strong>
    123     *        entries)
    124     * @throws IllegalArgumentException if the passed in collection is <strong>null</strong> or contains <strong>null</strong> entries
    125     */
    126    public CombinedReloadingController(final Collection<? extends ReloadingController> subCtrls) {
    127        super(DUMMY);
    128        controllers = checkManagedControllers(subCtrls);
    129        detector = new MultiReloadingControllerDetector(this);
    130    }
    131
    132    /**
    133     * {@inheritDoc} This implementation returns a special reloading detector which operates on all managed controllers.
    134     */
    135    @Override
    136    public ReloadingDetector getDetector() {
    137        return detector;
    138    }
    139
    140    /**
    141     * Gets a (unmodifiable) collection with the sub controllers managed by this combined controller.
    142     *
    143     * @return a collection with sub controllers
    144     */
    145    public Collection<ReloadingController> getSubControllers() {
    146        return controllers;
    147    }
    148
    149    /**
    150     * Resets the reloading state of all managed sub controllers unconditionally. This method is intended to be called after
    151     * the creation of an instance. It may be the case that some of the sub controllers are already in reloading state, so
    152     * their state is out of sync with this controller's global reloading state. This method ensures that the reloading
    153     * state of all sub controllers is reset.
    154     */
    155    public void resetInitialReloadingState() {
    156        getDetector().reloadingPerformed();
    157    }
    158}