diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java index 357dacd43367..a635509e922b 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java @@ -41,6 +41,7 @@ import org.springframework.boot.actuate.endpoint.MetricsEndpoint; import org.springframework.boot.actuate.endpoint.PublicMetrics; import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint; +import org.springframework.boot.actuate.endpoint.ScheduledTaskEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.boot.actuate.endpoint.TraceEndpoint; import org.springframework.boot.actuate.health.HealthAggregator; @@ -185,6 +186,13 @@ public ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoi return new ConfigurationPropertiesReportEndpoint(); } + + @Bean + @ConditionalOnMissingBean + public ScheduledTaskEndpoint scheduledTaskEndpoint() { + return new ScheduledTaskEndpoint(); + } + @Configuration @ConditionalOnBean(Flyway.class) @ConditionalOnClass(Flyway.class) diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ScheduledTaskEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ScheduledTaskEndpoint.java new file mode 100644 index 000000000000..36670e3422fd --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ScheduledTaskEndpoint.java @@ -0,0 +1,179 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.endpoint; + + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.scheduling.annotation.SchedulingConfigurer; +import org.springframework.scheduling.config.CronTask; +import org.springframework.scheduling.config.IntervalTask; +import org.springframework.scheduling.config.ScheduledTaskRegistrar; +import org.springframework.scheduling.config.Task; +import org.springframework.scheduling.config.TriggerTask; +import org.springframework.scheduling.support.ScheduledMethodRunnable; + +/** + * {@link Endpoint} to expose scheduled task information. + * + * @author Yunkun Huang + * @since 2.0.0 + */ +@ConfigurationProperties(prefix = "endpoints.schedules") +public class ScheduledTaskEndpoint + extends AbstractEndpoint> + implements SchedulingConfigurer { + private List list = new ArrayList<>(); + + public ScheduledTaskEndpoint() { + super("schedules"); + } + + /** + * Create a new {@link ScheduledTaskEndpoint} instance. + */ + @Override + public List invoke() { + return this.list; + } + + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + this.list.addAll(build(taskRegistrar.getFixedRateTaskList(), ScheduledType.FIXEDRATE)); + this.list.addAll(build(taskRegistrar.getCronTaskList(), ScheduledType.CRON)); + this.list.addAll(build(taskRegistrar.getFixedDelayTaskList(), ScheduledType.FIXEDDELAY)); + this.list.addAll(build(taskRegistrar.getTriggerTaskList(), ScheduledType.TRIGGER)); + } + + private List build(final List tasks, final ScheduledType type) { + return tasks.stream().map(task -> { + ScheduledTaskInformation scheduledTaskInformation = new ScheduledTaskInformation(); + scheduledTaskInformation.setType(type); + scheduledTaskInformation.setName(getGenericName(task.getRunnable())); + if (task instanceof IntervalTask) { + IntervalTask intervalTask = (IntervalTask) task; + scheduledTaskInformation.setInitialDelay(intervalTask.getInitialDelay()); + scheduledTaskInformation.setInterval(intervalTask.getInterval()); + } + if (task instanceof CronTask) { + CronTask cronTask = (CronTask) task; + scheduledTaskInformation.setExpression(cronTask.getExpression()); + } + if (task instanceof TriggerTask) { + TriggerTask triggerTask = (TriggerTask) task; + scheduledTaskInformation.setTrigger(triggerTask.getTrigger().toString()); + } + return scheduledTaskInformation; + }).collect(Collectors.toList()); + } + + private String getGenericName(Runnable runnable) { + if (runnable instanceof ScheduledMethodRunnable) { + return ((ScheduledMethodRunnable) runnable).getMethod().toGenericString(); + } + return runnable.toString(); + } + + /** + * Information for one scheduled task. + */ + @JsonInclude(Include.NON_EMPTY) + public class ScheduledTaskInformation { + private ScheduledType type; + private long interval; + private long initialDelay; + private String name; + private String expression; + private String trigger; + + public ScheduledType getType() { + return this.type; + } + + public void setType(ScheduledType type) { + this.type = type; + } + + public long getInterval() { + return this.interval; + } + + public void setInterval(long interval) { + this.interval = interval; + } + + public long getInitialDelay() { + return this.initialDelay; + } + + public void setInitialDelay(long initialDelay) { + this.initialDelay = initialDelay; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + public void setExpression(String expression) { + this.expression = expression; + } + + public String getExpression() { + return this.expression; + } + + public void setTrigger(String trigger) { + this.trigger = trigger; + } + + public String getTrigger() { + return this.trigger; + } + } + + /** + * Four different scheduled task type. + */ + public enum ScheduledType { + /** + * Execute with a fixed period in milliseconds between invocations. + */ + FIXEDRATE, + /** + * Execute with a cron-like expression. + */ + CRON, + /** + * Execute with a fixed period in milliseconds between the end of the last invocation and the start of the next. + */ + FIXEDDELAY, + /** + * Execute by a trigger. + */ + TRIGGER + } +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ScheduledTaskEndpointTest.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ScheduledTaskEndpointTest.java new file mode 100644 index 000000000000..79ca8e67cdf0 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ScheduledTaskEndpointTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.endpoint; + +import java.util.List; +import java.util.Optional; + +import org.junit.Test; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + + +import static org.assertj.core.api.Assertions.assertThat; + +public class ScheduledTaskEndpointTest extends AbstractEndpointTests { + + public ScheduledTaskEndpointTest() { + super(Config.class, ScheduledTaskEndpoint.class, "schedules", true, "endpoints.schedules"); + } + + @Test + public void shouldGetTaskInformation() throws Exception { + List invoke = getEndpointBean().invoke(); + + assertThat(invoke.size()).isEqualTo(3); + + Optional fixedRateTaskOptional = + filterTaskInformation(invoke, ScheduledTaskEndpoint.ScheduledType.FIXEDRATE); + assertThat(fixedRateTaskOptional.isPresent()).isTrue(); + assertThat(fixedRateTaskOptional.get().getInterval()).isEqualTo(2000); + assertThat(fixedRateTaskOptional.get().getInitialDelay()).isEqualTo(0); + + Optional cronTaskOptional = + filterTaskInformation(invoke, ScheduledTaskEndpoint.ScheduledType.CRON); + assertThat(cronTaskOptional.isPresent()).isTrue(); + assertThat(cronTaskOptional.get().getExpression()).isEqualTo("* */1 * * * *"); + assertThat(cronTaskOptional.get().getInitialDelay()).isEqualTo(0); + assertThat(cronTaskOptional.get().getName()).contains("Config.cronMethod"); + } + + private Optional filterTaskInformation( + List invoke, ScheduledTaskEndpoint.ScheduledType type) { + return invoke.stream().filter(scheduledTaskInformation -> scheduledTaskInformation.getType() + .equals(type)).findFirst(); + } + + @Configuration + @EnableConfigurationProperties + @EnableScheduling + public static class Config { + + @Bean + public ScheduledTaskEndpoint endpoint() { + return new ScheduledTaskEndpoint(); + } + + @Scheduled(fixedDelay = 1000) + public void fixedDelayMethod() { + + } + + @Scheduled(fixedRate = 2000) + public void fixedRateMethod() { + + } + + @Scheduled(cron = "* */1 * * * *") + public void cronMethod() { + + } + } +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HalBrowserMvcEndpointVanillaIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HalBrowserMvcEndpointVanillaIntegrationTests.java index a9cb5ee87c43..a0107914e38e 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HalBrowserMvcEndpointVanillaIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/HalBrowserMvcEndpointVanillaIntegrationTests.java @@ -123,7 +123,7 @@ public void endpointsAllListed() throws Exception { @Test public void endpointsEachHaveSelf() throws Exception { Set collections = new HashSet<>(Arrays.asList("/trace", "/beans", "/dump", - "/heapdump", "/loggers", "/auditevents")); + "/heapdump", "/loggers", "/auditevents", "/schedules")); for (MvcEndpoint endpoint : this.mvcEndpoints.getEndpoints()) { String path = endpoint.getPath(); if (collections.contains(path)) {