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 * https://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.reloading;
18
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.Objects;
23
24 /**
25 * <p>
26 * A specialized {@code ReloadingController} implementation which manages an arbitrary number of other
27 * {@code ReloadingController} objects.
28 * </p>
29 * <p>
30 * This class can be used to handle multiple simple controllers for reload operations as a single object. As a usage
31 * example consider a combined configuration containing a number of configuration sources of which some support
32 * reloading. In this scenario all {@code ReloadingController} instances for the reloading-enabled sources can be added
33 * to a {@code CombinedReloadingController}. Then by triggering the combined controller a reload check is performed on
34 * all child sources.
35 * </p>
36 * <p>
37 * This class is a typical implementation of the <em>composite pattern</em>. An instance is constructed with a
38 * collection of sub {@code ReloadingController} objects. Its operations are implemented by delegating to all child
39 * controllers.
40 * </p>
41 * <p>
42 * This class expects the managed controller objects to be passed to the constructor. From this list a defensive copy is
43 * created so that it cannot be changed later on. Derived classes can override the {@link #getSubControllers()} method
44 * if they need another way to handle child controllers (for example a more dynamic way). However, they are then responsible to
45 * ensure a safe access to this list in a multi-threaded environment.
46 * </p>
47 *
48 * @since 2.0
49 */
50 public class CombinedReloadingController extends ReloadingController {
51
52 /**
53 * A specialized implementation of the {@code ReloadingDetector} interface which operates on a collection of
54 * {@code ReloadingController} objects. The methods defined by the {@code ReloadingDetector} interface are delegated to
55 * the managed controllers.
56 */
57 private static final class MultiReloadingControllerDetector implements ReloadingDetector {
58
59 /** A reference to the owning combined reloading controller. */
60 private final CombinedReloadingController owner;
61
62 /**
63 * Creates a new instance of {@code MultiReloadingControllerDetector}.
64 *
65 * @param owner the owner
66 */
67 public MultiReloadingControllerDetector(final CombinedReloadingController owner) {
68 this.owner = owner;
69 }
70
71 /**
72 * {@inheritDoc} This implementation delegates to the managed controllers. For all of them the
73 * {@code checkForReloading()} method is called, giving them the chance to trigger a reload if necessary. If one of
74 * these calls returns <strong>true</strong>, the result of this method is <strong>true</strong>, otherwise <strong>false</strong>.
75 */
76 @Override
77 public boolean isReloadingRequired() {
78 return owner.getSubControllers().stream().reduce(false, (b, rc) -> b | rc.checkForReloading(null), (t, u) -> t | u);
79 }
80
81 /**
82 * {@inheritDoc} This implementation resets the reloading state on all managed controllers.
83 */
84 @Override
85 public void reloadingPerformed() {
86 owner.getSubControllers().forEach(ReloadingController::resetReloadingState);
87 }
88 }
89
90 /** Constant for a dummy reloading detector. */
91 private static final ReloadingDetector DUMMY = new MultiReloadingControllerDetector(null);
92
93 /**
94 * Checks the collection with the passed in sub controllers and creates a defensive copy.
95 *
96 * @param subCtrls the collection with sub controllers
97 * @return a copy of the collection to be stored in the newly created instance
98 * @throws IllegalArgumentException if the passed in collection is <strong>null</strong> or contains <strong>null</strong> entries
99 */
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 }