MultiBackgroundInitializer.java

  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. import java.util.Collections;
  19. import java.util.HashMap;
  20. import java.util.Map;
  21. import java.util.NoSuchElementException;
  22. import java.util.Objects;
  23. import java.util.Set;
  24. import java.util.concurrent.ExecutorService;

  25. /**
  26.  * A specialized {@link BackgroundInitializer} implementation that can deal with
  27.  * multiple background initialization tasks.
  28.  *
  29.  * <p>
  30.  * This class has a similar purpose as {@link BackgroundInitializer}. However,
  31.  * it is not limited to a single background initialization task. Rather it
  32.  * manages an arbitrary number of {@link BackgroundInitializer} objects,
  33.  * executes them, and waits until they are completely initialized. This is
  34.  * useful for applications that have to perform multiple initialization tasks
  35.  * that can run in parallel (i.e. that do not depend on each other). This class
  36.  * takes care about the management of an {@link ExecutorService} and shares it
  37.  * with the {@link BackgroundInitializer} objects it is responsible for; so the
  38.  * using application need not bother with these details.
  39.  * </p>
  40.  * <p>
  41.  * The typical usage scenario for {@link MultiBackgroundInitializer} is as
  42.  * follows:
  43.  * </p>
  44.  * <ul>
  45.  * <li>Create a new instance of the class. Optionally pass in a pre-configured
  46.  * {@link ExecutorService}. Alternatively {@link MultiBackgroundInitializer} can
  47.  * create a temporary {@link ExecutorService} and delete it after initialization
  48.  * is complete.</li>
  49.  * <li>Create specialized {@link BackgroundInitializer} objects for the
  50.  * initialization tasks to be performed and add them to the {@code
  51.  * MultiBackgroundInitializer} using the
  52.  * {@link #addInitializer(String, BackgroundInitializer)} method.</li>
  53.  * <li>After all initializers have been added, call the {@link #start()} method.
  54.  * </li>
  55.  * <li>When access to the result objects produced by the {@code
  56.  * BackgroundInitializer} objects is needed call the {@link #get()} method. The
  57.  * object returned here provides access to all result objects created during
  58.  * initialization. It also stores information about exceptions that have
  59.  * occurred.</li>
  60.  * </ul>
  61.  * <p>
  62.  * {@link MultiBackgroundInitializer} starts a special controller task that
  63.  * starts all {@link BackgroundInitializer} objects added to the instance.
  64.  * Before the an initializer is started it is checked whether this initializer
  65.  * already has an {@link ExecutorService} set. If this is the case, this {@code
  66.  * ExecutorService} is used for running the background task. Otherwise the
  67.  * current {@link ExecutorService} of this {@link MultiBackgroundInitializer} is
  68.  * shared with the initializer.
  69.  * </p>
  70.  * <p>
  71.  * The easiest way of using this class is to let it deal with the management of
  72.  * an {@link ExecutorService} itself: If no external {@link ExecutorService} is
  73.  * provided, the class creates a temporary {@link ExecutorService} (that is
  74.  * capable of executing all background tasks in parallel) and destroys it at the
  75.  * end of background processing.
  76.  * </p>
  77.  * <p>
  78.  * Alternatively an external {@link ExecutorService} can be provided - either at
  79.  * construction time or later by calling the
  80.  * {@link #setExternalExecutor(ExecutorService)} method. In this case all
  81.  * background tasks are scheduled at this external {@link ExecutorService}.
  82.  * <strong>Important note:</strong> When using an external {@code
  83.  * ExecutorService} be sure that the number of threads managed by the service is
  84.  * large enough. Otherwise a deadlock can happen! This is the case in the
  85.  * following scenario: {@link MultiBackgroundInitializer} starts a task that
  86.  * starts all registered {@link BackgroundInitializer} objects and waits for
  87.  * their completion. If for instance a single threaded {@link ExecutorService}
  88.  * is used, none of the background tasks can be executed, and the task created
  89.  * by {@link MultiBackgroundInitializer} waits forever.
  90.  * </p>
  91.  *
  92.  * @since 3.0
  93.  */
  94. public class MultiBackgroundInitializer
  95.         extends
  96.         BackgroundInitializer<MultiBackgroundInitializer.MultiBackgroundInitializerResults> {

  97.     /**
  98.      * A data class for storing the results of the background initialization
  99.      * performed by {@link MultiBackgroundInitializer}. Objects of this inner
  100.      * class are returned by {@link MultiBackgroundInitializer#initialize()}.
  101.      * They allow access to all result objects produced by the
  102.      * {@link BackgroundInitializer} objects managed by the owning instance. It
  103.      * is also possible to retrieve status information about single
  104.      * {@link BackgroundInitializer}s, i.e. whether they completed normally or
  105.      * caused an exception.
  106.      */
  107.     public static class MultiBackgroundInitializerResults {
  108.         /** A map with the child initializers. */
  109.         private final Map<String, BackgroundInitializer<?>> initializers;

  110.         /** A map with the result objects. */
  111.         private final Map<String, Object> resultObjects;

  112.         /** A map with the exceptions. */
  113.         private final Map<String, ConcurrentException> exceptions;

  114.         /**
  115.          * Creates a new instance of {@link MultiBackgroundInitializerResults}
  116.          * and initializes it with maps for the {@link BackgroundInitializer}
  117.          * objects, their result objects and the exceptions thrown by them.
  118.          *
  119.          * @param inits the {@link BackgroundInitializer} objects
  120.          * @param results the result objects
  121.          * @param excepts the exceptions
  122.          */
  123.         private MultiBackgroundInitializerResults(
  124.                 final Map<String, BackgroundInitializer<?>> inits,
  125.                 final Map<String, Object> results,
  126.                 final Map<String, ConcurrentException> excepts) {
  127.             initializers = inits;
  128.             resultObjects = results;
  129.             exceptions = excepts;
  130.         }

  131.         /**
  132.          * Checks whether an initializer with the given name exists. If not,
  133.          * throws an exception. If it exists, the associated child initializer
  134.          * is returned.
  135.          *
  136.          * @param name the name to check
  137.          * @return the initializer with this name
  138.          * @throws NoSuchElementException if the name is unknown
  139.          */
  140.         private BackgroundInitializer<?> checkName(final String name) {
  141.             final BackgroundInitializer<?> init = initializers.get(name);
  142.             if (init == null) {
  143.                 throw new NoSuchElementException(
  144.                         "No child initializer with name " + name);
  145.             }

  146.             return init;
  147.         }

  148.         /**
  149.          * Returns the {@link ConcurrentException} object that was thrown by the
  150.          * {@link BackgroundInitializer} with the given name. If this
  151.          * initializer did not throw an exception, the return value is
  152.          * <b>null</b>. If the name cannot be resolved, an exception is thrown.
  153.          *
  154.          * @param name the name of the {@link BackgroundInitializer}
  155.          * @return the exception thrown by this initializer
  156.          * @throws NoSuchElementException if the name cannot be resolved
  157.          */
  158.         public ConcurrentException getException(final String name) {
  159.             checkName(name);
  160.             return exceptions.get(name);
  161.         }

  162.         /**
  163.          * Returns the {@link BackgroundInitializer} with the given name. If the
  164.          * name cannot be resolved, an exception is thrown.
  165.          *
  166.          * @param name the name of the {@link BackgroundInitializer}
  167.          * @return the {@link BackgroundInitializer} with this name
  168.          * @throws NoSuchElementException if the name cannot be resolved
  169.          */
  170.         public BackgroundInitializer<?> getInitializer(final String name) {
  171.             return checkName(name);
  172.         }

  173.         /**
  174.          * Returns the result object produced by the {@code
  175.          * BackgroundInitializer} with the given name. This is the object
  176.          * returned by the initializer's {@code initialize()} method. If this
  177.          * {@link BackgroundInitializer} caused an exception, <b>null</b> is
  178.          * returned. If the name cannot be resolved, an exception is thrown.
  179.          *
  180.          * @param name the name of the {@link BackgroundInitializer}
  181.          * @return the result object produced by this {@code
  182.          * BackgroundInitializer}
  183.          * @throws NoSuchElementException if the name cannot be resolved
  184.          */
  185.         public Object getResultObject(final String name) {
  186.             checkName(name);
  187.             return resultObjects.get(name);
  188.         }

  189.         /**
  190.          * Returns a set with the names of all {@link BackgroundInitializer}
  191.          * objects managed by the {@link MultiBackgroundInitializer}.
  192.          *
  193.          * @return an (unmodifiable) set with the names of the managed {@code
  194.          * BackgroundInitializer} objects
  195.          */
  196.         public Set<String> initializerNames() {
  197.             return Collections.unmodifiableSet(initializers.keySet());
  198.         }

  199.         /**
  200.          * Returns a flag whether the {@link BackgroundInitializer} with the
  201.          * given name caused an exception.
  202.          *
  203.          * @param name the name of the {@link BackgroundInitializer}
  204.          * @return a flag whether this initializer caused an exception
  205.          * @throws NoSuchElementException if the name cannot be resolved
  206.          */
  207.         public boolean isException(final String name) {
  208.             checkName(name);
  209.             return exceptions.containsKey(name);
  210.         }

  211.         /**
  212.          * Returns a flag whether the whole initialization was successful. This
  213.          * is the case if no child initializer has thrown an exception.
  214.          *
  215.          * @return a flag whether the initialization was successful
  216.          */
  217.         public boolean isSuccessful() {
  218.             return exceptions.isEmpty();
  219.         }
  220.     }

  221.     /** A map with the child initializers. */
  222.     private final Map<String, BackgroundInitializer<?>> childInitializers = new HashMap<>();

  223.     /**
  224.      * Creates a new instance of {@link MultiBackgroundInitializer}.
  225.      */
  226.     public MultiBackgroundInitializer() {
  227.     }

  228.     /**
  229.      * Creates a new instance of {@link MultiBackgroundInitializer} and
  230.      * initializes it with the given external {@link ExecutorService}.
  231.      *
  232.      * @param exec the {@link ExecutorService} for executing the background
  233.      * tasks
  234.      */
  235.     public MultiBackgroundInitializer(final ExecutorService exec) {
  236.         super(exec);
  237.     }

  238.     /**
  239.      * Adds a new {@link BackgroundInitializer} to this object. When this
  240.      * {@link MultiBackgroundInitializer} is started, the given initializer will
  241.      * be processed. This method must not be called after {@link #start()} has
  242.      * been invoked.
  243.      *
  244.      * @param name the name of the initializer (must not be <b>null</b>)
  245.      * @param backgroundInitializer the {@link BackgroundInitializer} to add (must not be
  246.      * <b>null</b>)
  247.      * @throws NullPointerException if either {@code name} or {@code backgroundInitializer}
  248.      *         is {@code null}
  249.      * @throws IllegalStateException if {@code start()} has already been called
  250.      */
  251.     public void addInitializer(final String name, final BackgroundInitializer<?> backgroundInitializer) {
  252.         Objects.requireNonNull(name, "name");
  253.         Objects.requireNonNull(backgroundInitializer, "backgroundInitializer");

  254.         synchronized (this) {
  255.             if (isStarted()) {
  256.                 throw new IllegalStateException("addInitializer() must not be called after start()!");
  257.             }
  258.             childInitializers.put(name, backgroundInitializer);
  259.         }
  260.     }

  261.     /**
  262.      * Calls the closer of all child {@code BackgroundInitializer} objects
  263.      *
  264.      * @throws ConcurrentException throws an ConcurrentException that will have all other exceptions as suppressed exceptions. ConcurrentException thrown by children will be unwrapped.
  265.      * @since 3.14.0
  266.      */
  267.     @Override
  268.     public void close() throws ConcurrentException {
  269.         ConcurrentException exception = null;

  270.         for (final BackgroundInitializer<?> child : childInitializers.values()) {
  271.             try {
  272.                 child.close();
  273.             } catch (final Exception e) {
  274.                 if (exception == null) {
  275.                     exception = new ConcurrentException();
  276.                 }

  277.                 if (e instanceof ConcurrentException) {
  278.                     // Because ConcurrentException is only created by classes in this package
  279.                     // we can safely unwrap it.
  280.                     exception.addSuppressed(e.getCause());
  281.                 } else {
  282.                     exception.addSuppressed(e);
  283.                 }
  284.             }
  285.         }

  286.         if (exception != null) {
  287.             throw exception;
  288.         }
  289.     }

  290.     /**
  291.      * Returns the number of tasks needed for executing all child {@code
  292.      * BackgroundInitializer} objects in parallel. This implementation sums up
  293.      * the required tasks for all child initializers (which is necessary if one
  294.      * of the child initializers is itself a {@link MultiBackgroundInitializer}
  295.      * ). Then it adds 1 for the control task that waits for the completion of
  296.      * the children.
  297.      *
  298.      * @return the number of tasks required for background processing
  299.      */
  300.     @Override
  301.     protected int getTaskCount() {
  302.         return 1 + childInitializers.values().stream().mapToInt(BackgroundInitializer::getTaskCount).sum();
  303.     }

  304.     /**
  305.      * Creates the results object. This implementation starts all child {@code
  306.      * BackgroundInitializer} objects. Then it collects their results and
  307.      * creates a {@link MultiBackgroundInitializerResults} object with this
  308.      * data. If a child initializer throws a checked exceptions, it is added to
  309.      * the results object. Unchecked exceptions are propagated.
  310.      *
  311.      * @return the results object
  312.      * @throws Exception if an error occurs
  313.      */
  314.     @Override
  315.     protected MultiBackgroundInitializerResults initialize() throws Exception {
  316.         final Map<String, BackgroundInitializer<?>> inits;
  317.         synchronized (this) {
  318.             // create a snapshot to operate on
  319.             inits = new HashMap<>(childInitializers);
  320.         }

  321.         // start the child initializers
  322.         final ExecutorService exec = getActiveExecutor();
  323.         inits.values().forEach(bi -> {
  324.             if (bi.getExternalExecutor() == null) {
  325.                 // share the executor service if necessary
  326.                 bi.setExternalExecutor(exec);
  327.             }
  328.             bi.start();
  329.         });

  330.         // collect the results
  331.         final Map<String, Object> results = new HashMap<>();
  332.         final Map<String, ConcurrentException> excepts = new HashMap<>();
  333.         inits.forEach((k, v) -> {
  334.             try {
  335.                 results.put(k, v.get());
  336.             } catch (final ConcurrentException cex) {
  337.                 excepts.put(k, cex);
  338.             }
  339.         });

  340.         return new MultiBackgroundInitializerResults(inits, results, excepts);
  341.     }

  342.     /**
  343.      * Tests whether this all child {@code BackgroundInitializer} objects are initialized.
  344.      * Once initialized, always returns true.
  345.      *
  346.      * @return whether all child {@code BackgroundInitializer} objects instance are initialized. Once initialized, always returns true. If there are no child {@code BackgroundInitializer} objects return false.
  347.      * @since 3.14.0
  348.      */
  349.     @Override
  350.     public boolean isInitialized() {
  351.         if (childInitializers.isEmpty()) {
  352.             return false;
  353.         }

  354.         return childInitializers.values().stream().allMatch(BackgroundInitializer::isInitialized);
  355.     }
  356. }