    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.lang3.concurrent;
    018
    019import java.util.Collections;
    020import java.util.HashMap;
    021import java.util.Map;
    022import java.util.NoSuchElementException;
    023import java.util.Objects;
    024import java.util.Set;
    025import java.util.concurrent.ExecutorService;
    026
    027/**
    028 * A specialized {@link BackgroundInitializer} implementation that can deal with
    029 * multiple background initialization tasks.
    030 *
    031 * <p>
    032 * This class has a similar purpose as {@link BackgroundInitializer}. However,
    033 * it is not limited to a single background initialization task. Rather it
    034 * manages an arbitrary number of {@link BackgroundInitializer} objects,
    035 * executes them, and waits until they are completely initialized. This is
    036 * useful for applications that have to perform multiple initialization tasks
    037 * that can run in parallel (i.e. that do not depend on each other). This class
    038 * takes care about the management of an {@link ExecutorService} and shares it
    039 * with the {@link BackgroundInitializer} objects it is responsible for; so the
    040 * using application need not bother with these details.
    041 * </p>
    042 * <p>
    043 * The typical usage scenario for {@link MultiBackgroundInitializer} is as
    044 * follows:
    045 * </p>
    046 * <ul>
    047 * <li>Create a new instance of the class. Optionally pass in a pre-configured
    048 * {@link ExecutorService}. Alternatively {@link MultiBackgroundInitializer} can
    049 * create a temporary {@link ExecutorService} and delete it after initialization
    050 * is complete.</li>
    051 * <li>Create specialized {@link BackgroundInitializer} objects for the
    052 * initialization tasks to be performed and add them to the {@code
    053 * MultiBackgroundInitializer} using the
    054 * {@link #addInitializer(String, BackgroundInitializer)} method.</li>
    055 * <li>After all initializers have been added, call the {@link #start()} method.
    056 * </li>
    057 * <li>When access to the result objects produced by the {@code
    058 * BackgroundInitializer} objects is needed call the {@link #get()} method. The
    059 * object returned here provides access to all result objects created during
    060 * initialization. It also stores information about exceptions that have
    061 * occurred.</li>
    062 * </ul>
    063 * <p>
    064 * {@link MultiBackgroundInitializer} starts a special controller task that
    065 * starts all {@link BackgroundInitializer} objects added to the instance.
    066 * Before the an initializer is started it is checked whether this initializer
    067 * already has an {@link ExecutorService} set. If this is the case, this {@code
    068 * ExecutorService} is used for running the background task. Otherwise the
    069 * current {@link ExecutorService} of this {@link MultiBackgroundInitializer} is
    070 * shared with the initializer.
    071 * </p>
    072 * <p>
    073 * The easiest way of using this class is to let it deal with the management of
    074 * an {@link ExecutorService} itself: If no external {@link ExecutorService} is
    075 * provided, the class creates a temporary {@link ExecutorService} (that is
    076 * capable of executing all background tasks in parallel) and destroys it at the
    077 * end of background processing.
    078 * </p>
    079 * <p>
    080 * Alternatively an external {@link ExecutorService} can be provided - either at
    081 * construction time or later by calling the
    082 * {@link #setExternalExecutor(ExecutorService)} method. In this case all
    083 * background tasks are scheduled at this external {@link ExecutorService}.
    084 * <strong>Important note:</strong> When using an external {@code
    085 * ExecutorService} be sure that the number of threads managed by the service is
    086 * large enough. Otherwise a deadlock can happen! This is the case in the
    087 * following scenario: {@link MultiBackgroundInitializer} starts a task that
    088 * starts all registered {@link BackgroundInitializer} objects and waits for
    089 * their completion. If for instance a single threaded {@link ExecutorService}
    090 * is used, none of the background tasks can be executed, and the task created
    091 * by {@link MultiBackgroundInitializer} waits forever.
    092 * </p>
    093 *
    094 * @since 3.0
    095 */
    096public class MultiBackgroundInitializer
    097        extends
    098        BackgroundInitializer<MultiBackgroundInitializer.MultiBackgroundInitializerResults> {
    099
    100    /** A map with the child initializers. */
    101    private final Map<String, BackgroundInitializer<?>> childInitializers = new HashMap<>();
    102
    103    /**
    104     * Creates a new instance of {@link MultiBackgroundInitializer}.
    105     */
    106    public MultiBackgroundInitializer() {
    107    }
    108
    109    /**
    110     * Creates a new instance of {@link MultiBackgroundInitializer} and
    111     * initializes it with the given external {@link ExecutorService}.
    112     *
    113     * @param exec the {@link ExecutorService} for executing the background
    114     * tasks
    115     */
    116    public MultiBackgroundInitializer(final ExecutorService exec) {
    117        super(exec);
    118    }
    119
    120    /**
    121     * Adds a new {@link BackgroundInitializer} to this object. When this
    122     * {@link MultiBackgroundInitializer} is started, the given initializer will
    123     * be processed. This method must not be called after {@link #start()} has
    124     * been invoked.
    125     *
    126     * @param name the name of the initializer (must not be <b>null</b>)
    127     * @param backgroundInitializer the {@link BackgroundInitializer} to add (must not be
    128     * <b>null</b>)
    129     * @throws NullPointerException if either {@code name} or {@code backgroundInitializer}
    130     *         is {@code null}
    131     * @throws IllegalStateException if {@code start()} has already been called
    132     */
    133    public void addInitializer(final String name, final BackgroundInitializer<?> backgroundInitializer) {
    134        Objects.requireNonNull(name, "name");
    135        Objects.requireNonNull(backgroundInitializer, "backgroundInitializer");
    136
    137        synchronized (this) {
    138            if (isStarted()) {
    139                throw new IllegalStateException("addInitializer() must not be called after start()!");
    140            }
    141            childInitializers.put(name, backgroundInitializer);
    142        }
    143    }
    144
    145    /**
    146     * Returns the number of tasks needed for executing all child {@code
    147     * BackgroundInitializer} objects in parallel. This implementation sums up
    148     * the required tasks for all child initializers (which is necessary if one
    149     * of the child initializers is itself a {@link MultiBackgroundInitializer}
    150     * ). Then it adds 1 for the control task that waits for the completion of
    151     * the children.
    152     *
    153     * @return the number of tasks required for background processing
    154     */
    155    @Override
    156    protected int getTaskCount() {
    157        return 1 + childInitializers.values().stream().mapToInt(BackgroundInitializer::getTaskCount).sum();
    158    }
    159
    160    /**
    161     * Creates the results object. This implementation starts all child {@code
    162     * BackgroundInitializer} objects. Then it collects their results and
    163     * creates a {@link MultiBackgroundInitializerResults} object with this
    164     * data. If a child initializer throws a checked exceptions, it is added to
    165     * the results object. Unchecked exceptions are propagated.
    166     *
    167     * @return the results object
    168     * @throws Exception if an error occurs
    169     */
    170    @Override
    171    protected MultiBackgroundInitializerResults initialize() throws Exception {
    172        final Map<String, BackgroundInitializer<?>> inits;
    173        synchronized (this) {
    174            // create a snapshot to operate on
    175            inits = new HashMap<>(childInitializers);
    176        }
    177
    178        // start the child initializers
    179        final ExecutorService exec = getActiveExecutor();
    180        inits.values().forEach(bi -> {
    181            if (bi.getExternalExecutor() == null) {
    182                // share the executor service if necessary
    183                bi.setExternalExecutor(exec);
    184            }
    185            bi.start();
    186        });
    187
    188        // collect the results
    189        final Map<String, Object> results = new HashMap<>();
    190        final Map<String, ConcurrentException> excepts = new HashMap<>();
    191        inits.forEach((k, v) -> {
    192            try {
    193                results.put(k, v.get());
    194            } catch (final ConcurrentException cex) {
    195                excepts.put(k, cex);
    196            }
    197        });
    198
    199        return new MultiBackgroundInitializerResults(inits, results, excepts);
    200    }
    201
    202    /**
    203     * A data class for storing the results of the background initialization
    204     * performed by {@link MultiBackgroundInitializer}. Objects of this inner
    205     * class are returned by {@link MultiBackgroundInitializer#initialize()}.
    206     * They allow access to all result objects produced by the
    207     * {@link BackgroundInitializer} objects managed by the owning instance. It
    208     * is also possible to retrieve status information about single
    209     * {@link BackgroundInitializer}s, i.e. whether they completed normally or
    210     * caused an exception.
    211     */
    212    public static class MultiBackgroundInitializerResults {
    213        /** A map with the child initializers. */
    214        private final Map<String, BackgroundInitializer<?>> initializers;
    215
    216        /** A map with the result objects. */
    217        private final Map<String, Object> resultObjects;
    218
    219        /** A map with the exceptions. */
    220        private final Map<String, ConcurrentException> exceptions;
    221
    222        /**
    223         * Creates a new instance of {@link MultiBackgroundInitializerResults}
    224         * and initializes it with maps for the {@link BackgroundInitializer}
    225         * objects, their result objects and the exceptions thrown by them.
    226         *
    227         * @param inits the {@link BackgroundInitializer} objects
    228         * @param results the result objects
    229         * @param excepts the exceptions
    230         */
    231        private MultiBackgroundInitializerResults(
    232                final Map<String, BackgroundInitializer<?>> inits,
    233                final Map<String, Object> results,
    234                final Map<String, ConcurrentException> excepts) {
    235            initializers = inits;
    236            resultObjects = results;
    237            exceptions = excepts;
    238        }
    239
    240        /**
    241         * Returns the {@link BackgroundInitializer} with the given name. If the
    242         * name cannot be resolved, an exception is thrown.
    243         *
    244         * @param name the name of the {@link BackgroundInitializer}
    245         * @return the {@link BackgroundInitializer} with this name
    246         * @throws NoSuchElementException if the name cannot be resolved
    247         */
    248        public BackgroundInitializer<?> getInitializer(final String name) {
    249            return checkName(name);
    250        }
    251
    252        /**
    253         * Returns the result object produced by the {@code
    254         * BackgroundInitializer} with the given name. This is the object
    255         * returned by the initializer's {@code initialize()} method. If this
    256         * {@link BackgroundInitializer} caused an exception, <b>null</b> is
    257         * returned. If the name cannot be resolved, an exception is thrown.
    258         *
    259         * @param name the name of the {@link BackgroundInitializer}
    260         * @return the result object produced by this {@code
    261         * BackgroundInitializer}
    262         * @throws NoSuchElementException if the name cannot be resolved
    263         */
    264        public Object getResultObject(final String name) {
    265            checkName(name);
    266            return resultObjects.get(name);
    267        }
    268
    269        /**
    270         * Returns a flag whether the {@link BackgroundInitializer} with the
    271         * given name caused an exception.
    272         *
    273         * @param name the name of the {@link BackgroundInitializer}
    274         * @return a flag whether this initializer caused an exception
    275         * @throws NoSuchElementException if the name cannot be resolved
    276         */
    277        public boolean isException(final String name) {
    278            checkName(name);
    279            return exceptions.containsKey(name);
    280        }
    281
    282        /**
    283         * Returns the {@link ConcurrentException} object that was thrown by the
    284         * {@link BackgroundInitializer} with the given name. If this
    285         * initializer did not throw an exception, the return value is
    286         * <b>null</b>. If the name cannot be resolved, an exception is thrown.
    287         *
    288         * @param name the name of the {@link BackgroundInitializer}
    289         * @return the exception thrown by this initializer
    290         * @throws NoSuchElementException if the name cannot be resolved
    291         */
    292        public ConcurrentException getException(final String name) {
    293            checkName(name);
    294            return exceptions.get(name);
    295        }
    296
    297        /**
    298         * Returns a set with the names of all {@link BackgroundInitializer}
    299         * objects managed by the {@link MultiBackgroundInitializer}.
    300         *
    301         * @return an (unmodifiable) set with the names of the managed {@code
    302         * BackgroundInitializer} objects
    303         */
    304        public Set<String> initializerNames() {
    305            return Collections.unmodifiableSet(initializers.keySet());
    306        }
    307
    308        /**
    309         * Returns a flag whether the whole initialization was successful. This
    310         * is the case if no child initializer has thrown an exception.
    311         *
    312         * @return a flag whether the initialization was successful
    313         */
    314        public boolean isSuccessful() {
    315            return exceptions.isEmpty();
    316        }
    317
    318        /**
    319         * Checks whether an initializer with the given name exists. If not,
    320         * throws an exception. If it exists, the associated child initializer
    321         * is returned.
    322         *
    323         * @param name the name to check
    324         * @return the initializer with this name
    325         * @throws NoSuchElementException if the name is unknown
    326         */
    327        private BackgroundInitializer<?> checkName(final String name) {
    328            final BackgroundInitializer<?> init = initializers.get(name);
    329            if (init == null) {
    330                throw new NoSuchElementException(
    331                        "No child initializer with name " + name);
    332            }
    333
    334            return init;
    335        }
    336    }
    337}