001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.task;
018
019import java.time.Duration;
020import java.util.Arrays;
021import java.util.Collections;
022import java.util.LinkedHashSet;
023import java.util.Set;
024
025import org.springframework.beans.BeanUtils;
026import org.springframework.boot.context.properties.PropertyMapper;
027import org.springframework.core.task.TaskDecorator;
028import org.springframework.core.task.TaskExecutor;
029import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
030import org.springframework.util.Assert;
031import org.springframework.util.CollectionUtils;
032
033/**
034 * Builder that can be used to configure and create a {@link TaskExecutor}. Provides
035 * convenience methods to set common {@link ThreadPoolTaskExecutor} settings and register
036 * {@link #taskDecorator(TaskDecorator)}). For advanced configuration, consider using
037 * {@link TaskExecutorCustomizer}.
038 * <p>
039 * In a typical auto-configured Spring Boot application this builder is available as a
040 * bean and can be injected whenever a {@link TaskExecutor} is needed.
041 *
042 * @author Stephane Nicoll
043 * @since 2.1.0
044 */
045public class TaskExecutorBuilder {
046
047        private final Integer queueCapacity;
048
049        private final Integer corePoolSize;
050
051        private final Integer maxPoolSize;
052
053        private final Boolean allowCoreThreadTimeOut;
054
055        private final Duration keepAlive;
056
057        private final String threadNamePrefix;
058
059        private final TaskDecorator taskDecorator;
060
061        private final Set<TaskExecutorCustomizer> customizers;
062
063        public TaskExecutorBuilder() {
064                this.queueCapacity = null;
065                this.corePoolSize = null;
066                this.maxPoolSize = null;
067                this.allowCoreThreadTimeOut = null;
068                this.keepAlive = null;
069                this.threadNamePrefix = null;
070                this.taskDecorator = null;
071                this.customizers = null;
072        }
073
074        private TaskExecutorBuilder(Integer queueCapacity, Integer corePoolSize,
075                        Integer maxPoolSize, Boolean allowCoreThreadTimeOut, Duration keepAlive,
076                        String threadNamePrefix, TaskDecorator taskDecorator,
077                        Set<TaskExecutorCustomizer> customizers) {
078                this.queueCapacity = queueCapacity;
079                this.corePoolSize = corePoolSize;
080                this.maxPoolSize = maxPoolSize;
081                this.allowCoreThreadTimeOut = allowCoreThreadTimeOut;
082                this.keepAlive = keepAlive;
083                this.threadNamePrefix = threadNamePrefix;
084                this.taskDecorator = taskDecorator;
085                this.customizers = customizers;
086        }
087
088        /**
089         * Set the capacity of the queue. An unbounded capacity does not increase the pool and
090         * therefore ignores {@link #maxPoolSize(int) maxPoolSize}.
091         * @param queueCapacity the queue capacity to set
092         * @return a new builder instance
093         */
094        public TaskExecutorBuilder queueCapacity(int queueCapacity) {
095                return new TaskExecutorBuilder(queueCapacity, this.corePoolSize, this.maxPoolSize,
096                                this.allowCoreThreadTimeOut, this.keepAlive, this.threadNamePrefix,
097                                this.taskDecorator, this.customizers);
098        }
099
100        /**
101         * Set the core number of threads. Effectively that maximum number of threads as long
102         * as the queue is not full.
103         * <p>
104         * Core threads can grow and shrink if {@link #allowCoreThreadTimeOut(boolean)} is
105         * enabled.
106         * @param corePoolSize the core pool size to set
107         * @return a new builder instance
108         */
109        public TaskExecutorBuilder corePoolSize(int corePoolSize) {
110                return new TaskExecutorBuilder(this.queueCapacity, corePoolSize, this.maxPoolSize,
111                                this.allowCoreThreadTimeOut, this.keepAlive, this.threadNamePrefix,
112                                this.taskDecorator, this.customizers);
113        }
114
115        /**
116         * Set the maximum allowed number of threads. When the {@link #queueCapacity(int)
117         * queue} is full, the pool can expand up to that size to accommodate the load.
118         * <p>
119         * If the {@link #queueCapacity(int) queue capacity} is unbounded, this setting is
120         * ignored.
121         * @param maxPoolSize the max pool size to set
122         * @return a new builder instance
123         */
124        public TaskExecutorBuilder maxPoolSize(int maxPoolSize) {
125                return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize, maxPoolSize,
126                                this.allowCoreThreadTimeOut, this.keepAlive, this.threadNamePrefix,
127                                this.taskDecorator, this.customizers);
128        }
129
130        /**
131         * Set whether core threads are allow to time out. When enabled, this enables dynamic
132         * growing and shrinking of the pool.
133         * @param allowCoreThreadTimeOut if core threads are allowed to time out
134         * @return a new builder instance
135         */
136        public TaskExecutorBuilder allowCoreThreadTimeOut(boolean allowCoreThreadTimeOut) {
137                return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize,
138                                this.maxPoolSize, allowCoreThreadTimeOut, this.keepAlive,
139                                this.threadNamePrefix, this.taskDecorator, this.customizers);
140        }
141
142        /**
143         * Set the time limit for which threads may remain idle before being terminated.
144         * @param keepAlive the keep alive to set
145         * @return a new builder instance
146         */
147        public TaskExecutorBuilder keepAlive(Duration keepAlive) {
148                return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize,
149                                this.maxPoolSize, this.allowCoreThreadTimeOut, keepAlive,
150                                this.threadNamePrefix, this.taskDecorator, this.customizers);
151        }
152
153        /**
154         * Set the prefix to use for the names of newly created threads.
155         * @param threadNamePrefix the thread name prefix to set
156         * @return a new builder instance
157         */
158        public TaskExecutorBuilder threadNamePrefix(String threadNamePrefix) {
159                return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize,
160                                this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive,
161                                threadNamePrefix, this.taskDecorator, this.customizers);
162        }
163
164        /**
165         * Set the {@link TaskDecorator} to use or {@code null} to not use any.
166         * @param taskDecorator the task decorator to use
167         * @return a new builder instance
168         */
169        public TaskExecutorBuilder taskDecorator(TaskDecorator taskDecorator) {
170                return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize,
171                                this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive,
172                                this.threadNamePrefix, taskDecorator, this.customizers);
173        }
174
175        /**
176         * Set the {@link TaskExecutorCustomizer TaskExecutorCustomizers} that should be
177         * applied to the {@link ThreadPoolTaskExecutor}. Customizers are applied in the order
178         * that they were added after builder configuration has been applied. Setting this
179         * value will replace any previously configured customizers.
180         * @param customizers the customizers to set
181         * @return a new builder instance
182         * @see #additionalCustomizers(TaskExecutorCustomizer...)
183         */
184        public TaskExecutorBuilder customizers(TaskExecutorCustomizer... customizers) {
185                Assert.notNull(customizers, "Customizers must not be null");
186                return customizers(Arrays.asList(customizers));
187        }
188
189        /**
190         * Set the {@link TaskExecutorCustomizer TaskExecutorCustomizers} that should be
191         * applied to the {@link ThreadPoolTaskExecutor}. Customizers are applied in the order
192         * that they were added after builder configuration has been applied. Setting this
193         * value will replace any previously configured customizers.
194         * @param customizers the customizers to set
195         * @return a new builder instance
196         * @see #additionalCustomizers(TaskExecutorCustomizer...)
197         */
198        public TaskExecutorBuilder customizers(Iterable<TaskExecutorCustomizer> customizers) {
199                Assert.notNull(customizers, "Customizers must not be null");
200                return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize,
201                                this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive,
202                                this.threadNamePrefix, this.taskDecorator, append(null, customizers));
203        }
204
205        /**
206         * Add {@link TaskExecutorCustomizer TaskExecutorCustomizers} that should be applied
207         * to the {@link ThreadPoolTaskExecutor}. Customizers are applied in the order that
208         * they were added after builder configuration has been applied.
209         * @param customizers the customizers to add
210         * @return a new builder instance
211         * @see #customizers(TaskExecutorCustomizer...)
212         */
213        public TaskExecutorBuilder additionalCustomizers(
214                        TaskExecutorCustomizer... customizers) {
215                Assert.notNull(customizers, "Customizers must not be null");
216                return additionalCustomizers(Arrays.asList(customizers));
217        }
218
219        /**
220         * Add {@link TaskExecutorCustomizer TaskExecutorCustomizers} that should be applied
221         * to the {@link ThreadPoolTaskExecutor}. Customizers are applied in the order that
222         * they were added after builder configuration has been applied.
223         * @param customizers the customizers to add
224         * @return a new builder instance
225         * @see #customizers(TaskExecutorCustomizer...)
226         */
227        public TaskExecutorBuilder additionalCustomizers(
228                        Iterable<TaskExecutorCustomizer> customizers) {
229                Assert.notNull(customizers, "Customizers must not be null");
230                return new TaskExecutorBuilder(this.queueCapacity, this.corePoolSize,
231                                this.maxPoolSize, this.allowCoreThreadTimeOut, this.keepAlive,
232                                this.threadNamePrefix, this.taskDecorator,
233                                append(this.customizers, customizers));
234        }
235
236        /**
237         * Build a new {@link ThreadPoolTaskExecutor} instance and configure it using this
238         * builder.
239         * @return a configured {@link ThreadPoolTaskExecutor} instance.
240         * @see #build(Class)
241         * @see #configure(ThreadPoolTaskExecutor)
242         */
243        public ThreadPoolTaskExecutor build() {
244                return build(ThreadPoolTaskExecutor.class);
245        }
246
247        /**
248         * Build a new {@link ThreadPoolTaskExecutor} instance of the specified type and
249         * configure it using this builder.
250         * @param <T> the type of task executor
251         * @param taskExecutorClass the template type to create
252         * @return a configured {@link ThreadPoolTaskExecutor} instance.
253         * @see #build()
254         * @see #configure(ThreadPoolTaskExecutor)
255         */
256        public <T extends ThreadPoolTaskExecutor> T build(Class<T> taskExecutorClass) {
257                return configure(BeanUtils.instantiateClass(taskExecutorClass));
258        }
259
260        /**
261         * Configure the provided {@link ThreadPoolTaskExecutor} instance using this builder.
262         * @param <T> the type of task executor
263         * @param taskExecutor the {@link ThreadPoolTaskExecutor} to configure
264         * @return the task executor instance
265         * @see #build()
266         * @see #build(Class)
267         */
268        public <T extends ThreadPoolTaskExecutor> T configure(T taskExecutor) {
269                PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
270                map.from(this.queueCapacity).to(taskExecutor::setQueueCapacity);
271                map.from(this.corePoolSize).to(taskExecutor::setCorePoolSize);
272                map.from(this.maxPoolSize).to(taskExecutor::setMaxPoolSize);
273                map.from(this.keepAlive).asInt(Duration::getSeconds)
274                                .to(taskExecutor::setKeepAliveSeconds);
275                map.from(this.allowCoreThreadTimeOut).to(taskExecutor::setAllowCoreThreadTimeOut);
276                map.from(this.threadNamePrefix).whenHasText()
277                                .to(taskExecutor::setThreadNamePrefix);
278                map.from(this.taskDecorator).to(taskExecutor::setTaskDecorator);
279                if (!CollectionUtils.isEmpty(this.customizers)) {
280                        this.customizers.forEach((customizer) -> customizer.customize(taskExecutor));
281                }
282                return taskExecutor;
283        }
284
285        private <T> Set<T> append(Set<T> set, Iterable<? extends T> additions) {
286                Set<T> result = new LinkedHashSet<>((set != null) ? set : Collections.emptySet());
287                additions.forEach(result::add);
288                return Collections.unmodifiableSet(result);
289        }
290
291}