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 /** 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}