/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.lang3.concurrent;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import org.apache.commons.lang3.Validate;
A specialized BackgroundInitializer
implementation that can deal with multiple background initialization tasks.
This class has a similar purpose as BackgroundInitializer
. However, it is not limited to a single background initialization task. Rather it manages an arbitrary number of BackgroundInitializer
objects, executes them, and waits until they are completely initialized. This is useful for applications that have to perform multiple initialization tasks that can run in parallel (i.e. that do not depend on each other). This class takes care about the management of an ExecutorService
and shares it with the BackgroundInitializer
objects it is responsible for; so the using application need not bother with these details.
The typical usage scenario for MultiBackgroundInitializer
is as follows:
- Create a new instance of the class. Optionally pass in a pre-configured
ExecutorService
. Alternatively MultiBackgroundInitializer
can create a temporary ExecutorService
and delete it after initialization is complete.
- Create specialized
BackgroundInitializer
objects for the initialization tasks to be performed and add them to the
MultiBackgroundInitializer
using the addInitializer(String, BackgroundInitializer<?>)
method.
- After all initializers have been added, call the
BackgroundInitializer<MultiBackgroundInitializerResults>.start()
method.
- When access to the result objects produced by the
BackgroundInitializer
objects is needed call the BackgroundInitializer<MultiBackgroundInitializerResults>.get()
method. The object returned here provides access to all result objects created during initialization. It also stores information about exceptions that have occurred.
MultiBackgroundInitializer
starts a special controller task that starts all BackgroundInitializer
objects added to the instance. Before the an initializer is started it is checked whether this initializer already has an ExecutorService
set. If this is the case, this
ExecutorService
is used for running the background task. Otherwise the current ExecutorService
of this MultiBackgroundInitializer
is shared with the initializer.
The easiest way of using this class is to let it deal with the management of an ExecutorService
itself: If no external ExecutorService
is provided, the class creates a temporary ExecutorService
(that is capable of executing all background tasks in parallel) and destroys it at the end of background processing.
Alternatively an external ExecutorService
can be provided - either at construction time or later by calling the BackgroundInitializer<MultiBackgroundInitializerResults>.setExternalExecutor(ExecutorService)
method. In this case all background tasks are scheduled at this external ExecutorService
. Important note: When using an external
ExecutorService
be sure that the number of threads managed by the service is large enough. Otherwise a deadlock can happen! This is the case in the following scenario: MultiBackgroundInitializer
starts a task that starts all registered BackgroundInitializer
objects and waits for their completion. If for instance a single threaded ExecutorService
is used, none of the background tasks can be executed, and the task created by MultiBackgroundInitializer
waits forever.
Since: 3.0
/**
* <p>
* A specialized {@link BackgroundInitializer} implementation that can deal with
* multiple background initialization tasks.
* </p>
* <p>
* This class has a similar purpose as {@link BackgroundInitializer}. However,
* it is not limited to a single background initialization task. Rather it
* manages an arbitrary number of {@code BackgroundInitializer} objects,
* executes them, and waits until they are completely initialized. This is
* useful for applications that have to perform multiple initialization tasks
* that can run in parallel (i.e. that do not depend on each other). This class
* takes care about the management of an {@code ExecutorService} and shares it
* with the {@code BackgroundInitializer} objects it is responsible for; so the
* using application need not bother with these details.
* </p>
* <p>
* The typical usage scenario for {@code MultiBackgroundInitializer} is as
* follows:
* </p>
* <ul>
* <li>Create a new instance of the class. Optionally pass in a pre-configured
* {@code ExecutorService}. Alternatively {@code MultiBackgroundInitializer} can
* create a temporary {@code ExecutorService} and delete it after initialization
* is complete.</li>
* <li>Create specialized {@link BackgroundInitializer} objects for the
* initialization tasks to be performed and add them to the {@code
* MultiBackgroundInitializer} using the
* {@link #addInitializer(String, BackgroundInitializer)} method.</li>
* <li>After all initializers have been added, call the {@link #start()} method.
* </li>
* <li>When access to the result objects produced by the {@code
* BackgroundInitializer} objects is needed call the {@link #get()} method. The
* object returned here provides access to all result objects created during
* initialization. It also stores information about exceptions that have
* occurred.</li>
* </ul>
* <p>
* {@code MultiBackgroundInitializer} starts a special controller task that
* starts all {@code BackgroundInitializer} objects added to the instance.
* Before the an initializer is started it is checked whether this initializer
* already has an {@code ExecutorService} set. If this is the case, this {@code
* ExecutorService} is used for running the background task. Otherwise the
* current {@code ExecutorService} of this {@code MultiBackgroundInitializer} is
* shared with the initializer.
* </p>
* <p>
* The easiest way of using this class is to let it deal with the management of
* an {@code ExecutorService} itself: If no external {@code ExecutorService} is
* provided, the class creates a temporary {@code ExecutorService} (that is
* capable of executing all background tasks in parallel) and destroys it at the
* end of background processing.
* </p>
* <p>
* Alternatively an external {@code ExecutorService} can be provided - either at
* construction time or later by calling the
* {@link #setExternalExecutor(ExecutorService)} method. In this case all
* background tasks are scheduled at this external {@code ExecutorService}.
* <strong>Important note:</strong> When using an external {@code
* ExecutorService} be sure that the number of threads managed by the service is
* large enough. Otherwise a deadlock can happen! This is the case in the
* following scenario: {@code MultiBackgroundInitializer} starts a task that
* starts all registered {@code BackgroundInitializer} objects and waits for
* their completion. If for instance a single threaded {@code ExecutorService}
* is used, none of the background tasks can be executed, and the task created
* by {@code MultiBackgroundInitializer} waits forever.
* </p>
*
* @since 3.0
*/
public class MultiBackgroundInitializer
extends
BackgroundInitializer<MultiBackgroundInitializer.MultiBackgroundInitializerResults> {
A map with the child initializers. /** A map with the child initializers. */
private final Map<String, BackgroundInitializer<?>> childInitializers =
new HashMap<>();
Creates a new instance of MultiBackgroundInitializer
. /**
* Creates a new instance of {@code MultiBackgroundInitializer}.
*/
public MultiBackgroundInitializer() {
super();
}
Creates a new instance of MultiBackgroundInitializer
and initializes it with the given external ExecutorService
. Params: - exec – the
ExecutorService
for executing the background tasks
/**
* Creates a new instance of {@code MultiBackgroundInitializer} and
* initializes it with the given external {@code ExecutorService}.
*
* @param exec the {@code ExecutorService} for executing the background
* tasks
*/
public MultiBackgroundInitializer(final ExecutorService exec) {
super(exec);
}
Adds a new BackgroundInitializer
to this object. When this MultiBackgroundInitializer
is started, the given initializer will be processed. This method must not be called after BackgroundInitializer<MultiBackgroundInitializerResults>.start()
has been invoked. Params: - name – the name of the initializer (must not be null)
- init – the
BackgroundInitializer
to add (must not be null)
Throws: - IllegalArgumentException – if a required parameter is missing
- IllegalStateException – if
start()
has already been called
/**
* Adds a new {@code BackgroundInitializer} to this object. When this
* {@code MultiBackgroundInitializer} is started, the given initializer will
* be processed. This method must not be called after {@link #start()} has
* been invoked.
*
* @param name the name of the initializer (must not be <b>null</b>)
* @param init the {@code BackgroundInitializer} to add (must not be
* <b>null</b>)
* @throws IllegalArgumentException if a required parameter is missing
* @throws IllegalStateException if {@code start()} has already been called
*/
public void addInitializer(final String name, final BackgroundInitializer<?> init) {
Validate.notNull(name, "Name of child initializer must not be null!");
Validate.notNull(init, "Child initializer must not be null!");
synchronized (this) {
if (isStarted()) {
throw new IllegalStateException(
"addInitializer() must not be called after start()!");
}
childInitializers.put(name, init);
}
}
Returns the number of tasks needed for executing all child
BackgroundInitializer
objects in parallel. This implementation sums up the required tasks for all child initializers (which is necessary if one of the child initializers is itself a MultiBackgroundInitializer
). Then it adds 1 for the control task that waits for the completion of the children. Returns: the number of tasks required for background processing
/**
* Returns the number of tasks needed for executing all child {@code
* BackgroundInitializer} objects in parallel. This implementation sums up
* the required tasks for all child initializers (which is necessary if one
* of the child initializers is itself a {@code MultiBackgroundInitializer}
* ). Then it adds 1 for the control task that waits for the completion of
* the children.
*
* @return the number of tasks required for background processing
*/
@Override
protected int getTaskCount() {
int result = 1;
for (final BackgroundInitializer<?> bi : childInitializers.values()) {
result += bi.getTaskCount();
}
return result;
}
Creates the results object. This implementation starts all child
BackgroundInitializer
objects. Then it collects their results and creates a MultiBackgroundInitializerResults
object with this data. If a child initializer throws a checked exceptions, it is added to the results object. Unchecked exceptions are propagated. Throws: - Exception – if an error occurs
Returns: the results object
/**
* Creates the results object. This implementation starts all child {@code
* BackgroundInitializer} objects. Then it collects their results and
* creates a {@code MultiBackgroundInitializerResults} object with this
* data. If a child initializer throws a checked exceptions, it is added to
* the results object. Unchecked exceptions are propagated.
*
* @return the results object
* @throws Exception if an error occurs
*/
@Override
protected MultiBackgroundInitializerResults initialize() throws Exception {
Map<String, BackgroundInitializer<?>> inits;
synchronized (this) {
// create a snapshot to operate on
inits = new HashMap<>(
childInitializers);
}
// start the child initializers
final ExecutorService exec = getActiveExecutor();
for (final BackgroundInitializer<?> bi : inits.values()) {
if (bi.getExternalExecutor() == null) {
// share the executor service if necessary
bi.setExternalExecutor(exec);
}
bi.start();
}
// collect the results
final Map<String, Object> results = new HashMap<>();
final Map<String, ConcurrentException> excepts = new HashMap<>();
for (final Map.Entry<String, BackgroundInitializer<?>> e : inits.entrySet()) {
try {
results.put(e.getKey(), e.getValue().get());
} catch (final ConcurrentException cex) {
excepts.put(e.getKey(), cex);
}
}
return new MultiBackgroundInitializerResults(inits, results, excepts);
}
A data class for storing the results of the background initialization performed by MultiBackgroundInitializer
. Objects of this inner class are returned by MultiBackgroundInitializer.initialize()
. They allow access to all result objects produced by the BackgroundInitializer
objects managed by the owning instance. It is also possible to retrieve status information about single BackgroundInitializer
s, i.e. whether they completed normally or caused an exception. /**
* A data class for storing the results of the background initialization
* performed by {@code MultiBackgroundInitializer}. Objects of this inner
* class are returned by {@link MultiBackgroundInitializer#initialize()}.
* They allow access to all result objects produced by the
* {@link BackgroundInitializer} objects managed by the owning instance. It
* is also possible to retrieve status information about single
* {@link BackgroundInitializer}s, i.e. whether they completed normally or
* caused an exception.
*/
public static class MultiBackgroundInitializerResults {
A map with the child initializers. /** A map with the child initializers. */
private final Map<String, BackgroundInitializer<?>> initializers;
A map with the result objects. /** A map with the result objects. */
private final Map<String, Object> resultObjects;
A map with the exceptions. /** A map with the exceptions. */
private final Map<String, ConcurrentException> exceptions;
Creates a new instance of MultiBackgroundInitializerResults
and initializes it with maps for the BackgroundInitializer
objects, their result objects and the exceptions thrown by them. Params: - inits – the
BackgroundInitializer
objects - results – the result objects
- excepts – the exceptions
/**
* Creates a new instance of {@code MultiBackgroundInitializerResults}
* and initializes it with maps for the {@code BackgroundInitializer}
* objects, their result objects and the exceptions thrown by them.
*
* @param inits the {@code BackgroundInitializer} objects
* @param results the result objects
* @param excepts the exceptions
*/
private MultiBackgroundInitializerResults(
final Map<String, BackgroundInitializer<?>> inits,
final Map<String, Object> results,
final Map<String, ConcurrentException> excepts) {
initializers = inits;
resultObjects = results;
exceptions = excepts;
}
Returns the BackgroundInitializer
with the given name. If the name cannot be resolved, an exception is thrown. Params: - name – the name of the
BackgroundInitializer
Throws: - NoSuchElementException – if the name cannot be resolved
Returns: the BackgroundInitializer
with this name
/**
* Returns the {@code BackgroundInitializer} with the given name. If the
* name cannot be resolved, an exception is thrown.
*
* @param name the name of the {@code BackgroundInitializer}
* @return the {@code BackgroundInitializer} with this name
* @throws NoSuchElementException if the name cannot be resolved
*/
public BackgroundInitializer<?> getInitializer(final String name) {
return checkName(name);
}
Returns the result object produced by the
BackgroundInitializer
with the given name. This is the object returned by the initializer's initialize()
method. If this BackgroundInitializer
caused an exception, null is
returned. If the name cannot be resolved, an exception is thrown.
Params: - name – the name of the
BackgroundInitializer
Throws: - NoSuchElementException – if the name cannot be resolved
Returns: the result object produced by this
BackgroundInitializer
/**
* Returns the result object produced by the {@code
* BackgroundInitializer} with the given name. This is the object
* returned by the initializer's {@code initialize()} method. If this
* {@code BackgroundInitializer} caused an exception, <b>null</b> is
* returned. If the name cannot be resolved, an exception is thrown.
*
* @param name the name of the {@code BackgroundInitializer}
* @return the result object produced by this {@code
* BackgroundInitializer}
* @throws NoSuchElementException if the name cannot be resolved
*/
public Object getResultObject(final String name) {
checkName(name);
return resultObjects.get(name);
}
Returns a flag whether the BackgroundInitializer
with the given name caused an exception. Params: - name – the name of the
BackgroundInitializer
Throws: - NoSuchElementException – if the name cannot be resolved
Returns: a flag whether this initializer caused an exception
/**
* Returns a flag whether the {@code BackgroundInitializer} with the
* given name caused an exception.
*
* @param name the name of the {@code BackgroundInitializer}
* @return a flag whether this initializer caused an exception
* @throws NoSuchElementException if the name cannot be resolved
*/
public boolean isException(final String name) {
checkName(name);
return exceptions.containsKey(name);
}
Returns the ConcurrentException
object that was thrown by the BackgroundInitializer
with the given name. If this initializer did not throw an exception, the return value is null. If the name cannot be resolved, an exception is thrown.
Params: - name – the name of the
BackgroundInitializer
Throws: - NoSuchElementException – if the name cannot be resolved
Returns: the exception thrown by this initializer
/**
* Returns the {@code ConcurrentException} object that was thrown by the
* {@code BackgroundInitializer} with the given name. If this
* initializer did not throw an exception, the return value is
* <b>null</b>. If the name cannot be resolved, an exception is thrown.
*
* @param name the name of the {@code BackgroundInitializer}
* @return the exception thrown by this initializer
* @throws NoSuchElementException if the name cannot be resolved
*/
public ConcurrentException getException(final String name) {
checkName(name);
return exceptions.get(name);
}
Returns a set with the names of all BackgroundInitializer
objects managed by the MultiBackgroundInitializer
. Returns: an (unmodifiable) set with the names of the managed
BackgroundInitializer
objects
/**
* Returns a set with the names of all {@code BackgroundInitializer}
* objects managed by the {@code MultiBackgroundInitializer}.
*
* @return an (unmodifiable) set with the names of the managed {@code
* BackgroundInitializer} objects
*/
public Set<String> initializerNames() {
return Collections.unmodifiableSet(initializers.keySet());
}
Returns a flag whether the whole initialization was successful. This
is the case if no child initializer has thrown an exception.
Returns: a flag whether the initialization was successful
/**
* Returns a flag whether the whole initialization was successful. This
* is the case if no child initializer has thrown an exception.
*
* @return a flag whether the initialization was successful
*/
public boolean isSuccessful() {
return exceptions.isEmpty();
}
Checks whether an initializer with the given name exists. If not,
throws an exception. If it exists, the associated child initializer
is returned.
Params: - name – the name to check
Throws: - NoSuchElementException – if the name is unknown
Returns: the initializer with this name
/**
* Checks whether an initializer with the given name exists. If not,
* throws an exception. If it exists, the associated child initializer
* is returned.
*
* @param name the name to check
* @return the initializer with this name
* @throws NoSuchElementException if the name is unknown
*/
private BackgroundInitializer<?> checkName(final String name) {
final BackgroundInitializer<?> init = initializers.get(name);
if (init == null) {
throw new NoSuchElementException(
"No child initializer with name " + name);
}
return init;
}
}
}