View Javadoc
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    *      http://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.lang3.concurrent;
18  
19  import java.util.Collections;
20  import java.util.HashMap;
21  import java.util.Map;
22  import java.util.NoSuchElementException;
23  import java.util.Set;
24  import java.util.concurrent.ExecutorService;
25  
26  import org.apache.commons.lang3.Validate;
27  
28  /**
29   * <p>
30   * A specialized {@link BackgroundInitializer} implementation that can deal with
31   * multiple background initialization tasks.
32   * </p>
33   * <p>
34   * This class has a similar purpose as {@link BackgroundInitializer}. However,
35   * it is not limited to a single background initialization task. Rather it
36   * manages an arbitrary number of {@code BackgroundInitializer} objects,
37   * executes them, and waits until they are completely initialized. This is
38   * useful for applications that have to perform multiple initialization tasks
39   * that can run in parallel (i.e. that do not depend on each other). This class
40   * takes care about the management of an {@code ExecutorService} and shares it
41   * with the {@code BackgroundInitializer} objects it is responsible for; so the
42   * using application need not bother with these details.
43   * </p>
44   * <p>
45   * The typical usage scenario for {@code MultiBackgroundInitializer} is as
46   * follows:
47   * </p>
48   * <ul>
49   * <li>Create a new instance of the class. Optionally pass in a pre-configured
50   * {@code ExecutorService}. Alternatively {@code MultiBackgroundInitializer} can
51   * create a temporary {@code ExecutorService} and delete it after initialization
52   * is complete.</li>
53   * <li>Create specialized {@link BackgroundInitializer} objects for the
54   * initialization tasks to be performed and add them to the {@code
55   * MultiBackgroundInitializer} using the
56   * {@link #addInitializer(String, BackgroundInitializer)} method.</li>
57   * <li>After all initializers have been added, call the {@link #start()} method.
58   * </li>
59   * <li>When access to the result objects produced by the {@code
60   * BackgroundInitializer} objects is needed call the {@link #get()} method. The
61   * object returned here provides access to all result objects created during
62   * initialization. It also stores information about exceptions that have
63   * occurred.</li>
64   * </ul>
65   * <p>
66   * {@code MultiBackgroundInitializer} starts a special controller task that
67   * starts all {@code BackgroundInitializer} objects added to the instance.
68   * Before the an initializer is started it is checked whether this initializer
69   * already has an {@code ExecutorService} set. If this is the case, this {@code
70   * ExecutorService} is used for running the background task. Otherwise the
71   * current {@code ExecutorService} of this {@code MultiBackgroundInitializer} is
72   * shared with the initializer.
73   * </p>
74   * <p>
75   * The easiest way of using this class is to let it deal with the management of
76   * an {@code ExecutorService} itself: If no external {@code ExecutorService} is
77   * provided, the class creates a temporary {@code ExecutorService} (that is
78   * capable of executing all background tasks in parallel) and destroys it at the
79   * end of background processing.
80   * </p>
81   * <p>
82   * Alternatively an external {@code ExecutorService} can be provided - either at
83   * construction time or later by calling the
84   * {@link #setExternalExecutor(ExecutorService)} method. In this case all
85   * background tasks are scheduled at this external {@code ExecutorService}.
86   * <strong>Important note:</strong> When using an external {@code
87   * ExecutorService} be sure that the number of threads managed by the service is
88   * large enough. Otherwise a deadlock can happen! This is the case in the
89   * following scenario: {@code MultiBackgroundInitializer} starts a task that
90   * starts all registered {@code BackgroundInitializer} objects and waits for
91   * their completion. If for instance a single threaded {@code ExecutorService}
92   * is used, none of the background tasks can be executed, and the task created
93   * by {@code MultiBackgroundInitializer} waits forever.
94   * </p>
95   *
96   * @since 3.0
97   */
98  public class MultiBackgroundInitializer
99          extends
100         BackgroundInitializer<MultiBackgroundInitializer.MultiBackgroundInitializerResults> {
101     /** A map with the child initializers. */
102     private final Map<String, BackgroundInitializer<?>> childInitializers =
103         new HashMap<>();
104 
105     /**
106      * Creates a new instance of {@code MultiBackgroundInitializer}.
107      */
108     public MultiBackgroundInitializer() {
109         super();
110     }
111 
112     /**
113      * Creates a new instance of {@code MultiBackgroundInitializer} and
114      * initializes it with the given external {@code ExecutorService}.
115      *
116      * @param exec the {@code ExecutorService} for executing the background
117      * tasks
118      */
119     public MultiBackgroundInitializer(final ExecutorService exec) {
120         super(exec);
121     }
122 
123     /**
124      * Adds a new {@code BackgroundInitializer} to this object. When this
125      * {@code MultiBackgroundInitializer} is started, the given initializer will
126      * be processed. This method must not be called after {@link #start()} has
127      * been invoked.
128      *
129      * @param name the name of the initializer (must not be <b>null</b>)
130      * @param init the {@code BackgroundInitializer} to add (must not be
131      * <b>null</b>)
132      * @throws IllegalArgumentException if a required parameter is missing
133      * @throws IllegalStateException if {@code start()} has already been called
134      */
135     public void addInitializer(final String name, final BackgroundInitializer<?> init) {
136         Validate.isTrue(name != null, "Name of child initializer must not be null!");
137         Validate.isTrue(init != null, "Child initializer must not be null!");
138 
139         synchronized (this) {
140             if (isStarted()) {
141                 throw new IllegalStateException(
142                         "addInitializer() must not be called after start()!");
143             }
144             childInitializers.put(name, init);
145         }
146     }
147 
148     /**
149      * Returns the number of tasks needed for executing all child {@code
150      * BackgroundInitializer} objects in parallel. This implementation sums up
151      * the required tasks for all child initializers (which is necessary if one
152      * of the child initializers is itself a {@code MultiBackgroundInitializer}
153      * ). Then it adds 1 for the control task that waits for the completion of
154      * the children.
155      *
156      * @return the number of tasks required for background processing
157      */
158     @Override
159     protected int getTaskCount() {
160         int result = 1;
161 
162         for (final BackgroundInitializer<?> bi : childInitializers.values()) {
163             result += bi.getTaskCount();
164         }
165 
166         return result;
167     }
168 
169     /**
170      * Creates the results object. This implementation starts all child {@code
171      * BackgroundInitializer} objects. Then it collects their results and
172      * creates a {@code MultiBackgroundInitializerResults} object with this
173      * data. If a child initializer throws a checked exceptions, it is added to
174      * the results object. Unchecked exceptions are propagated.
175      *
176      * @return the results object
177      * @throws Exception if an error occurs
178      */
179     @Override
180     protected MultiBackgroundInitializerResults initialize() throws Exception {
181         Map<String, BackgroundInitializer<?>> inits;
182         synchronized (this) {
183             // create a snapshot to operate on
184             inits = new HashMap<>(
185                     childInitializers);
186         }
187 
188         // start the child initializers
189         final ExecutorService exec = getActiveExecutor();
190         for (final BackgroundInitializer<?> bi : inits.values()) {
191             if (bi.getExternalExecutor() == null) {
192                 // share the executor service if necessary
193                 bi.setExternalExecutor(exec);
194             }
195             bi.start();
196         }
197 
198         // collect the results
199         final Map<String, Object> results = new HashMap<>();
200         final Map<String, ConcurrentException> excepts = new HashMap<>();
201         for (final Map.Entry<String, BackgroundInitializer<?>> e : inits.entrySet()) {
202             try {
203                 results.put(e.getKey(), e.getValue().get());
204             } catch (final ConcurrentException cex) {
205                 excepts.put(e.getKey(), cex);
206             }
207         }
208 
209         return new MultiBackgroundInitializerResults(inits, results, excepts);
210     }
211 
212     /**
213      * A data class for storing the results of the background initialization
214      * performed by {@code MultiBackgroundInitializer}. Objects of this inner
215      * class are returned by {@link MultiBackgroundInitializer#initialize()}.
216      * They allow access to all result objects produced by the
217      * {@link BackgroundInitializer} objects managed by the owning instance. It
218      * is also possible to retrieve status information about single
219      * {@link BackgroundInitializer}s, i.e. whether they completed normally or
220      * caused an exception.
221      */
222     public static class MultiBackgroundInitializerResults {
223         /** A map with the child initializers. */
224         private final Map<String, BackgroundInitializer<?>> initializers;
225 
226         /** A map with the result objects. */
227         private final Map<String, Object> resultObjects;
228 
229         /** A map with the exceptions. */
230         private final Map<String, ConcurrentException> exceptions;
231 
232         /**
233          * Creates a new instance of {@code MultiBackgroundInitializerResults}
234          * and initializes it with maps for the {@code BackgroundInitializer}
235          * objects, their result objects and the exceptions thrown by them.
236          *
237          * @param inits the {@code BackgroundInitializer} objects
238          * @param results the result objects
239          * @param excepts the exceptions
240          */
241         private MultiBackgroundInitializerResults(
242                 final Map<String, BackgroundInitializer<?>> inits,
243                 final Map<String, Object> results,
244                 final Map<String, ConcurrentException> excepts) {
245             initializers = inits;
246             resultObjects = results;
247             exceptions = excepts;
248         }
249 
250         /**
251          * Returns the {@code BackgroundInitializer} with the given name. If the
252          * name cannot be resolved, an exception is thrown.
253          *
254          * @param name the name of the {@code BackgroundInitializer}
255          * @return the {@code BackgroundInitializer} with this name
256          * @throws NoSuchElementException if the name cannot be resolved
257          */
258         public BackgroundInitializer<?> getInitializer(final String name) {
259             return checkName(name);
260         }
261 
262         /**
263          * Returns the result object produced by the {@code
264          * BackgroundInitializer} with the given name. This is the object
265          * returned by the initializer's {@code initialize()} method. If this
266          * {@code BackgroundInitializer} caused an exception, <b>null</b> is
267          * returned. If the name cannot be resolved, an exception is thrown.
268          *
269          * @param name the name of the {@code BackgroundInitializer}
270          * @return the result object produced by this {@code
271          * BackgroundInitializer}
272          * @throws NoSuchElementException if the name cannot be resolved
273          */
274         public Object getResultObject(final String name) {
275             checkName(name);
276             return resultObjects.get(name);
277         }
278 
279         /**
280          * Returns a flag whether the {@code BackgroundInitializer} with the
281          * given name caused an exception.
282          *
283          * @param name the name of the {@code BackgroundInitializer}
284          * @return a flag whether this initializer caused an exception
285          * @throws NoSuchElementException if the name cannot be resolved
286          */
287         public boolean isException(final String name) {
288             checkName(name);
289             return exceptions.containsKey(name);
290         }
291 
292         /**
293          * Returns the {@code ConcurrentException} object that was thrown by the
294          * {@code BackgroundInitializer} with the given name. If this
295          * initializer did not throw an exception, the return value is
296          * <b>null</b>. If the name cannot be resolved, an exception is thrown.
297          *
298          * @param name the name of the {@code BackgroundInitializer}
299          * @return the exception thrown by this initializer
300          * @throws NoSuchElementException if the name cannot be resolved
301          */
302         public ConcurrentException getException(final String name) {
303             checkName(name);
304             return exceptions.get(name);
305         }
306 
307         /**
308          * Returns a set with the names of all {@code BackgroundInitializer}
309          * objects managed by the {@code MultiBackgroundInitializer}.
310          *
311          * @return an (unmodifiable) set with the names of the managed {@code
312          * BackgroundInitializer} objects
313          */
314         public Set<String> initializerNames() {
315             return Collections.unmodifiableSet(initializers.keySet());
316         }
317 
318         /**
319          * Returns a flag whether the whole initialization was successful. This
320          * is the case if no child initializer has thrown an exception.
321          *
322          * @return a flag whether the initialization was successful
323          */
324         public boolean isSuccessful() {
325             return exceptions.isEmpty();
326         }
327 
328         /**
329          * Checks whether an initializer with the given name exists. If not,
330          * throws an exception. If it exists, the associated child initializer
331          * is returned.
332          *
333          * @param name the name to check
334          * @return the initializer with this name
335          * @throws NoSuchElementException if the name is unknown
336          */
337         private BackgroundInitializer<?> checkName(final String name) {
338             final BackgroundInitializer<?> init = initializers.get(name);
339             if (init == null) {
340                 throw new NoSuchElementException(
341                         "No child initializer with name " + name);
342             }
343 
344             return init;
345         }
346     }
347 }