    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     * A specialized implementation of the {@code ReloadingDetector} interface which operates on a collection of
    053     * {@code ReloadingController} objects. The methods defined by the {@code ReloadingDetector} interface are delegated to
    054     * the managed controllers.
    055     */
    056    private static final class MultiReloadingControllerDetector implements ReloadingDetector {
    057        /** A reference to the owning combined reloading controller. */
    058        private final CombinedReloadingController owner;
    059
    060        /**
    061         * Creates a new instance of {@code MultiReloadingControllerDetector}.
    062         *
    063         * @param owner the owner
    064         */
    065        public MultiReloadingControllerDetector(final CombinedReloadingController owner) {
    066            this.owner = owner;
    067        }
    068
    069        /**
    070         * {@inheritDoc} This implementation delegates to the managed controllers. For all of them the
    071         * {@code checkForReloading()} method is called, giving them the chance to trigger a reload if necessary. If one of
    072         * these calls returns <strong>true</strong>, the result of this method is <strong>true</strong>, otherwise <strong>false</strong>.
    073         */
    074        @Override
    075        public boolean isReloadingRequired() {
    076            return owner.getSubControllers().stream().reduce(false, (b, rc) -> b | rc.checkForReloading(null), (t, u) -> t | u);
    077        }
    078
    079        /**
    080         * {@inheritDoc} This implementation resets the reloading state on all managed controllers.
    081         */
    082        @Override
    083        public void reloadingPerformed() {
    084            owner.getSubControllers().forEach(ReloadingController::resetReloadingState);
    085        }
    086    }
    087
    088    /** Constant for a dummy reloading detector. */
    089    private static final ReloadingDetector DUMMY = new MultiReloadingControllerDetector(null);
    090
    091    /**
    092     * Checks the collection with the passed in sub controllers and creates a defensive copy.
    093     *
    094     * @param subCtrls the collection with sub controllers
    095     * @return a copy of the collection to be stored in the newly created instance
    096     * @throws IllegalArgumentException if the passed in collection is <strong>null</strong> or contains <strong>null</strong> entries
    097     */
    098    private static Collection<ReloadingController> checkManagedControllers(final Collection<? extends ReloadingController> subCtrls) {
    099        if (subCtrls == null) {
    100            throw new IllegalArgumentException("Collection with sub controllers must not be null!");
    101        }
    102        final Collection<ReloadingController> ctrls = new ArrayList<>(subCtrls);
    103        if (ctrls.stream().anyMatch(Objects::isNull)) {
    104            throw new IllegalArgumentException("Collection with sub controllers contains a null entry!");
    105        }
    106
    107        return Collections.unmodifiableCollection(ctrls);
    108    }
    109
    110    /** The collection with managed reloading controllers. */
    111    private final Collection<ReloadingController> controllers;
    112
    113    /** The reloading detector used by this instance. */
    114    private final ReloadingDetector detector;
    115
    116    /**
    117     * Creates a new instance of {@code CombinedReloadingController} and initializes it with the {@code ReloadingController}
    118     * objects to be managed.
    119     *
    120     * @param subCtrls the collection with sub {@code ReloadingController}s (must not be <strong>null</strong> or contain <strong>null</strong>
    121     *        entries)
    122     * @throws IllegalArgumentException if the passed in collection is <strong>null</strong> or contains <strong>null</strong> entries
    123     */
    124    public CombinedReloadingController(final Collection<? extends ReloadingController> subCtrls) {
    125        super(DUMMY);
    126        controllers = checkManagedControllers(subCtrls);
    127        detector = new MultiReloadingControllerDetector(this);
    128    }
    129
    130    /**
    131     * {@inheritDoc} This implementation returns a special reloading detector which operates on all managed controllers.
    132     */
    133    @Override
    134    public ReloadingDetector getDetector() {
    135        return detector;
    136    }
    137
    138    /**
    139     * Gets a (unmodifiable) collection with the sub controllers managed by this combined controller.
    140     *
    141     * @return a collection with sub controllers
    142     */
    143    public Collection<ReloadingController> getSubControllers() {
    144        return controllers;
    145    }
    146
    147    /**
    148     * Resets the reloading state of all managed sub controllers unconditionally. This method is intended to be called after
    149     * the creation of an instance. It may be the case that some of the sub controllers are already in reloading state, so
    150     * their state is out of sync with this controller's global reloading state. This method ensures that the reloading
    151     * state of all sub controllers is reset.
    152     */
    153    public void resetInitialReloadingState() {
    154        getDetector().reloadingPerformed();
    155    }
    156}