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