From ef986b13e5d3fccdd2a89fd2bba4c630b123b91d Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 6 Apr 2021 15:44:24 +0100 Subject: [PATCH] Polish Quartz endpoint documentation See gh-10364 --- .../src/docs/asciidoc/endpoints/quartz.adoc | 44 ++++++-- .../AbstractEndpointDocumentationTests.java | 2 +- .../QuartzEndpointDocumentationTests.java | 105 +++++++++++------- 3 files changed, 96 insertions(+), 55 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/quartz.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/quartz.adoc index 384e34430c..a650f44892 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/quartz.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/docs/asciidoc/endpoints/quartz.adoc @@ -157,7 +157,12 @@ include::{snippets}/quartz/trigger-details-cron/curl-request.adoc[] The preceding example retrieves the details of trigger identified by the `samples` group and `example` name. -The resulting response has a common structure and a specific additional object according to the trigger implementation. + + +[[quartz-trigger-common-response-structure]] +=== Common Response Structure + +The response has a common structure and an additional object that is specific to the trigger's type. There are five supported types: * `cron` for `CronTrigger` @@ -166,9 +171,14 @@ There are five supported types: * `calendarInterval` for `CalendarIntervalTrigger` * `custom` for any other trigger implementations +The following table describes the structure of the common elements of the response: + +[cols="2,1,3"] +include::{snippets}/quartz/trigger-details-common/response-fields.adoc[] + -[[quartz-trigger-cron]] +[[quartz-trigger-cron-response-structure]] === Cron Trigger Response Structure A cron trigger defines the cron expression that is used to determine when it has to fire. @@ -177,14 +187,16 @@ The resulting response for such a trigger implementation is similar to the follo include::{snippets}/quartz/trigger-details-cron/http-response.adoc[] -The following table describes the structure of the response: +Much of the response is common to all trigger types. +The structure of the common elements of the response was <>. +The following table describes the structure of the parts of the response that are specific to cron triggers: [cols="2,1,3"] include::{snippets}/quartz/trigger-details-cron/response-fields.adoc[] -[[quartz-trigger-simple]] +[[quartz-trigger-simple-response-structure]] === Simple Trigger Response Structure A simple trigger is used to fire a Job at a given moment in time, and optionally repeated at a specified interval. @@ -193,14 +205,16 @@ The resulting response for such a trigger implementation is similar to the follo include::{snippets}/quartz/trigger-details-simple/http-response.adoc[] -The following table describes the structure of the response: +Much of the response is common to all trigger types. +The structure of the common elements of the response was <>. +The following table describes the structure of the parts of the response that are specific to simple triggers: [cols="2,1,3"] include::{snippets}/quartz/trigger-details-simple/response-fields.adoc[] -[[quartz-trigger-daily-time-interval]] +[[quartz-trigger-daily-time-interval-response-structure]] === Daily Time Interval Trigger Response Structure A daily time interval trigger is used to fire a Job based upon daily repeating time intervals. @@ -209,30 +223,34 @@ The resulting response for such a trigger implementation is similar to the follo include::{snippets}/quartz/trigger-details-daily-time-interval/http-response.adoc[] -The following table describes the structure of the response: +Much of the response is common to all trigger types. +The structure of the common elements of the response was <>. +The following table describes the structure of the parts of the response that are specific to daily time interval triggers: [cols="2,1,3"] include::{snippets}/quartz/trigger-details-daily-time-interval/response-fields.adoc[] -[[quartz-trigger-calendar-interval]] +[[quartz-trigger-calendar-interval-response-structure]] === Calendar Interval Trigger Response Structure -A daily time interval trigger is used to fire a Job based upon repeating calendar time intervals. +A calendar interval trigger is used to fire a Job based upon repeating calendar time intervals. The resulting response for such a trigger implementation is similar to the following: include::{snippets}/quartz/trigger-details-calendar-interval/http-response.adoc[] -The following table describes the structure of the response: +Much of the response is common to all trigger types. +The structure of the common elements of the response was <>. +The following table describes the structure of the parts of the response that are specific to calendar interval triggers: [cols="2,1,3"] include::{snippets}/quartz/trigger-details-calendar-interval/response-fields.adoc[] -[[quartz-trigger-custom]] +[[quartz-trigger-custom-response-structure]] === Custom Trigger Response Structure A custom trigger is any other implementation. @@ -241,7 +259,9 @@ The resulting response for such a trigger implementation is similar to the follo include::{snippets}/quartz/trigger-details-custom/http-response.adoc[] -The following table describes the structure of the response: +Much of the response is common to all trigger types. +The structure of the common elements of the response was <>. +The following table describes the structure of the parts of the response that are specific to custom triggers: [cols="2,1,3"] include::{snippets}/quartz/trigger-details-custom/response-fields.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AbstractEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AbstractEndpointDocumentationTests.java index e496dfb07d..501c09d307 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AbstractEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AbstractEndpointDocumentationTests.java @@ -59,7 +59,7 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWit "management.endpoints.web.exposure.include=*", "spring.jackson.default-property-inclusion=non_null" }) public abstract class AbstractEndpointDocumentationTests { - protected String describeEnumValues(Class> enumType) { + protected static String describeEnumValues(Class> enumType) { return StringUtils.collectionToDelimitedString(Stream.of(enumType.getEnumConstants()) .map((constant) -> "`" + constant.name() + "`").collect(Collectors.toList()), ", "); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/QuartzEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/QuartzEndpointDocumentationTests.java index acb1545543..87cb2b9f1b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/QuartzEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/QuartzEndpointDocumentationTests.java @@ -67,7 +67,9 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.relaxedResponseFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -88,24 +90,24 @@ class QuartzEndpointDocumentationTests extends MockMvcEndpointDocumentationTests private static final JobDetail jobThree = JobBuilder.newJob(Job.class).withIdentity("jobThree", "tests").build(); - private static final CronTrigger triggerOne = TriggerBuilder.newTrigger().forJob(jobOne).withPriority(3) + private static final CronTrigger cronTrigger = TriggerBuilder.newTrigger().forJob(jobOne).withPriority(3) .withDescription("3AM on weekdays").withIdentity("3am-weekdays", "samples") .withSchedule( CronScheduleBuilder.atHourAndMinuteOnGivenDaysOfWeek(3, 0, 1, 2, 3, 4, 5).inTimeZone(timeZone)) .build(); - private static final SimpleTrigger triggerTwo = TriggerBuilder.newTrigger().forJob(jobOne).withPriority(7) + private static final SimpleTrigger simpleTrigger = TriggerBuilder.newTrigger().forJob(jobOne).withPriority(7) .withDescription("Once a day").withIdentity("every-day", "samples") .withSchedule(SimpleScheduleBuilder.repeatHourlyForever(24)).build(); - private static final CalendarIntervalTrigger triggerThree = TriggerBuilder.newTrigger().forJob(jobTwo) + private static final CalendarIntervalTrigger calendarIntervalTrigger = TriggerBuilder.newTrigger().forJob(jobTwo) .withDescription("Once a week").withIdentity("once-a-week", "samples") .withSchedule(CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withIntervalInWeeks(1) .inTimeZone(timeZone)) .build(); - private static final DailyTimeIntervalTrigger triggerFour = TriggerBuilder.newTrigger().forJob(jobThree) - .withDescription("Every hour between 9AM and 6PM on Tuesday and Thursday") + private static final DailyTimeIntervalTrigger dailyTimeIntervalTrigger = TriggerBuilder.newTrigger() + .forJob(jobThree).withDescription("Every hour between 9AM and 6PM on Tuesday and Thursday") .withIdentity("every-hour-tue-thu") .withSchedule(DailyTimeIntervalScheduleBuilder.dailyTimeIntervalSchedule() .onDaysOfTheWeek(Calendar.TUESDAY, Calendar.THURSDAY) @@ -148,9 +150,10 @@ class QuartzEndpointDocumentationTests extends MockMvcEndpointDocumentationTests fieldWithPath("name").description("Name of the trigger."), fieldWithPath("description").description("Description of the trigger, if any."), fieldWithPath("state") - .description("State of the trigger, can be NONE, NORMAL, PAUSED, COMPLETE, ERROR, or BLOCKED."), + .description("State of the trigger (" + describeEnumValues(TriggerState.class) + ")."), fieldWithPath("type").description( - "Type of the trigger, determine the key of the object containing implementation-specific details."), + "Type of the trigger (`calendarInterval`, `cron`, `custom`, `dailyTimeInterval`, `simple`). " + + "Determines the key of the object containing type-specific details."), fieldWithPath("calendarName").description("Name of the Calendar associated with this Trigger, if any."), startTime(""), endTime(""), previousFireTime(""), nextFireTime(""), priority(""), fieldWithPath("finalFireTime").optional().type(JsonFieldType.STRING) @@ -164,7 +167,7 @@ class QuartzEndpointDocumentationTests extends MockMvcEndpointDocumentationTests @Test void quartzReport() throws Exception { mockJobs(jobOne, jobTwo, jobThree); - mockTriggers(triggerOne, triggerTwo, triggerThree, triggerFour); + mockTriggers(cronTrigger, simpleTrigger, calendarIntervalTrigger, dailyTimeIntervalTrigger); this.mockMvc.perform(get("/actuator/quartz")).andExpect(status().isOk()) .andDo(document("quartz/report", responseFields(fieldWithPath("jobs.groups").description("An array of job group names."), @@ -181,7 +184,7 @@ class QuartzEndpointDocumentationTests extends MockMvcEndpointDocumentationTests @Test void quartzTriggers() throws Exception { - mockTriggers(triggerOne, triggerTwo, triggerThree, triggerFour); + mockTriggers(cronTrigger, simpleTrigger, calendarIntervalTrigger, dailyTimeIntervalTrigger); this.mockMvc.perform(get("/actuator/quartz/triggers")).andExpect(status().isOk()) .andDo(document("quartz/triggers", responseFields(fieldWithPath("groups").description("Trigger groups keyed by name."), @@ -202,16 +205,17 @@ class QuartzEndpointDocumentationTests extends MockMvcEndpointDocumentationTests @Test void quartzTriggerGroup() throws Exception { - CronTrigger cron = triggerOne.getTriggerBuilder().startAt(fromUtc("2020-11-30T17:00:00Z")) + CronTrigger cron = cronTrigger.getTriggerBuilder().startAt(fromUtc("2020-11-30T17:00:00Z")) .endAt(fromUtc("2020-12-30T03:00:00Z")).withIdentity("3am-week", "tests").build(); setPreviousNextFireTime(cron, "2020-12-04T03:00:00Z", "2020-12-07T03:00:00Z"); - SimpleTrigger simple = triggerTwo.getTriggerBuilder().withIdentity("every-day", "tests").build(); + SimpleTrigger simple = simpleTrigger.getTriggerBuilder().withIdentity("every-day", "tests").build(); setPreviousNextFireTime(simple, null, "2020-12-04T12:00:00Z"); - CalendarIntervalTrigger calendarInterval = triggerThree.getTriggerBuilder().withIdentity("once-a-week", "tests") - .startAt(fromUtc("2019-07-10T14:00:00Z")).endAt(fromUtc("2023-01-01T12:00:00Z")).build(); + CalendarIntervalTrigger calendarInterval = calendarIntervalTrigger.getTriggerBuilder() + .withIdentity("once-a-week", "tests").startAt(fromUtc("2019-07-10T14:00:00Z")) + .endAt(fromUtc("2023-01-01T12:00:00Z")).build(); setPreviousNextFireTime(calendarInterval, "2020-12-02T14:00:00Z", "2020-12-08T14:00:00Z"); - DailyTimeIntervalTrigger tueThuTrigger = triggerFour.getTriggerBuilder().withIdentity("tue-thu", "tests") - .build(); + DailyTimeIntervalTrigger tueThuTrigger = dailyTimeIntervalTrigger.getTriggerBuilder() + .withIdentity("tue-thu", "tests").build(); Trigger customTrigger = mock(Trigger.class); given(customTrigger.getKey()).willReturn(TriggerKey.triggerKey("once-a-year-custom", "tests")); given(customTrigger.toString()).willReturn("com.example.CustomTrigger@fdsfsd"); @@ -242,9 +246,9 @@ class QuartzEndpointDocumentationTests extends MockMvcEndpointDocumentationTests @Test void quartzJob() throws Exception { mockJobs(jobOne); - CronTrigger firstTrigger = triggerOne.getTriggerBuilder().build(); + CronTrigger firstTrigger = cronTrigger.getTriggerBuilder().build(); setPreviousNextFireTime(firstTrigger, null, "2020-12-07T03:00:00Z"); - SimpleTrigger secondTrigger = triggerTwo.getTriggerBuilder().build(); + SimpleTrigger secondTrigger = simpleTrigger.getTriggerBuilder().build(); setPreviousNextFireTime(secondTrigger, "2020-12-04T03:00:00Z", "2020-12-04T12:00:00Z"); mockTriggers(firstTrigger, secondTrigger); given(this.scheduler.getTriggersOfJob(jobOne.getKey())) @@ -266,53 +270,71 @@ class QuartzEndpointDocumentationTests extends MockMvcEndpointDocumentationTests previousFireTime("triggers.[]."), nextFireTime("triggers.[]."), priority("triggers.[].")))); } + @Test + void quartzTriggerCommon() throws Exception { + setupTriggerDetails(cronTrigger.getTriggerBuilder(), TriggerState.NORMAL); + this.mockMvc.perform(get("/actuator/quartz/triggers/samples/example")).andExpect(status().isOk()) + .andDo(document("quartz/trigger-details-common", responseFields(commonCronDetails).and( + subsectionWithPath("calendarInterval").description( + "Calendar time interval trigger details, if any. Present when `type` is `calendarInterval`.") + .optional().type(JsonFieldType.OBJECT), + subsectionWithPath("custom") + .description("Custom trigger details, if any. Present when `type` is `custom`.") + .optional().type(JsonFieldType.OBJECT), + subsectionWithPath("cron") + .description("Cron trigger details, if any. Present when `type` is `cron`.").optional() + .type(JsonFieldType.OBJECT), + subsectionWithPath("dailyTimeInterval").description( + "Daily time interval trigger details, if any. Present when `type` is `dailyTimeInterval`.") + .optional().type(JsonFieldType.OBJECT), + subsectionWithPath("simple") + .description("Simple trigger details, if any. Present when `type` is `simple`.") + .optional().type(JsonFieldType.OBJECT)))); + } + @Test void quartzTriggerCron() throws Exception { - setupTriggerDetails(triggerOne.getTriggerBuilder(), TriggerState.NORMAL); + setupTriggerDetails(cronTrigger.getTriggerBuilder(), TriggerState.NORMAL); this.mockMvc.perform(get("/actuator/quartz/triggers/samples/example")).andExpect(status().isOk()) .andDo(document("quartz/trigger-details-cron", - responseFields(commonCronDetails) - .and(fieldWithPath("cron").description("Cron trigger specific details.")) + relaxedResponseFields(fieldWithPath("cron").description("Cron trigger specific details.")) .andWithPrefix("cron.", cronTriggerSummary))); } @Test void quartzTriggerSimple() throws Exception { - setupTriggerDetails(triggerTwo.getTriggerBuilder(), TriggerState.NORMAL); + setupTriggerDetails(simpleTrigger.getTriggerBuilder(), TriggerState.NORMAL); this.mockMvc.perform(get("/actuator/quartz/triggers/samples/example")).andExpect(status().isOk()) .andDo(document("quartz/trigger-details-simple", - responseFields(commonCronDetails) - .and(fieldWithPath("simple").description("Simple trigger specific details.")) + relaxedResponseFields(fieldWithPath("simple").description("Simple trigger specific details.")) .andWithPrefix("simple.", simpleTriggerSummary) .and(repeatCount("simple."), timesTriggered("simple.")))); } @Test void quartzTriggerCalendarInterval() throws Exception { - setupTriggerDetails(triggerThree.getTriggerBuilder(), TriggerState.NORMAL); + setupTriggerDetails(calendarIntervalTrigger.getTriggerBuilder(), TriggerState.NORMAL); this.mockMvc.perform(get("/actuator/quartz/triggers/samples/example")).andExpect(status().isOk()) - .andDo(document("quartz/trigger-details-calendar-interval", responseFields(commonCronDetails) - .and(fieldWithPath("calendarInterval") - .description("Calendar interval trigger specific details.")) - .andWithPrefix("calendarInterval.", calendarIntervalTriggerSummary) - .and(timesTriggered("calendarInterval."), - fieldWithPath("calendarInterval.preserveHourOfDayAcrossDaylightSavings").description( - "Whether to fire the trigger at the same time of day, regardless of daylight " - + "saving time transitions."), - fieldWithPath("calendarInterval.skipDayIfHourDoesNotExist").description( - "Whether to skip if the hour of the day does not exist on a given day.")))); + .andDo(document("quartz/trigger-details-calendar-interval", relaxedResponseFields( + fieldWithPath("calendarInterval").description("Calendar interval trigger specific details.")) + .andWithPrefix("calendarInterval.", calendarIntervalTriggerSummary) + .and(timesTriggered("calendarInterval."), fieldWithPath( + "calendarInterval.preserveHourOfDayAcrossDaylightSavings").description( + "Whether to fire the trigger at the same time of day, regardless of daylight " + + "saving time transitions."), + fieldWithPath("calendarInterval.skipDayIfHourDoesNotExist").description( + "Whether to skip if the hour of the day does not exist on a given day.")))); } @Test void quartzTriggerDailyTimeInterval() throws Exception { - setupTriggerDetails(triggerFour.getTriggerBuilder(), TriggerState.PAUSED); + setupTriggerDetails(dailyTimeIntervalTrigger.getTriggerBuilder(), TriggerState.PAUSED); this.mockMvc.perform(get("/actuator/quartz/triggers/samples/example")).andExpect(status().isOk()) .andDo(document("quartz/trigger-details-daily-time-interval", - responseFields(commonCronDetails) - .and(fieldWithPath("dailyTimeInterval") - .description("Daily time interval trigger specific details.")) - .andWithPrefix("dailyTimeInterval.", dailyTimeIntervalTriggerSummary) - .and(repeatCount("dailyTimeInterval."), timesTriggered("dailyTimeInterval.")))); + relaxedResponseFields(fieldWithPath("dailyTimeInterval") + .description("Daily time interval trigger specific details.")) + .andWithPrefix("dailyTimeInterval.", dailyTimeIntervalTriggerSummary) + .and(repeatCount("dailyTimeInterval."), timesTriggered("dailyTimeInterval.")))); } @Test @@ -331,8 +353,7 @@ class QuartzEndpointDocumentationTests extends MockMvcEndpointDocumentationTests mockTriggers(trigger); this.mockMvc.perform(get("/actuator/quartz/triggers/samples/example")).andExpect(status().isOk()) .andDo(document("quartz/trigger-details-custom", - responseFields(commonCronDetails) - .and(fieldWithPath("custom").description("Custom trigger specific details.")) + relaxedResponseFields(fieldWithPath("custom").description("Custom trigger specific details.")) .andWithPrefix("custom.", customTriggerSummary))); }