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