001/*
002 * Copyright 2002-2020 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 *      https://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.scheduling.config;
018
019import java.util.concurrent.RejectedExecutionHandler;
020
021import org.springframework.beans.factory.BeanNameAware;
022import org.springframework.beans.factory.DisposableBean;
023import org.springframework.beans.factory.FactoryBean;
024import org.springframework.beans.factory.InitializingBean;
025import org.springframework.core.task.TaskExecutor;
026import org.springframework.lang.Nullable;
027import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
028import org.springframework.util.StringUtils;
029
030/**
031 * {@link FactoryBean} for creating {@link ThreadPoolTaskExecutor} instances,
032 * primarily used behind the XML task namespace.
033 *
034 * @author Mark Fisher
035 * @author Juergen Hoeller
036 * @since 3.0
037 */
038public class TaskExecutorFactoryBean implements
039                FactoryBean<TaskExecutor>, BeanNameAware, InitializingBean, DisposableBean {
040
041        @Nullable
042        private String poolSize;
043
044        @Nullable
045        private Integer queueCapacity;
046
047        @Nullable
048        private RejectedExecutionHandler rejectedExecutionHandler;
049
050        @Nullable
051        private Integer keepAliveSeconds;
052
053        @Nullable
054        private String beanName;
055
056        @Nullable
057        private ThreadPoolTaskExecutor target;
058
059
060        public void setPoolSize(String poolSize) {
061                this.poolSize = poolSize;
062        }
063
064        public void setQueueCapacity(int queueCapacity) {
065                this.queueCapacity = queueCapacity;
066        }
067
068        public void setRejectedExecutionHandler(RejectedExecutionHandler rejectedExecutionHandler) {
069                this.rejectedExecutionHandler = rejectedExecutionHandler;
070        }
071
072        public void setKeepAliveSeconds(int keepAliveSeconds) {
073                this.keepAliveSeconds = keepAliveSeconds;
074        }
075
076        @Override
077        public void setBeanName(String beanName) {
078                this.beanName = beanName;
079        }
080
081
082        @Override
083        public void afterPropertiesSet() {
084                ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
085                determinePoolSizeRange(executor);
086                if (this.queueCapacity != null) {
087                        executor.setQueueCapacity(this.queueCapacity);
088                }
089                if (this.keepAliveSeconds != null) {
090                        executor.setKeepAliveSeconds(this.keepAliveSeconds);
091                }
092                if (this.rejectedExecutionHandler != null) {
093                        executor.setRejectedExecutionHandler(this.rejectedExecutionHandler);
094                }
095                if (this.beanName != null) {
096                        executor.setThreadNamePrefix(this.beanName + "-");
097                }
098                executor.afterPropertiesSet();
099                this.target = executor;
100        }
101
102        private void determinePoolSizeRange(ThreadPoolTaskExecutor executor) {
103                if (StringUtils.hasText(this.poolSize)) {
104                        try {
105                                int corePoolSize;
106                                int maxPoolSize;
107                                int separatorIndex = this.poolSize.indexOf('-');
108                                if (separatorIndex != -1) {
109                                        corePoolSize = Integer.parseInt(this.poolSize.substring(0, separatorIndex));
110                                        maxPoolSize = Integer.parseInt(this.poolSize.substring(separatorIndex + 1));
111                                        if (corePoolSize > maxPoolSize) {
112                                                throw new IllegalArgumentException(
113                                                                "Lower bound of pool-size range must not exceed the upper bound");
114                                        }
115                                        if (this.queueCapacity == null) {
116                                                // No queue-capacity provided, so unbounded
117                                                if (corePoolSize == 0) {
118                                                        // Actually set 'corePoolSize' to the upper bound of the range
119                                                        // but allow core threads to timeout...
120                                                        executor.setAllowCoreThreadTimeOut(true);
121                                                        corePoolSize = maxPoolSize;
122                                                }
123                                                else {
124                                                        // Non-zero lower bound implies a core-max size range...
125                                                        throw new IllegalArgumentException(
126                                                                        "A non-zero lower bound for the size range requires a queue-capacity value");
127                                                }
128                                        }
129                                }
130                                else {
131                                        Integer value = Integer.valueOf(this.poolSize);
132                                        corePoolSize = value;
133                                        maxPoolSize = value;
134                                }
135                                executor.setCorePoolSize(corePoolSize);
136                                executor.setMaxPoolSize(maxPoolSize);
137                        }
138                        catch (NumberFormatException ex) {
139                                throw new IllegalArgumentException("Invalid pool-size value [" + this.poolSize + "]: only single " +
140                                                "maximum integer (e.g. \"5\") and minimum-maximum range (e.g. \"3-5\") are supported", ex);
141                        }
142                }
143        }
144
145
146        @Override
147        @Nullable
148        public TaskExecutor getObject() {
149                return this.target;
150        }
151
152        @Override
153        public Class<? extends TaskExecutor> getObjectType() {
154                return (this.target != null ? this.target.getClass() : ThreadPoolTaskExecutor.class);
155        }
156
157        @Override
158        public boolean isSingleton() {
159                return true;
160        }
161
162
163        @Override
164        public void destroy() {
165                if (this.target != null) {
166                        this.target.destroy();
167                }
168        }
169
170}