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 *     http://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 (e.g. 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 <b>true</b>, the result of this method is <b>true</b>, otherwise <b>false</b>.
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 <b>null</b> or contains <b>null</b> 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 <b>null</b> or contain <b>null</b>
121     *        entries)
122     * @throws IllegalArgumentException if the passed in collection is <b>null</b> or contains <b>null</b> 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}