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