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}