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.actuate.scheduling;
018
019import java.lang.reflect.Method;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.LinkedHashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.Objects;
026import java.util.function.Function;
027import java.util.stream.Collectors;
028
029import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
030import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
031import org.springframework.scheduling.Trigger;
032import org.springframework.scheduling.config.CronTask;
033import org.springframework.scheduling.config.FixedDelayTask;
034import org.springframework.scheduling.config.FixedRateTask;
035import org.springframework.scheduling.config.IntervalTask;
036import org.springframework.scheduling.config.ScheduledTask;
037import org.springframework.scheduling.config.ScheduledTaskHolder;
038import org.springframework.scheduling.config.Task;
039import org.springframework.scheduling.config.TriggerTask;
040import org.springframework.scheduling.support.CronTrigger;
041import org.springframework.scheduling.support.PeriodicTrigger;
042import org.springframework.scheduling.support.ScheduledMethodRunnable;
043
044/**
045 * {@link Endpoint} to expose information about an application's scheduled tasks.
046 *
047 * @author Andy Wilkinson
048 * @since 2.0.0
049 */
050@Endpoint(id = "scheduledtasks")
051public class ScheduledTasksEndpoint {
052
053        private final Collection<ScheduledTaskHolder> scheduledTaskHolders;
054
055        public ScheduledTasksEndpoint(Collection<ScheduledTaskHolder> scheduledTaskHolders) {
056                this.scheduledTaskHolders = scheduledTaskHolders;
057        }
058
059        @ReadOperation
060        public ScheduledTasksReport scheduledTasks() {
061                Map<TaskType, List<TaskDescription>> descriptionsByType = this.scheduledTaskHolders
062                                .stream().flatMap((holder) -> holder.getScheduledTasks().stream())
063                                .map(ScheduledTask::getTask).map(TaskDescription::of)
064                                .filter(Objects::nonNull)
065                                .collect(Collectors.groupingBy(TaskDescription::getType));
066                return new ScheduledTasksReport(descriptionsByType);
067        }
068
069        /**
070         * A report of an application's scheduled {@link Task Tasks}, primarily intended for
071         * serialization to JSON.
072         */
073        public static final class ScheduledTasksReport {
074
075                private final List<TaskDescription> cron;
076
077                private final List<TaskDescription> fixedDelay;
078
079                private final List<TaskDescription> fixedRate;
080
081                private ScheduledTasksReport(
082                                Map<TaskType, List<TaskDescription>> descriptionsByType) {
083                        this.cron = descriptionsByType.getOrDefault(TaskType.CRON,
084                                        Collections.emptyList());
085                        this.fixedDelay = descriptionsByType.getOrDefault(TaskType.FIXED_DELAY,
086                                        Collections.emptyList());
087                        this.fixedRate = descriptionsByType.getOrDefault(TaskType.FIXED_RATE,
088                                        Collections.emptyList());
089                }
090
091                public List<TaskDescription> getCron() {
092                        return this.cron;
093                }
094
095                public List<TaskDescription> getFixedDelay() {
096                        return this.fixedDelay;
097                }
098
099                public List<TaskDescription> getFixedRate() {
100                        return this.fixedRate;
101                }
102
103        }
104
105        /**
106         * Base class for descriptions of a {@link Task}.
107         */
108        public abstract static class TaskDescription {
109
110                private static final Map<Class<? extends Task>, Function<Task, TaskDescription>> DESCRIBERS = new LinkedHashMap<>();
111
112                static {
113                        DESCRIBERS.put(FixedRateTask.class,
114                                        (task) -> new FixedRateTaskDescription((FixedRateTask) task));
115                        DESCRIBERS.put(FixedDelayTask.class,
116                                        (task) -> new FixedDelayTaskDescription((FixedDelayTask) task));
117                        DESCRIBERS.put(CronTask.class,
118                                        (task) -> new CronTaskDescription((CronTask) task));
119                        DESCRIBERS.put(TriggerTask.class,
120                                        (task) -> describeTriggerTask((TriggerTask) task));
121                }
122
123                private final TaskType type;
124
125                private final RunnableDescription runnable;
126
127                private static TaskDescription of(Task task) {
128                        return DESCRIBERS.entrySet().stream()
129                                        .filter((entry) -> entry.getKey().isInstance(task))
130                                        .map((entry) -> entry.getValue().apply(task)).findFirst()
131                                        .orElse(null);
132                }
133
134                private static TaskDescription describeTriggerTask(TriggerTask triggerTask) {
135                        Trigger trigger = triggerTask.getTrigger();
136                        if (trigger instanceof CronTrigger) {
137                                return new CronTaskDescription(triggerTask, (CronTrigger) trigger);
138                        }
139                        if (trigger instanceof PeriodicTrigger) {
140                                PeriodicTrigger periodicTrigger = (PeriodicTrigger) trigger;
141                                if (periodicTrigger.isFixedRate()) {
142                                        return new FixedRateTaskDescription(triggerTask, periodicTrigger);
143                                }
144                                return new FixedDelayTaskDescription(triggerTask, periodicTrigger);
145                        }
146                        return null;
147                }
148
149                protected TaskDescription(TaskType type, Runnable runnable) {
150                        this.type = type;
151                        this.runnable = new RunnableDescription(runnable);
152                }
153
154                private TaskType getType() {
155                        return this.type;
156                }
157
158                public final RunnableDescription getRunnable() {
159                        return this.runnable;
160                }
161
162        }
163
164        /**
165         * A description of an {@link IntervalTask}.
166         */
167        public static class IntervalTaskDescription extends TaskDescription {
168
169                private final long initialDelay;
170
171                private final long interval;
172
173                protected IntervalTaskDescription(TaskType type, IntervalTask task) {
174                        super(type, task.getRunnable());
175                        this.initialDelay = task.getInitialDelay();
176                        this.interval = task.getInterval();
177                }
178
179                protected IntervalTaskDescription(TaskType type, TriggerTask task,
180                                PeriodicTrigger trigger) {
181                        super(type, task.getRunnable());
182                        this.initialDelay = trigger.getInitialDelay();
183                        this.interval = trigger.getPeriod();
184                }
185
186                public long getInitialDelay() {
187                        return this.initialDelay;
188                }
189
190                public long getInterval() {
191                        return this.interval;
192                }
193
194        }
195
196        /**
197         * A description of a {@link FixedDelayTask} or a {@link TriggerTask} with a
198         * fixed-delay {@link PeriodicTrigger}.
199         */
200        public static final class FixedDelayTaskDescription extends IntervalTaskDescription {
201
202                private FixedDelayTaskDescription(FixedDelayTask task) {
203                        super(TaskType.FIXED_DELAY, task);
204                }
205
206                private FixedDelayTaskDescription(TriggerTask task, PeriodicTrigger trigger) {
207                        super(TaskType.FIXED_DELAY, task, trigger);
208                }
209
210        }
211
212        /**
213         * A description of a {@link FixedRateTask} or a {@link TriggerTask} with a fixed-rate
214         * {@link PeriodicTrigger}.
215         */
216        public static final class FixedRateTaskDescription extends IntervalTaskDescription {
217
218                private FixedRateTaskDescription(FixedRateTask task) {
219                        super(TaskType.FIXED_RATE, task);
220                }
221
222                private FixedRateTaskDescription(TriggerTask task, PeriodicTrigger trigger) {
223                        super(TaskType.FIXED_RATE, task, trigger);
224                }
225
226        }
227
228        /**
229         * A description of a {@link CronTask} or a {@link TriggerTask} with a
230         * {@link CronTrigger}.
231         */
232        public static final class CronTaskDescription extends TaskDescription {
233
234                private final String expression;
235
236                private CronTaskDescription(CronTask task) {
237                        super(TaskType.CRON, task.getRunnable());
238                        this.expression = task.getExpression();
239                }
240
241                private CronTaskDescription(TriggerTask task, CronTrigger trigger) {
242                        super(TaskType.CRON, task.getRunnable());
243                        this.expression = trigger.getExpression();
244                }
245
246                public String getExpression() {
247                        return this.expression;
248                }
249
250        }
251
252        /**
253         * A description of a {@link Task Task's} {@link Runnable}.
254         *
255         * @author Andy Wilkinson
256         */
257        public static final class RunnableDescription {
258
259                private final String target;
260
261                private RunnableDescription(Runnable runnable) {
262                        if (runnable instanceof ScheduledMethodRunnable) {
263                                Method method = ((ScheduledMethodRunnable) runnable).getMethod();
264                                this.target = method.getDeclaringClass().getName() + "."
265                                                + method.getName();
266                        }
267                        else {
268                                this.target = runnable.getClass().getName();
269                        }
270                }
271
272                public String getTarget() {
273                        return this.target;
274                }
275
276        }
277
278        private enum TaskType {
279
280                CRON, FIXED_DELAY, FIXED_RATE
281
282        }
283
284}