001/*
002 * Copyright 2002-2016 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.ArrayList;
020import java.util.Collections;
021import java.util.Date;
022import java.util.HashMap;
023import java.util.LinkedHashSet;
024import java.util.List;
025import java.util.Map;
026import java.util.Set;
027import java.util.concurrent.Executors;
028import java.util.concurrent.ScheduledExecutorService;
029
030import org.springframework.beans.factory.DisposableBean;
031import org.springframework.beans.factory.InitializingBean;
032import org.springframework.scheduling.TaskScheduler;
033import org.springframework.scheduling.Trigger;
034import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
035import org.springframework.scheduling.support.CronTrigger;
036import org.springframework.util.Assert;
037import org.springframework.util.CollectionUtils;
038
039/**
040 * Helper bean for registering tasks with a {@link TaskScheduler}, typically using cron
041 * expressions.
042 *
043 * <p>As of Spring 3.1, {@code ScheduledTaskRegistrar} has a more prominent user-facing
044 * role when used in conjunction with the @{@link
045 * org.springframework.scheduling.annotation.EnableAsync EnableAsync} annotation and its
046 * {@link org.springframework.scheduling.annotation.SchedulingConfigurer
047 * SchedulingConfigurer} callback interface.
048 *
049 * @author Juergen Hoeller
050 * @author Chris Beams
051 * @author Tobias Montagna-Hay
052 * @since 3.0
053 * @see org.springframework.scheduling.annotation.EnableAsync
054 * @see org.springframework.scheduling.annotation.SchedulingConfigurer
055 */
056public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean {
057
058        private TaskScheduler taskScheduler;
059
060        private ScheduledExecutorService localExecutor;
061
062        private List<TriggerTask> triggerTasks;
063
064        private List<CronTask> cronTasks;
065
066        private List<IntervalTask> fixedRateTasks;
067
068        private List<IntervalTask> fixedDelayTasks;
069
070        private final Map<Task, ScheduledTask> unresolvedTasks = new HashMap<Task, ScheduledTask>(16);
071
072        private final Set<ScheduledTask> scheduledTasks = new LinkedHashSet<ScheduledTask>(16);
073
074
075        /**
076         * Set the {@link TaskScheduler} to register scheduled tasks with.
077         */
078        public void setTaskScheduler(TaskScheduler taskScheduler) {
079                Assert.notNull(taskScheduler, "TaskScheduler must not be null");
080                this.taskScheduler = taskScheduler;
081        }
082
083        /**
084         * Set the {@link TaskScheduler} to register scheduled tasks with, or a
085         * {@link java.util.concurrent.ScheduledExecutorService} to be wrapped as a
086         * {@code TaskScheduler}.
087         */
088        public void setScheduler(Object scheduler) {
089                Assert.notNull(scheduler, "Scheduler object must not be null");
090                if (scheduler instanceof TaskScheduler) {
091                        this.taskScheduler = (TaskScheduler) scheduler;
092                }
093                else if (scheduler instanceof ScheduledExecutorService) {
094                        this.taskScheduler = new ConcurrentTaskScheduler(((ScheduledExecutorService) scheduler));
095                }
096                else {
097                        throw new IllegalArgumentException("Unsupported scheduler type: " + scheduler.getClass());
098                }
099        }
100
101        /**
102         * Return the {@link TaskScheduler} instance for this registrar (may be {@code null}).
103         */
104        public TaskScheduler getScheduler() {
105                return this.taskScheduler;
106        }
107
108
109        /**
110         * Specify triggered tasks as a Map of Runnables (the tasks) and Trigger objects
111         * (typically custom implementations of the {@link Trigger} interface).
112         */
113        public void setTriggerTasks(Map<Runnable, Trigger> triggerTasks) {
114                this.triggerTasks = new ArrayList<TriggerTask>();
115                for (Map.Entry<Runnable, Trigger> task : triggerTasks.entrySet()) {
116                        addTriggerTask(new TriggerTask(task.getKey(), task.getValue()));
117                }
118        }
119
120        /**
121         * Specify triggered tasks as a list of {@link TriggerTask} objects. Primarily used
122         * by {@code <task:*>} namespace parsing.
123         * @since 3.2
124         * @see ScheduledTasksBeanDefinitionParser
125         */
126        public void setTriggerTasksList(List<TriggerTask> triggerTasks) {
127                this.triggerTasks = triggerTasks;
128        }
129
130        /**
131         * Get the trigger tasks as an unmodifiable list of {@link TriggerTask} objects.
132         * @return the list of tasks (never {@code null})
133         * @since 4.2
134         */
135        public List<TriggerTask> getTriggerTaskList() {
136                return (this.triggerTasks != null? Collections.unmodifiableList(this.triggerTasks) :
137                                Collections.<TriggerTask>emptyList());
138        }
139
140        /**
141         * Specify triggered tasks as a Map of Runnables (the tasks) and cron expressions.
142         * @see CronTrigger
143         */
144        public void setCronTasks(Map<Runnable, String> cronTasks) {
145                this.cronTasks = new ArrayList<CronTask>();
146                for (Map.Entry<Runnable, String> task : cronTasks.entrySet()) {
147                        addCronTask(task.getKey(), task.getValue());
148                }
149        }
150
151        /**
152         * Specify triggered tasks as a list of {@link CronTask} objects. Primarily used by
153         * {@code <task:*>} namespace parsing.
154         * @since 3.2
155         * @see ScheduledTasksBeanDefinitionParser
156         */
157        public void setCronTasksList(List<CronTask> cronTasks) {
158                this.cronTasks = cronTasks;
159        }
160
161        /**
162         * Get the cron tasks as an unmodifiable list of {@link CronTask} objects.
163         * @return the list of tasks (never {@code null})
164         * @since 4.2
165         */
166        public List<CronTask> getCronTaskList() {
167                return (this.cronTasks != null ? Collections.unmodifiableList(this.cronTasks) :
168                                Collections.<CronTask>emptyList());
169        }
170
171        /**
172         * Specify triggered tasks as a Map of Runnables (the tasks) and fixed-rate values.
173         * @see TaskScheduler#scheduleAtFixedRate(Runnable, long)
174         */
175        public void setFixedRateTasks(Map<Runnable, Long> fixedRateTasks) {
176                this.fixedRateTasks = new ArrayList<IntervalTask>();
177                for (Map.Entry<Runnable, Long> task : fixedRateTasks.entrySet()) {
178                        addFixedRateTask(task.getKey(), task.getValue());
179                }
180        }
181
182        /**
183         * Specify fixed-rate tasks as a list of {@link IntervalTask} objects. Primarily used
184         * by {@code <task:*>} namespace parsing.
185         * @since 3.2
186         * @see ScheduledTasksBeanDefinitionParser
187         */
188        public void setFixedRateTasksList(List<IntervalTask> fixedRateTasks) {
189                this.fixedRateTasks = fixedRateTasks;
190        }
191
192        /**
193         * Get the fixed-rate tasks as an unmodifiable list of {@link IntervalTask} objects.
194         * @return the list of tasks (never {@code null})
195         * @since 4.2
196         */
197        public List<IntervalTask> getFixedRateTaskList() {
198                return (this.fixedRateTasks != null ? Collections.unmodifiableList(this.fixedRateTasks) :
199                                Collections.<IntervalTask>emptyList());
200        }
201
202        /**
203         * Specify triggered tasks as a Map of Runnables (the tasks) and fixed-delay values.
204         * @see TaskScheduler#scheduleWithFixedDelay(Runnable, long)
205         */
206        public void setFixedDelayTasks(Map<Runnable, Long> fixedDelayTasks) {
207                this.fixedDelayTasks = new ArrayList<IntervalTask>();
208                for (Map.Entry<Runnable, Long> task : fixedDelayTasks.entrySet()) {
209                        addFixedDelayTask(task.getKey(), task.getValue());
210                }
211        }
212
213        /**
214         * Specify fixed-delay tasks as a list of {@link IntervalTask} objects. Primarily used
215         * by {@code <task:*>} namespace parsing.
216         * @since 3.2
217         * @see ScheduledTasksBeanDefinitionParser
218         */
219        public void setFixedDelayTasksList(List<IntervalTask> fixedDelayTasks) {
220                this.fixedDelayTasks = fixedDelayTasks;
221        }
222
223        /**
224         * Get the fixed-delay tasks as an unmodifiable list of {@link IntervalTask} objects.
225         * @return the list of tasks (never {@code null})
226         * @since 4.2
227         */
228        public List<IntervalTask> getFixedDelayTaskList() {
229                return (this.fixedDelayTasks != null ? Collections.unmodifiableList(this.fixedDelayTasks) :
230                                Collections.<IntervalTask>emptyList());
231        }
232
233
234        /**
235         * Add a Runnable task to be triggered per the given {@link Trigger}.
236         * @see TaskScheduler#scheduleAtFixedRate(Runnable, long)
237         */
238        public void addTriggerTask(Runnable task, Trigger trigger) {
239                addTriggerTask(new TriggerTask(task, trigger));
240        }
241
242        /**
243         * Add a {@code TriggerTask}.
244         * @since 3.2
245         * @see TaskScheduler#scheduleAtFixedRate(Runnable, long)
246         */
247        public void addTriggerTask(TriggerTask task) {
248                if (this.triggerTasks == null) {
249                        this.triggerTasks = new ArrayList<TriggerTask>();
250                }
251                this.triggerTasks.add(task);
252        }
253
254        /**
255         * Add a Runnable task to be triggered per the given cron expression
256         */
257        public void addCronTask(Runnable task, String expression) {
258                addCronTask(new CronTask(task, expression));
259        }
260
261        /**
262         * Add a {@link CronTask}.
263         * @since 3.2
264         */
265        public void addCronTask(CronTask task) {
266                if (this.cronTasks == null) {
267                        this.cronTasks = new ArrayList<CronTask>();
268                }
269                this.cronTasks.add(task);
270        }
271
272        /**
273         * Add a {@code Runnable} task to be triggered at the given fixed-rate interval.
274         * @see TaskScheduler#scheduleAtFixedRate(Runnable, long)
275         */
276        public void addFixedRateTask(Runnable task, long interval) {
277                addFixedRateTask(new IntervalTask(task, interval, 0));
278        }
279
280        /**
281         * Add a fixed-rate {@link IntervalTask}.
282         * @since 3.2
283         * @see TaskScheduler#scheduleAtFixedRate(Runnable, long)
284         */
285        public void addFixedRateTask(IntervalTask task) {
286                if (this.fixedRateTasks == null) {
287                        this.fixedRateTasks = new ArrayList<IntervalTask>();
288                }
289                this.fixedRateTasks.add(task);
290        }
291
292        /**
293         * Add a Runnable task to be triggered with the given fixed delay.
294         * @see TaskScheduler#scheduleWithFixedDelay(Runnable, long)
295         */
296        public void addFixedDelayTask(Runnable task, long delay) {
297                addFixedDelayTask(new IntervalTask(task, delay, 0));
298        }
299
300        /**
301         * Add a fixed-delay {@link IntervalTask}.
302         * @since 3.2
303         * @see TaskScheduler#scheduleWithFixedDelay(Runnable, long)
304         */
305        public void addFixedDelayTask(IntervalTask task) {
306                if (this.fixedDelayTasks == null) {
307                        this.fixedDelayTasks = new ArrayList<IntervalTask>();
308                }
309                this.fixedDelayTasks.add(task);
310        }
311
312
313        /**
314         * Return whether this {@code ScheduledTaskRegistrar} has any tasks registered.
315         * @since 3.2
316         */
317        public boolean hasTasks() {
318                return (!CollectionUtils.isEmpty(this.triggerTasks) ||
319                                !CollectionUtils.isEmpty(this.cronTasks) ||
320                                !CollectionUtils.isEmpty(this.fixedRateTasks) ||
321                                !CollectionUtils.isEmpty(this.fixedDelayTasks));
322        }
323
324
325        /**
326         * Calls {@link #scheduleTasks()} at bean construction time.
327         */
328        @Override
329        public void afterPropertiesSet() {
330                scheduleTasks();
331        }
332
333        /**
334         * Schedule all registered tasks against the underlying {@linkplain
335         * #setTaskScheduler(TaskScheduler) task scheduler}.
336         */
337        protected void scheduleTasks() {
338                if (this.taskScheduler == null) {
339                        this.localExecutor = Executors.newSingleThreadScheduledExecutor();
340                        this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
341                }
342                if (this.triggerTasks != null) {
343                        for (TriggerTask task : this.triggerTasks) {
344                                addScheduledTask(scheduleTriggerTask(task));
345                        }
346                }
347                if (this.cronTasks != null) {
348                        for (CronTask task : this.cronTasks) {
349                                addScheduledTask(scheduleCronTask(task));
350                        }
351                }
352                if (this.fixedRateTasks != null) {
353                        for (IntervalTask task : this.fixedRateTasks) {
354                                addScheduledTask(scheduleFixedRateTask(task));
355                        }
356                }
357                if (this.fixedDelayTasks != null) {
358                        for (IntervalTask task : this.fixedDelayTasks) {
359                                addScheduledTask(scheduleFixedDelayTask(task));
360                        }
361                }
362        }
363
364        private void addScheduledTask(ScheduledTask task) {
365                if (task != null) {
366                        this.scheduledTasks.add(task);
367                }
368        }
369
370
371        /**
372         * Schedule the specified trigger task, either right away if possible
373         * or on initialization of the scheduler.
374         * @return a handle to the scheduled task, allowing to cancel it
375         * @since 4.3
376         */
377        public ScheduledTask scheduleTriggerTask(TriggerTask task) {
378                ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
379                boolean newTask = false;
380                if (scheduledTask == null) {
381                        scheduledTask = new ScheduledTask();
382                        newTask = true;
383                }
384                if (this.taskScheduler != null) {
385                        scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
386                }
387                else {
388                        addTriggerTask(task);
389                        this.unresolvedTasks.put(task, scheduledTask);
390                }
391                return (newTask ? scheduledTask : null);
392        }
393
394        /**
395         * Schedule the specified cron task, either right away if possible
396         * or on initialization of the scheduler.
397         * @return a handle to the scheduled task, allowing to cancel it
398         * (or {@code null} if processing a previously registered task)
399         * @since 4.3
400         */
401        public ScheduledTask scheduleCronTask(CronTask task) {
402                ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
403                boolean newTask = false;
404                if (scheduledTask == null) {
405                        scheduledTask = new ScheduledTask();
406                        newTask = true;
407                }
408                if (this.taskScheduler != null) {
409                        scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());
410                }
411                else {
412                        addCronTask(task);
413                        this.unresolvedTasks.put(task, scheduledTask);
414                }
415                return (newTask ? scheduledTask : null);
416        }
417
418        /**
419         * Schedule the specified fixed-rate task, either right away if possible
420         * or on initialization of the scheduler.
421         * @return a handle to the scheduled task, allowing to cancel it
422         * (or {@code null} if processing a previously registered task)
423         * @since 4.3
424         */
425        public ScheduledTask scheduleFixedRateTask(IntervalTask task) {
426                ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
427                boolean newTask = false;
428                if (scheduledTask == null) {
429                        scheduledTask = new ScheduledTask();
430                        newTask = true;
431                }
432                if (this.taskScheduler != null) {
433                        if (task.getInitialDelay() > 0) {
434                                Date startTime = new Date(System.currentTimeMillis() + task.getInitialDelay());
435                                scheduledTask.future =
436                                                this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), startTime, task.getInterval());
437                        }
438                        else {
439                                scheduledTask.future =
440                                                this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), task.getInterval());
441                        }
442                }
443                else {
444                        addFixedRateTask(task);
445                        this.unresolvedTasks.put(task, scheduledTask);
446                }
447                return (newTask ? scheduledTask : null);
448        }
449
450        /**
451         * Schedule the specified fixed-delay task, either right away if possible
452         * or on initialization of the scheduler.
453         * @return a handle to the scheduled task, allowing to cancel it
454         * (or {@code null} if processing a previously registered task)
455         * @since 4.3
456         */
457        public ScheduledTask scheduleFixedDelayTask(IntervalTask task) {
458                ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
459                boolean newTask = false;
460                if (scheduledTask == null) {
461                        scheduledTask = new ScheduledTask();
462                        newTask = true;
463                }
464                if (this.taskScheduler != null) {
465                        if (task.getInitialDelay() > 0) {
466                                Date startTime = new Date(System.currentTimeMillis() + task.getInitialDelay());
467                                scheduledTask.future =
468                                                this.taskScheduler.scheduleWithFixedDelay(task.getRunnable(), startTime, task.getInterval());
469                        }
470                        else {
471                                scheduledTask.future =
472                                                this.taskScheduler.scheduleWithFixedDelay(task.getRunnable(), task.getInterval());
473                        }
474                }
475                else {
476                        addFixedDelayTask(task);
477                        this.unresolvedTasks.put(task, scheduledTask);
478                }
479                return (newTask ? scheduledTask : null);
480        }
481
482
483        @Override
484        public void destroy() {
485                for (ScheduledTask task : this.scheduledTasks) {
486                        task.cancel();
487                }
488                if (this.localExecutor != null) {
489                        this.localExecutor.shutdownNow();
490                }
491        }
492
493}