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