diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ScheduledTasksEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ScheduledTasksEndpointDocumentationTests.java index ae41931373d..f5a8b95be8a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ScheduledTasksEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ScheduledTasksEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation; import java.util.Collection; +import java.util.Date; import java.util.regex.Pattern; import org.junit.Test; @@ -26,9 +27,13 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.restdocs.payload.FieldDescriptor; +import org.springframework.scheduling.Trigger; +import org.springframework.scheduling.TriggerContext; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskHolder; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; @@ -56,12 +61,12 @@ public class ScheduledTasksEndpointDocumentationTests "com.example.Processor")), responseFields( fieldWithPath("cron").description("Cron tasks, if any."), - targetFieldWithPrefix("cron.[]"), + targetFieldWithPrefix("cron.[]."), fieldWithPath("cron.[].expression") .description("Cron expression."), fieldWithPath("fixedDelay") .description("Fixed delay tasks, if any."), - targetFieldWithPrefix("fixedDelay.[]"), + targetFieldWithPrefix("fixedDelay.[]."), initialDelayWithPrefix("fixedDelay.[]."), fieldWithPath("fixedDelay.[].interval").description( "Interval, in milliseconds, between the end of the last" @@ -71,7 +76,13 @@ public class ScheduledTasksEndpointDocumentationTests targetFieldWithPrefix("fixedRate.[]."), fieldWithPath("fixedRate.[].interval").description( "Interval, in milliseconds, between the start of each execution."), - initialDelayWithPrefix("fixedRate.[].")))); + initialDelayWithPrefix("fixedRate.[]."), + fieldWithPath("custom").description( + "Tasks with custom triggers, if any."), + targetFieldWithPrefix("custom.[]."), + fieldWithPath("custom.[].trigger") + .description("Trigger for the task.")))) + .andDo(MockMvcResultHandlers.print()); } private FieldDescriptor targetFieldWithPrefix(String prefix) { @@ -109,6 +120,30 @@ public class ScheduledTasksEndpointDocumentationTests } + @Bean + public SchedulingConfigurer schedulingConfigurer() { + return (registrar) -> registrar.addTriggerTask(new CustomTriggeredRunnable(), + new CustomTrigger()); + } + + static class CustomTrigger implements Trigger { + + @Override + public Date nextExecutionTime(TriggerContext triggerContext) { + return new Date(); + } + + } + + static class CustomTriggeredRunnable implements Runnable { + + @Override + public void run() { + + } + + } + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ThreadDumpEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ThreadDumpEndpointDocumentationTests.java index 156c7becb93..a213b566b45 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ThreadDumpEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ThreadDumpEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -16,6 +16,9 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.locks.ReentrantLock; + import org.junit.Test; import org.springframework.boot.actuate.management.ThreadDumpEndpoint; @@ -42,6 +45,22 @@ public class ThreadDumpEndpointDocumentationTests @Test public void threadDump() throws Exception { + ReentrantLock lock = new ReentrantLock(); + CountDownLatch latch = new CountDownLatch(1); + new Thread(() -> { + try { + lock.lock(); + try { + latch.await(); + } + finally { + lock.unlock(); + } + } + catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + }).start(); this.mockMvc.perform(get("/actuator/threaddump")).andExpect(status().isOk()) .andDo(MockMvcRestDocumentation.document("threaddump", preprocessResponse(limit("threads")), @@ -111,7 +130,7 @@ public class ThreadDumpEndpointDocumentationTests + "synchronizer.") .optional().type(JsonFieldType.STRING), fieldWithPath( - "threads.[].lockedSynchronizers.[].identifyHashCode") + "threads.[].lockedSynchronizers.[].identityHashCode") .description( "Identity hash code of the locked " + "synchronizer.") @@ -187,6 +206,7 @@ public class ThreadDumpEndpointDocumentationTests "Time in milliseconds that the thread has spent " + "waiting. -1 if thread contention " + "monitoring is disabled")))); + latch.countDown(); } @Configuration diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpoint.java index 719c10dd6ed..af7af403f41 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * Copyright 2012-2019 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. @@ -42,7 +42,8 @@ import org.springframework.scheduling.support.PeriodicTrigger; import org.springframework.scheduling.support.ScheduledMethodRunnable; /** - * {@link Endpoint} to expose information about an application's scheduled tasks. + * {@link Endpoint @Endpoint} to expose information about an application's scheduled + * tasks. * * @author Andy Wilkinson * @since 2.0.0 @@ -78,6 +79,8 @@ public class ScheduledTasksEndpoint { private final List fixedRate; + private final List custom; + private ScheduledTasksReport( Map> descriptionsByType) { this.cron = descriptionsByType.getOrDefault(TaskType.CRON, @@ -86,6 +89,8 @@ public class ScheduledTasksEndpoint { Collections.emptyList()); this.fixedRate = descriptionsByType.getOrDefault(TaskType.FIXED_RATE, Collections.emptyList()); + this.custom = descriptionsByType.getOrDefault(TaskType.CUSTOM_TRIGGER, + Collections.emptyList()); } public List getCron() { @@ -100,6 +105,10 @@ public class ScheduledTasksEndpoint { return this.fixedRate; } + public List getCustom() { + return this.custom; + } + } /** @@ -143,7 +152,7 @@ public class ScheduledTasksEndpoint { } return new FixedDelayTaskDescription(triggerTask, periodicTrigger); } - return null; + return new CustomTriggerTaskDescription(triggerTask); } protected TaskDescription(TaskType type, Runnable runnable) { @@ -249,6 +258,26 @@ public class ScheduledTasksEndpoint { } + /** + * A description of a {@link TriggerTask} with a custom {@link Trigger}. + * + * @since 2.1.3 + */ + public static final class CustomTriggerTaskDescription extends TaskDescription { + + private final String trigger; + + private CustomTriggerTaskDescription(TriggerTask task) { + super(TaskType.CUSTOM_TRIGGER, task.getRunnable()); + this.trigger = task.getTrigger().toString(); + } + + public String getTrigger() { + return this.trigger; + } + + } + /** * A description of a {@link Task Task's} {@link Runnable}. * @@ -277,7 +306,7 @@ public class ScheduledTasksEndpoint { private enum TaskType { - CRON, FIXED_DELAY, FIXED_RATE + CRON, CUSTOM_TRIGGER, FIXED_DELAY, FIXED_RATE } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpointTests.java index e57183bd6b7..c4940a78310 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/scheduling/ScheduledTasksEndpointTests.java @@ -17,17 +17,20 @@ package org.springframework.boot.actuate.scheduling; import java.util.Collection; +import java.util.Date; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import org.junit.Test; import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.CronTaskDescription; +import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.CustomTriggerTaskDescription; import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.FixedDelayTaskDescription; import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.FixedRateTaskDescription; import org.springframework.boot.actuate.scheduling.ScheduledTasksEndpoint.ScheduledTasksReport; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; +import org.springframework.scheduling.Trigger; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.SchedulingConfigurer; @@ -53,6 +56,7 @@ public class ScheduledTasksEndpointTests { run(CronScheduledMethod.class, (tasks) -> { assertThat(tasks.getFixedDelay()).isEmpty(); assertThat(tasks.getFixedRate()).isEmpty(); + assertThat(tasks.getCustom()).isEmpty(); assertThat(tasks.getCron()).hasSize(1); CronTaskDescription description = (CronTaskDescription) tasks.getCron() .get(0); @@ -67,6 +71,7 @@ public class ScheduledTasksEndpointTests { run(CronTriggerTask.class, (tasks) -> { assertThat(tasks.getFixedRate()).isEmpty(); assertThat(tasks.getFixedDelay()).isEmpty(); + assertThat(tasks.getCustom()).isEmpty(); assertThat(tasks.getCron()).hasSize(1); CronTaskDescription description = (CronTaskDescription) tasks.getCron() .get(0); @@ -81,6 +86,7 @@ public class ScheduledTasksEndpointTests { run(FixedDelayScheduledMethod.class, (tasks) -> { assertThat(tasks.getCron()).isEmpty(); assertThat(tasks.getFixedRate()).isEmpty(); + assertThat(tasks.getCustom()).isEmpty(); assertThat(tasks.getFixedDelay()).hasSize(1); FixedDelayTaskDescription description = (FixedDelayTaskDescription) tasks .getFixedDelay().get(0); @@ -96,6 +102,7 @@ public class ScheduledTasksEndpointTests { run(FixedDelayTriggerTask.class, (tasks) -> { assertThat(tasks.getCron()).isEmpty(); assertThat(tasks.getFixedRate()).isEmpty(); + assertThat(tasks.getCustom()).isEmpty(); assertThat(tasks.getFixedDelay()).hasSize(1); FixedDelayTaskDescription description = (FixedDelayTaskDescription) tasks .getFixedDelay().get(0); @@ -111,6 +118,7 @@ public class ScheduledTasksEndpointTests { run(FixedRateScheduledMethod.class, (tasks) -> { assertThat(tasks.getCron()).isEmpty(); assertThat(tasks.getFixedDelay()).isEmpty(); + assertThat(tasks.getCustom()).isEmpty(); assertThat(tasks.getFixedRate()).hasSize(1); FixedRateTaskDescription description = (FixedRateTaskDescription) tasks .getFixedRate().get(0); @@ -126,6 +134,7 @@ public class ScheduledTasksEndpointTests { run(FixedRateTriggerTask.class, (tasks) -> { assertThat(tasks.getCron()).isEmpty(); assertThat(tasks.getFixedDelay()).isEmpty(); + assertThat(tasks.getCustom()).isEmpty(); assertThat(tasks.getFixedRate()).hasSize(1); FixedRateTaskDescription description = (FixedRateTaskDescription) tasks .getFixedRate().get(0); @@ -136,6 +145,22 @@ public class ScheduledTasksEndpointTests { }); } + @Test + public void taskWithCustomTriggerIsReported() { + run(CustomTriggerTask.class, (tasks) -> { + assertThat(tasks.getCron()).isEmpty(); + assertThat(tasks.getFixedDelay()).isEmpty(); + assertThat(tasks.getFixedRate()).isEmpty(); + assertThat(tasks.getCustom()).hasSize(1); + CustomTriggerTaskDescription description = (CustomTriggerTaskDescription) tasks + .getCustom().get(0); + assertThat(description.getRunnable().getTarget()) + .isEqualTo(CustomTriggerRunnable.class.getName()); + assertThat(description.getTrigger()) + .isEqualTo(CustomTriggerTask.trigger.toString()); + }); + } + private void run(Class configuration, Consumer consumer) { this.contextRunner.withUserConfiguration(configuration).run((context) -> consumer .accept(context.getBean(ScheduledTasksEndpoint.class).scheduledTasks())); @@ -212,6 +237,17 @@ public class ScheduledTasksEndpointTests { } + private static class CustomTriggerTask implements SchedulingConfigurer { + + private static final Trigger trigger = (context) -> new Date(); + + @Override + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { + taskRegistrar.addTriggerTask(new CustomTriggerRunnable(), trigger); + } + + } + private static class CronTriggerRunnable implements Runnable { @Override @@ -239,4 +275,13 @@ public class ScheduledTasksEndpointTests { } + private static class CustomTriggerRunnable implements Runnable { + + @Override + public void run() { + + } + + } + }