001/*
002 * Copyright 2002-2015 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.concurrent;
018
019import java.util.concurrent.ExecutorService;
020import java.util.concurrent.Executors;
021import java.util.concurrent.RejectedExecutionHandler;
022import java.util.concurrent.ScheduledExecutorService;
023import java.util.concurrent.ScheduledThreadPoolExecutor;
024import java.util.concurrent.ThreadFactory;
025
026import org.springframework.beans.factory.FactoryBean;
027import org.springframework.lang.UsesJava7;
028import org.springframework.scheduling.support.DelegatingErrorHandlingRunnable;
029import org.springframework.scheduling.support.TaskUtils;
030import org.springframework.util.Assert;
031import org.springframework.util.ClassUtils;
032import org.springframework.util.ObjectUtils;
033
034/**
035 * {@link org.springframework.beans.factory.FactoryBean} that sets up
036 * a {@link java.util.concurrent.ScheduledExecutorService}
037 * (by default: a {@link java.util.concurrent.ScheduledThreadPoolExecutor})
038 * and exposes it for bean references.
039 *
040 * <p>Allows for registration of {@link ScheduledExecutorTask ScheduledExecutorTasks},
041 * automatically starting the {@link ScheduledExecutorService} on initialization and
042 * cancelling it on destruction of the context. In scenarios that only require static
043 * registration of tasks at startup, there is no need to access the
044 * {@link ScheduledExecutorService} instance itself in application code at all;
045 * {@code ScheduledExecutorFactoryBean} is then just being used for lifecycle integration.
046 *
047 * <p>For an alternative, you may set up a {@link ScheduledThreadPoolExecutor} instance
048 * directly using constructor injection, or use a factory method definition that points
049 * to the {@link java.util.concurrent.Executors} class.
050 * <b>This is strongly recommended in particular for common {@code @Bean} methods in
051 * configuration classes, where this {@code FactoryBean} variant would force you to
052 * return the {@code FactoryBean} type instead of {@code ScheduledExecutorService}.</b>
053 *
054 * <p>Note that {@link java.util.concurrent.ScheduledExecutorService}
055 * uses a {@link Runnable} instance that is shared between repeated executions,
056 * in contrast to Quartz which instantiates a new Job for each execution.
057 *
058 * <p><b>WARNING:</b> {@link Runnable Runnables} submitted via a native
059 * {@link java.util.concurrent.ScheduledExecutorService} are removed from
060 * the execution schedule once they throw an exception. If you would prefer
061 * to continue execution after such an exception, switch this FactoryBean's
062 * {@link #setContinueScheduledExecutionAfterException "continueScheduledExecutionAfterException"}
063 * property to "true".
064 *
065 * @author Juergen Hoeller
066 * @since 2.0
067 * @see #setPoolSize
068 * @see #setRemoveOnCancelPolicy
069 * @see #setThreadFactory
070 * @see ScheduledExecutorTask
071 * @see java.util.concurrent.ScheduledExecutorService
072 * @see java.util.concurrent.ScheduledThreadPoolExecutor
073 */
074@SuppressWarnings("serial")
075public class ScheduledExecutorFactoryBean extends ExecutorConfigurationSupport
076                implements FactoryBean<ScheduledExecutorService> {
077
078        // ScheduledThreadPoolExecutor.setRemoveOnCancelPolicy(boolean) only available on JDK 7+
079        private static final boolean setRemoveOnCancelPolicyAvailable =
080                        ClassUtils.hasMethod(ScheduledThreadPoolExecutor.class, "setRemoveOnCancelPolicy", boolean.class);
081
082
083        private int poolSize = 1;
084
085        private ScheduledExecutorTask[] scheduledExecutorTasks;
086
087        private boolean removeOnCancelPolicy = false;
088
089        private boolean continueScheduledExecutionAfterException = false;
090
091        private boolean exposeUnconfigurableExecutor = false;
092
093        private ScheduledExecutorService exposedExecutor;
094
095
096        /**
097         * Set the ScheduledExecutorService's pool size.
098         * Default is 1.
099         */
100        public void setPoolSize(int poolSize) {
101                Assert.isTrue(poolSize > 0, "'poolSize' must be 1 or higher");
102                this.poolSize = poolSize;
103        }
104
105        /**
106         * Register a list of ScheduledExecutorTask objects with the ScheduledExecutorService
107         * that this FactoryBean creates. Depending on each ScheduledExecutorTask's settings,
108         * it will be registered via one of ScheduledExecutorService's schedule methods.
109         * @see java.util.concurrent.ScheduledExecutorService#schedule(java.lang.Runnable, long, java.util.concurrent.TimeUnit)
110         * @see java.util.concurrent.ScheduledExecutorService#scheduleWithFixedDelay(java.lang.Runnable, long, long, java.util.concurrent.TimeUnit)
111         * @see java.util.concurrent.ScheduledExecutorService#scheduleAtFixedRate(java.lang.Runnable, long, long, java.util.concurrent.TimeUnit)
112         */
113        public void setScheduledExecutorTasks(ScheduledExecutorTask... scheduledExecutorTasks) {
114                this.scheduledExecutorTasks = scheduledExecutorTasks;
115        }
116
117        /**
118         * Set the remove-on-cancel mode on {@link ScheduledThreadPoolExecutor} (JDK 7+).
119         * <p>Default is {@code false}. If set to {@code true}, the target executor will be
120         * switched into remove-on-cancel mode (if possible, with a soft fallback otherwise).
121         */
122        public void setRemoveOnCancelPolicy(boolean removeOnCancelPolicy) {
123                this.removeOnCancelPolicy = removeOnCancelPolicy;
124        }
125
126        /**
127         * Specify whether to continue the execution of a scheduled task
128         * after it threw an exception.
129         * <p>Default is "false", matching the native behavior of a
130         * {@link java.util.concurrent.ScheduledExecutorService}.
131         * Switch this flag to "true" for exception-proof execution of each task,
132         * continuing scheduled execution as in the case of successful execution.
133         * @see java.util.concurrent.ScheduledExecutorService#scheduleAtFixedRate
134         */
135        public void setContinueScheduledExecutionAfterException(boolean continueScheduledExecutionAfterException) {
136                this.continueScheduledExecutionAfterException = continueScheduledExecutionAfterException;
137        }
138
139        /**
140         * Specify whether this FactoryBean should expose an unconfigurable
141         * decorator for the created executor.
142         * <p>Default is "false", exposing the raw executor as bean reference.
143         * Switch this flag to "true" to strictly prevent clients from
144         * modifying the executor's configuration.
145         * @see java.util.concurrent.Executors#unconfigurableScheduledExecutorService
146         */
147        public void setExposeUnconfigurableExecutor(boolean exposeUnconfigurableExecutor) {
148                this.exposeUnconfigurableExecutor = exposeUnconfigurableExecutor;
149        }
150
151
152        @Override
153        @UsesJava7
154        protected ExecutorService initializeExecutor(
155                        ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
156
157                ScheduledExecutorService executor =
158                                createExecutor(this.poolSize, threadFactory, rejectedExecutionHandler);
159
160                if (this.removeOnCancelPolicy) {
161                        if (setRemoveOnCancelPolicyAvailable && executor instanceof ScheduledThreadPoolExecutor) {
162                                ((ScheduledThreadPoolExecutor) executor).setRemoveOnCancelPolicy(true);
163                        }
164                        else {
165                                logger.info("Could not apply remove-on-cancel policy - not a Java 7+ ScheduledThreadPoolExecutor");
166                        }
167                }
168
169                // Register specified ScheduledExecutorTasks, if necessary.
170                if (!ObjectUtils.isEmpty(this.scheduledExecutorTasks)) {
171                        registerTasks(this.scheduledExecutorTasks, executor);
172                }
173
174                // Wrap executor with an unconfigurable decorator.
175                this.exposedExecutor = (this.exposeUnconfigurableExecutor ?
176                                Executors.unconfigurableScheduledExecutorService(executor) : executor);
177
178                return executor;
179        }
180
181        /**
182         * Create a new {@link ScheduledExecutorService} instance.
183         * <p>The default implementation creates a {@link ScheduledThreadPoolExecutor}.
184         * Can be overridden in subclasses to provide custom {@link ScheduledExecutorService} instances.
185         * @param poolSize the specified pool size
186         * @param threadFactory the ThreadFactory to use
187         * @param rejectedExecutionHandler the RejectedExecutionHandler to use
188         * @return a new ScheduledExecutorService instance
189         * @see #afterPropertiesSet()
190         * @see java.util.concurrent.ScheduledThreadPoolExecutor
191         */
192        protected ScheduledExecutorService createExecutor(
193                        int poolSize, ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
194
195                return new ScheduledThreadPoolExecutor(poolSize, threadFactory, rejectedExecutionHandler);
196        }
197
198        /**
199         * Register the specified {@link ScheduledExecutorTask ScheduledExecutorTasks}
200         * on the given {@link ScheduledExecutorService}.
201         * @param tasks the specified ScheduledExecutorTasks (never empty)
202         * @param executor the ScheduledExecutorService to register the tasks on.
203         */
204        protected void registerTasks(ScheduledExecutorTask[] tasks, ScheduledExecutorService executor) {
205                for (ScheduledExecutorTask task : tasks) {
206                        Runnable runnable = getRunnableToSchedule(task);
207                        if (task.isOneTimeTask()) {
208                                executor.schedule(runnable, task.getDelay(), task.getTimeUnit());
209                        }
210                        else {
211                                if (task.isFixedRate()) {
212                                        executor.scheduleAtFixedRate(runnable, task.getDelay(), task.getPeriod(), task.getTimeUnit());
213                                }
214                                else {
215                                        executor.scheduleWithFixedDelay(runnable, task.getDelay(), task.getPeriod(), task.getTimeUnit());
216                                }
217                        }
218                }
219        }
220
221        /**
222         * Determine the actual Runnable to schedule for the given task.
223         * <p>Wraps the task's Runnable in a
224         * {@link org.springframework.scheduling.support.DelegatingErrorHandlingRunnable}
225         * that will catch and log the Exception. If necessary, it will suppress the
226         * Exception according to the
227         * {@link #setContinueScheduledExecutionAfterException "continueScheduledExecutionAfterException"}
228         * flag.
229         * @param task the ScheduledExecutorTask to schedule
230         * @return the actual Runnable to schedule (may be a decorator)
231         */
232        protected Runnable getRunnableToSchedule(ScheduledExecutorTask task) {
233                return (this.continueScheduledExecutionAfterException ?
234                                new DelegatingErrorHandlingRunnable(task.getRunnable(), TaskUtils.LOG_AND_SUPPRESS_ERROR_HANDLER) :
235                                new DelegatingErrorHandlingRunnable(task.getRunnable(), TaskUtils.LOG_AND_PROPAGATE_ERROR_HANDLER));
236        }
237
238
239        @Override
240        public ScheduledExecutorService getObject() {
241                return this.exposedExecutor;
242        }
243
244        @Override
245        public Class<? extends ScheduledExecutorService> getObjectType() {
246                return (this.exposedExecutor != null ? this.exposedExecutor.getClass() : ScheduledExecutorService.class);
247        }
248
249        @Override
250        public boolean isSingleton() {
251                return true;
252        }
253
254}