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 * https://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 extends BackgroundInitializer<MultiBackgroundInitializer.MultiBackgroundInitializerResults> { 097 098 /** 099 * A data class for storing the results of the background initialization 100 * performed by {@link MultiBackgroundInitializer}. Objects of this inner 101 * class are returned by {@link MultiBackgroundInitializer#initialize()}. 102 * They allow access to all result objects produced by the 103 * {@link BackgroundInitializer} objects managed by the owning instance. It 104 * is also possible to retrieve status information about single 105 * {@link BackgroundInitializer}s, i.e. whether they completed normally or 106 * caused an exception. 107 */ 108 public static class MultiBackgroundInitializerResults { 109 /** A map with the child initializers. */ 110 private final Map<String, BackgroundInitializer<?>> initializers; 111 112 /** A map with the result objects. */ 113 private final Map<String, Object> resultObjects; 114 115 /** A map with the exceptions. */ 116 private final Map<String, ConcurrentException> exceptions; 117 118 /** 119 * Creates a new instance of {@link MultiBackgroundInitializerResults} and initializes it with maps for the {@link BackgroundInitializer} objects, their 120 * result objects and the exceptions thrown by them. 121 * 122 * @param initializers the {@link BackgroundInitializer} objects. 123 * @param resultObjects the result objects. 124 * @param exceptions the exceptions. 125 */ 126 private MultiBackgroundInitializerResults(final Map<String, BackgroundInitializer<?>> initializers, final Map<String, Object> resultObjects, 127 final Map<String, ConcurrentException> exceptions) { 128 this.initializers = initializers; 129 this.resultObjects = resultObjects; 130 this.exceptions = exceptions; 131 } 132 133 /** 134 * Checks whether an initializer with the given name exists. If not, 135 * throws an exception. If it exists, the associated child initializer 136 * is returned. 137 * 138 * @param name the name to check. 139 * @return the initializer with this name. 140 * @throws NoSuchElementException if the name is unknown. 141 */ 142 private BackgroundInitializer<?> checkName(final String name) { 143 final BackgroundInitializer<?> init = initializers.get(name); 144 if (init == null) { 145 throw new NoSuchElementException("No child initializer with name " + name); 146 } 147 return init; 148 } 149 150 /** 151 * Gets the {@link ConcurrentException} object that was thrown by the 152 * {@link BackgroundInitializer} with the given name. If this 153 * initializer did not throw an exception, the return value is 154 * <strong>null</strong>. If the name cannot be resolved, an exception is thrown. 155 * 156 * @param name the name of the {@link BackgroundInitializer}. 157 * @return the exception thrown by this initializer. 158 * @throws NoSuchElementException if the name cannot be resolved. 159 */ 160 public ConcurrentException getException(final String name) { 161 checkName(name); 162 return exceptions.get(name); 163 } 164 165 /** 166 * Gets the {@link BackgroundInitializer} with the given name. If the 167 * name cannot be resolved, an exception is thrown. 168 * 169 * @param name the name of the {@link BackgroundInitializer}. 170 * @return the {@link BackgroundInitializer} with this name. 171 * @throws NoSuchElementException if the name cannot be resolved. 172 */ 173 public BackgroundInitializer<?> getInitializer(final String name) { 174 return checkName(name); 175 } 176 177 /** 178 * Gets the result object produced by the {@code 179 * BackgroundInitializer} with the given name. This is the object returned by the initializer's {@code initialize()} method. If this 180 * {@link BackgroundInitializer} caused an exception, <strong>null</strong> is returned. If the name cannot be resolved, an exception is thrown. 181 * 182 * @param name the name of the {@link BackgroundInitializer}. 183 * @return the result object produced by this {@code BackgroundInitializer}. 184 * @throws NoSuchElementException if the name cannot be resolved. 185 */ 186 public Object getResultObject(final String name) { 187 checkName(name); 188 return resultObjects.get(name); 189 } 190 191 /** 192 * Returns a set with the names of all {@link BackgroundInitializer} objects managed by the {@link MultiBackgroundInitializer}. 193 * 194 * @return an (unmodifiable) set with the names of the managed {@code BackgroundInitializer} objects. 195 */ 196 public Set<String> initializerNames() { 197 return Collections.unmodifiableSet(initializers.keySet()); 198 } 199 200 /** 201 * Tests whether the {@link BackgroundInitializer} with the 202 * given name caused an exception. 203 * 204 * @param name the name of the {@link BackgroundInitializer}. 205 * @return a flag whether this initializer caused an exception. 206 * @throws NoSuchElementException if the name cannot be resolved. 207 */ 208 public boolean isException(final String name) { 209 checkName(name); 210 return exceptions.containsKey(name); 211 } 212 213 /** 214 * Tests whether the whole initialization was successful. This 215 * is the case if no child initializer has thrown an exception. 216 * 217 * @return a flag whether the initialization was successful. 218 */ 219 public boolean isSuccessful() { 220 return exceptions.isEmpty(); 221 } 222 } 223 224 /** A map with the child initializers. */ 225 private final Map<String, BackgroundInitializer<?>> childInitializers = new HashMap<>(); 226 227 /** 228 * Constructs a new instance of {@link MultiBackgroundInitializer}. 229 */ 230 public MultiBackgroundInitializer() { 231 } 232 233 /** 234 * Constructs a new instance of {@link MultiBackgroundInitializer} and 235 * initializes it with the given external {@link ExecutorService}. 236 * 237 * @param exec the {@link ExecutorService} for executing the background tasks. 238 */ 239 public MultiBackgroundInitializer(final ExecutorService exec) { 240 super(exec); 241 } 242 243 /** 244 * Adds a new {@link BackgroundInitializer} to this object. When this {@link MultiBackgroundInitializer} is started, the given initializer will be 245 * processed. This method must not be called after {@link #start()} has been invoked. 246 * 247 * @param name the name of the initializer (must not be <strong>null</strong>). 248 * @param backgroundInitializer the {@link BackgroundInitializer} to add (must not be <strong>null</strong>). 249 * @throws NullPointerException if either {@code name} or {@code backgroundInitializer} is {@code null}. 250 * @throws IllegalStateException if {@code start()} has already been called. 251 */ 252 public void addInitializer(final String name, final BackgroundInitializer<?> backgroundInitializer) { 253 Objects.requireNonNull(name, "name"); 254 Objects.requireNonNull(backgroundInitializer, "backgroundInitializer"); 255 synchronized (this) { 256 if (isStarted()) { 257 throw new IllegalStateException("addInitializer() must not be called after start()!"); 258 } 259 childInitializers.put(name, backgroundInitializer); 260 } 261 } 262 263 /** 264 * Calls the closer of all child {@code BackgroundInitializer} objects. 265 * 266 * @throws ConcurrentException throws an ConcurrentException that will have all other exceptions as suppressed exceptions. ConcurrentException thrown by 267 * children will be unwrapped. 268 * @since 3.14.0 269 */ 270 @Override 271 public void close() throws ConcurrentException { 272 ConcurrentException exception = null; 273 for (final BackgroundInitializer<?> child : childInitializers.values()) { 274 try { 275 child.close(); 276 } catch (final Exception e) { 277 if (exception == null) { 278 exception = new ConcurrentException(); 279 } 280 if (e instanceof ConcurrentException) { 281 // Because ConcurrentException is only created by classes in this package 282 // we can safely unwrap it. 283 exception.addSuppressed(e.getCause()); 284 } else { 285 exception.addSuppressed(e); 286 } 287 } 288 } 289 if (exception != null) { 290 throw exception; 291 } 292 } 293 294 /** 295 * Gets the number of tasks needed for executing all child {@code 296 * BackgroundInitializer} objects in parallel. This implementation sums up 297 * the required tasks for all child initializers (which is necessary if one 298 * of the child initializers is itself a {@link MultiBackgroundInitializer} 299 * ). Then it adds 1 for the control task that waits for the completion of 300 * the children. 301 * 302 * @return the number of tasks required for background processing. 303 */ 304 @Override 305 protected int getTaskCount() { 306 return 1 + childInitializers.values().stream().mapToInt(BackgroundInitializer::getTaskCount).sum(); 307 } 308 309 /** 310 * Creates the results object. This implementation starts all child {@code 311 * BackgroundInitializer} objects. Then it collects their results and 312 * creates a {@link MultiBackgroundInitializerResults} object with this 313 * data. If a child initializer throws a checked exceptions, it is added to 314 * the results object. Unchecked exceptions are propagated. 315 * 316 * @return the results object. 317 * @throws Exception if an error occurs. 318 */ 319 @Override 320 protected MultiBackgroundInitializerResults initialize() throws Exception { 321 final Map<String, BackgroundInitializer<?>> inits; 322 synchronized (this) { 323 // create a snapshot to operate on 324 inits = new HashMap<>(childInitializers); 325 } 326 // start the child initializers 327 final ExecutorService exec = getActiveExecutor(); 328 inits.values().forEach(bi -> { 329 if (bi.getExternalExecutor() == null) { 330 // share the executor service if necessary 331 bi.setExternalExecutor(exec); 332 } 333 bi.start(); 334 }); 335 // collect the results 336 final Map<String, Object> results = new HashMap<>(); 337 final Map<String, ConcurrentException> excepts = new HashMap<>(); 338 inits.forEach((k, v) -> { 339 try { 340 results.put(k, v.get()); 341 } catch (final ConcurrentException cex) { 342 excepts.put(k, cex); 343 } 344 }); 345 return new MultiBackgroundInitializerResults(inits, results, excepts); 346 } 347 348 /** 349 * Tests whether this all child {@code BackgroundInitializer} objects are initialized. Once initialized, always returns true. 350 * 351 * @return Whether all child {@code BackgroundInitializer} objects instance are initialized. Once initialized, always returns true. If there are no child 352 * {@code BackgroundInitializer} objects return false. 353 * @since 3.14.0 354 */ 355 @Override 356 public boolean isInitialized() { 357 if (childInitializers.isEmpty()) { 358 return false; 359 } 360 return childInitializers.values().stream().allMatch(BackgroundInitializer::isInitialized); 361 } 362}