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}