diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/endpoints/quartz.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/endpoints/quartz.adoc new file mode 100644 index 000000000000..8a6ffc56006f --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/endpoints/quartz.adoc @@ -0,0 +1,45 @@ +[[quartz]] += Quartz (`quartz`) + +The `quartz` endpoint provides information about the scheduled jobs that are managed by +Quartz Scheduler. + + + +[[quartz-report]] +== Retrieving the Quartz report + +To retrieve the Quartz, make a `GET` request to `/application/quartz`, +as shown in the following curl-based example: + +include::{snippets}quartz-report/curl-request.adoc[] + +The resulting response is similar to the following: + +include::{snippets}quartz-report/http-response.adoc[] + + + +[[quartz-job]] +== Retrieving the Quartz job details + +To retrieve the Quartz, make a `GET` request to `/application/quartz/{group}/{name}`, +as shown in the following curl-based example: + +include::{snippets}quartz-job/curl-request.adoc[] + +The preceding example retrieves the job with the `group` of `groupOne` and `name` of +`jobOne`. The resulting response is similar to the following: + +include::{snippets}quartz-job/http-response.adoc[] + + + +[[quartz-job-response-structure]] +=== Response Structure + +The response contains details of the scheduled job. The following table describes the +structure of the response: + +[cols="2,1,3"] +include::{snippets}quartz-job/response-fields.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/index.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/index.adoc index e30d7e6616c5..17c03ab0e828 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/index.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/index.adoc @@ -63,6 +63,7 @@ include::endpoints/logfile.adoc[leveloffset=+1] include::endpoints/loggers.adoc[leveloffset=+1] include::endpoints/metrics.adoc[leveloffset=+1] include::endpoints/prometheus.adoc[leveloffset=+1] +include::endpoints/quartz.adoc[leveloffset=+1] include::endpoints/scheduledtasks.adoc[leveloffset=+1] include::endpoints/sessions.adoc[leveloffset=+1] include::endpoints/shutdown.adoc[leveloffset=+1] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/quartz/QuartzEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/quartz/QuartzEndpointAutoConfiguration.java new file mode 100644 index 000000000000..6f8a4342fb7e --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/quartz/QuartzEndpointAutoConfiguration.java @@ -0,0 +1,51 @@ +/* + * 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.autoconfigure.quartz; + +import org.quartz.Scheduler; + +import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; +import org.springframework.boot.actuate.quartz.QuartzEndpoint; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for {@link QuartzEndpoint}. + * + * @author Vedran Pavic + * @since 2.0.0 + */ +@Configuration +@ConditionalOnClass(Scheduler.class) +@AutoConfigureAfter(QuartzAutoConfiguration.class) +public class QuartzEndpointAutoConfiguration { + + @Bean + @ConditionalOnBean(Scheduler.class) + @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint + public QuartzEndpoint quartzEndpoint(Scheduler scheduler) { + return new QuartzEndpoint(scheduler); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/quartz/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/quartz/package-info.java new file mode 100644 index 000000000000..27b1939c5f51 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/quartz/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Auto-configuration for actuator Quartz Scheduler concerns. + */ +package org.springframework.boot.actuate.autoconfigure.quartz; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories index 6b24efad9be5..300a6659acba 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories @@ -32,6 +32,7 @@ org.springframework.boot.actuate.autoconfigure.management.ThreadDumpEndpointAuto org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.mongo.MongoHealthIndicatorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.neo4j.Neo4jHealthIndicatorAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.quartz.QuartzEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.redis.RedisHealthIndicatorAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.scheduling.ScheduledTasksEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.session.SessionsEndpointAutoConfiguration,\ 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 new file mode 100644 index 000000000000..1dd41a39b922 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/QuartzEndpointDocumentationTests.java @@ -0,0 +1,148 @@ +/* + * 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.autoconfigure.endpoint.web.documentation; + +import java.time.Instant; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.regex.Pattern; + +import org.junit.Test; +import org.quartz.Job; +import org.quartz.JobBuilder; +import org.quartz.JobDetail; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SimpleScheduleBuilder; +import org.quartz.Trigger; +import org.quartz.TriggerBuilder; +import org.quartz.impl.matchers.GroupMatcher; + +import org.springframework.boot.actuate.quartz.QuartzEndpoint; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.replacePattern; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Tests for generating documentation describing the {@link QuartzEndpoint}. + * + * @author Vedran Pavic + */ +public class QuartzEndpointDocumentationTests extends AbstractEndpointDocumentationTests { + + private static final JobDetail jobOne = JobBuilder.newJob(Job.class) + .withIdentity("jobOne", "groupOne").withDescription("My first job").build(); + + private static final JobDetail jobTwo = JobBuilder.newJob(Job.class) + .withIdentity("jobTwo", "groupOne").build(); + + private static final JobDetail jobThree = JobBuilder.newJob(Job.class) + .withIdentity("jobThree", "groupTwo").build(); + + private static final Trigger triggerOne = TriggerBuilder.newTrigger().forJob(jobOne) + .withIdentity("triggerOne").withDescription("My first trigger") + .modifiedByCalendar("myCalendar") + .startAt(Date.from(Instant.parse("2017-12-01T12:00:00Z"))) + .endAt(Date.from(Instant.parse("2017-12-01T12:30:00Z"))) + .withSchedule(SimpleScheduleBuilder.repeatMinutelyForever()).build(); + + private static final Trigger triggerTwo = TriggerBuilder.newTrigger().forJob(jobOne) + .withIdentity("triggerTwo").withDescription("My second trigger") + .modifiedByCalendar("myCalendar") + .startAt(Date.from(Instant.parse("2017-12-01T00:00:00Z"))) + .endAt(Date.from(Instant.parse("2017-12-10T00:00:00Z"))) + .withSchedule(SimpleScheduleBuilder.repeatHourlyForever()).build(); + + @MockBean + private Scheduler scheduler; + + @Test + public void quartzReport() throws Exception { + String groupOne = jobOne.getKey().getGroup(); + String groupTwo = jobThree.getKey().getGroup(); + given(this.scheduler.getJobGroupNames()) + .willReturn(Arrays.asList(groupOne, groupTwo)); + given(this.scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupOne))) + .willReturn( + new HashSet<>(Arrays.asList(jobOne.getKey(), jobTwo.getKey()))); + given(this.scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupTwo))) + .willReturn(Collections.singleton(jobThree.getKey())); + this.mockMvc.perform(get("/application/quartz")).andExpect(status().isOk()) + .andDo(document("quartz/report")); + } + + @Test + public void quartzJob() throws Exception { + JobKey jobKey = jobOne.getKey(); + given(this.scheduler.getJobDetail(jobKey)).willReturn(jobOne); + given(this.scheduler.getTriggersOfJob(jobKey)) + .willAnswer(invocation -> Arrays.asList(triggerOne, triggerTwo)); + this.mockMvc.perform(get("/application/quartz/groupOne/jobOne")) + .andExpect(status().isOk()) + .andDo(document("quartz/job", + preprocessResponse(replacePattern( + Pattern.compile("org.quartz.Job"), "com.example.MyJob")), + responseFields( + fieldWithPath("jobGroup").description("Job group."), + fieldWithPath("jobName").description("Job name."), + fieldWithPath("description") + .description("Job description, if any."), + fieldWithPath("className").description("Job class."), + fieldWithPath("triggers.[].triggerGroup") + .description("Trigger group."), + fieldWithPath("triggers.[].triggerName") + .description("Trigger name."), + fieldWithPath("triggers.[].description") + .description("Trigger description, if any."), + fieldWithPath("triggers.[].calendarName") + .description("Trigger's calendar name, if any."), + fieldWithPath("triggers.[].startTime") + .description("Trigger's start time."), + fieldWithPath("triggers.[].endTime") + .description("Trigger's end time."), + fieldWithPath("triggers.[].nextFireTime") + .description("Trigger's next fire time."), + fieldWithPath("triggers.[].previousFireTime").description( + "Trigger's previous fire time, if any."), + fieldWithPath("triggers.[].finalFireTime").description( + "Trigger's final fire time, if any.")))); + } + + @Configuration + @Import(BaseDocumentationConfiguration.class) + static class TestConfiguration { + + @Bean + public QuartzEndpoint endpoint(Scheduler scheduler) { + return new QuartzEndpoint(scheduler); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/quartz/QuartzEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/quartz/QuartzEndpointAutoConfigurationTests.java new file mode 100644 index 000000000000..1d1c5411257b --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/quartz/QuartzEndpointAutoConfigurationTests.java @@ -0,0 +1,67 @@ +/* + * 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.autoconfigure.quartz; + +import org.junit.Test; +import org.quartz.Scheduler; + +import org.springframework.boot.actuate.quartz.QuartzEndpoint; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link QuartzEndpointAutoConfiguration}. + * + * @author Vedran Pavic + */ +public class QuartzEndpointAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of(QuartzEndpointAutoConfiguration.class)) + .withUserConfiguration(QuartzConfiguration.class); + + @Test + public void runShouldHaveEndpointBean() { + this.contextRunner.run( + (context) -> assertThat(context).hasSingleBean(QuartzEndpoint.class)); + } + + @Test + public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() + throws Exception { + this.contextRunner.withPropertyValues("management.endpoint.quartz.enabled:false") + .run((context) -> assertThat(context) + .doesNotHaveBean(QuartzEndpoint.class)); + } + + @Configuration + static class QuartzConfiguration { + + @Bean + public Scheduler scheduler() { + return mock(Scheduler.class); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/pom.xml b/spring-boot-project/spring-boot-actuator/pom.xml index 2d08eefbb8e8..0b37678d0500 100644 --- a/spring-boot-project/spring-boot-actuator/pom.xml +++ b/spring-boot-project/spring-boot-actuator/pom.xml @@ -106,6 +106,10 @@ liquibase-core true + + org.quartz-scheduler + quartz + org.springframework spring-jdbc diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/quartz/QuartzEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/quartz/QuartzEndpoint.java new file mode 100644 index 000000000000..7aacf17da9e0 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/quartz/QuartzEndpoint.java @@ -0,0 +1,202 @@ +/* + * 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.quartz; + +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.quartz.Job; +import org.quartz.JobDetail; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.Trigger; +import org.quartz.impl.matchers.GroupMatcher; + +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.annotation.Selector; +import org.springframework.util.Assert; + +/** + * {@link Endpoint} to expose Quartz Scheduler info. + * + * @author Vedran Pavic + * @since 2.0.0 + */ +@Endpoint(id = "quartz") +public class QuartzEndpoint { + + private final Scheduler scheduler; + + public QuartzEndpoint(Scheduler scheduler) { + Assert.notNull(scheduler, "Scheduler must not be null"); + this.scheduler = scheduler; + } + + @ReadOperation + public Map quartzReport() { + Map result = new LinkedHashMap<>(); + try { + for (String groupName : this.scheduler.getJobGroupNames()) { + List jobs = this.scheduler + .getJobKeys(GroupMatcher.jobGroupEquals(groupName)).stream() + .map(JobKey::getName).collect(Collectors.toList()); + result.put(groupName, jobs); + } + } + catch (SchedulerException ignored) { + } + return result; + } + + @ReadOperation + public QuartzJob quartzJob(@Selector String groupName, @Selector String jobName) { + try { + JobKey jobKey = JobKey.jobKey(jobName, groupName); + JobDetail jobDetail = this.scheduler.getJobDetail(jobKey); + List triggers = this.scheduler.getTriggersOfJob(jobKey); + return new QuartzJob(jobDetail, triggers); + } + catch (SchedulerException e) { + return null; + } + } + + /** + * Details of a {@link Job Quartz Job}. + */ + public static final class QuartzJob { + + private final String jobGroup; + + private final String jobName; + + private final String description; + + private final String className; + + private final List triggers = new ArrayList<>(); + + QuartzJob(JobDetail jobDetail, List triggers) { + this.jobGroup = jobDetail.getKey().getGroup(); + this.jobName = jobDetail.getKey().getName(); + this.description = jobDetail.getDescription(); + this.className = jobDetail.getJobClass().getName(); + triggers.forEach(trigger -> this.triggers.add(new QuartzTrigger(trigger))); + } + + public String getJobGroup() { + return this.jobGroup; + } + + public String getJobName() { + return this.jobName; + } + + public String getDescription() { + return this.description; + } + + public String getClassName() { + return this.className; + } + + public List getTriggers() { + return this.triggers; + } + + } + + /** + * Details of a {@link Trigger Quartz Trigger}. + */ + public static final class QuartzTrigger { + + private final String triggerGroup; + + private final String triggerName; + + private final String description; + + private final String calendarName; + + private final Date startTime; + + private final Date endTime; + + private final Date previousFireTime; + + private final Date nextFireTime; + + private final Date finalFireTime; + + QuartzTrigger(Trigger trigger) { + this.triggerGroup = trigger.getKey().getGroup(); + this.triggerName = trigger.getKey().getName(); + this.description = trigger.getDescription(); + this.calendarName = trigger.getCalendarName(); + this.startTime = trigger.getStartTime(); + this.endTime = trigger.getEndTime(); + this.previousFireTime = trigger.getPreviousFireTime(); + this.nextFireTime = trigger.getNextFireTime(); + this.finalFireTime = trigger.getFinalFireTime(); + } + + public String getTriggerGroup() { + return this.triggerGroup; + } + + public String getTriggerName() { + return this.triggerName; + } + + public String getDescription() { + return this.description; + } + + public String getCalendarName() { + return this.calendarName; + } + + public Date getStartTime() { + return this.startTime; + } + + public Date getEndTime() { + return this.endTime; + } + + public Date getPreviousFireTime() { + return this.previousFireTime; + } + + public Date getNextFireTime() { + return this.nextFireTime; + } + + public Date getFinalFireTime() { + return this.finalFireTime; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/quartz/package-info.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/quartz/package-info.java new file mode 100644 index 000000000000..0f27e990dcc6 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/quartz/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Actuator support for Quartz Scheduler. + */ +package org.springframework.boot.actuate.quartz; diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/quartz/QuartzEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/quartz/QuartzEndpointTests.java new file mode 100644 index 000000000000..743cd385eaef --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/quartz/QuartzEndpointTests.java @@ -0,0 +1,85 @@ +/* + * 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.quartz; + +import java.util.Collections; +import java.util.Map; + +import org.junit.Test; +import org.quartz.Job; +import org.quartz.JobBuilder; +import org.quartz.JobDetail; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.Trigger; +import org.quartz.TriggerBuilder; +import org.quartz.impl.matchers.GroupMatcher; + +import org.springframework.boot.actuate.quartz.QuartzEndpoint.QuartzJob; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link QuartzEndpoint}. + * + * @author Vedran Pavic + */ +public class QuartzEndpointTests { + + private final Scheduler scheduler = mock(Scheduler.class); + + private final QuartzEndpoint endpoint = new QuartzEndpoint(this.scheduler); + + private final JobDetail jobDetail = JobBuilder.newJob(Job.class) + .withIdentity("testJob").build(); + + private final Trigger trigger = TriggerBuilder.newTrigger().forJob(this.jobDetail) + .withIdentity("testTrigger").build(); + + @Test + public void quartzReport() throws Exception { + String jobGroup = this.jobDetail.getKey().getGroup(); + given(this.scheduler.getJobGroupNames()) + .willReturn(Collections.singletonList(jobGroup)); + given(this.scheduler.getJobKeys(GroupMatcher.jobGroupEquals(jobGroup))) + .willReturn(Collections.singleton(this.jobDetail.getKey())); + Map quartzReport = this.endpoint.quartzReport(); + assertThat(quartzReport).hasSize(1); + } + + @Test + public void quartzJob() throws Exception { + JobKey jobKey = this.jobDetail.getKey(); + given(this.scheduler.getJobDetail(jobKey)).willReturn(this.jobDetail); + given(this.scheduler.getTriggersOfJob(jobKey)) + .willAnswer(invocation -> Collections.singletonList(this.trigger)); + QuartzJob quartzJob = this.endpoint.quartzJob(jobKey.getGroup(), + jobKey.getName()); + assertThat(quartzJob.getJobGroup()).isEqualTo(jobKey.getGroup()); + assertThat(quartzJob.getJobName()).isEqualTo(jobKey.getName()); + assertThat(quartzJob.getClassName()) + .isEqualTo(this.jobDetail.getJobClass().getName()); + assertThat(quartzJob.getTriggers()).hasSize(1); + assertThat(quartzJob.getTriggers().get(0).getTriggerGroup()) + .isEqualTo(this.trigger.getKey().getGroup()); + assertThat(quartzJob.getTriggers().get(0).getTriggerName()) + .isEqualTo(this.trigger.getKey().getName()); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index ea907afe4fdd..22a305ce070e 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -1191,6 +1191,12 @@ content into your application; rather pick only the properties that you need. management.endpoint.scheduledtasks.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. management.endpoint.scheduledtasks.enabled= # Enable the scheduled tasks endpoint. + # QUARTZ ENDPOINT ({sc-spring-boot-actuator}/audit/QuartzEndpoint.{sc-ext}[QuartzEndpoint]) + endpoints.quartz.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. + endpoints.quartz.enabled= # Enable the quartz endpoint. + endpoints.quartz.jmx.enabled= # Expose the quartz endpoint as a JMX MBean. + endpoints.quartz.web.enabled= # Expose the quartz endpoint as a Web endpoint. + # SESSIONS ENDPOINT ({sc-spring-boot-actuator}/session/SessionsEndpoint.{sc-ext}[SessionsEndpoint]) management.endpoint.sessions.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. management.endpoint.sessions.enabled= # Enable the sessions endpoint. diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 0ad0e503d957..ab5ca52e0cb2 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -101,6 +101,9 @@ The following technology-agnostic endpoints are available: |`mappings` |Displays a collated list of all `@RequestMapping` paths. +|`quartz` +|Shows information about Quartz Scheduler jobs. + |`scheduledtasks` |Displays the scheduled tasks in your application. diff --git a/spring-boot-samples/spring-boot-sample-quartz/pom.xml b/spring-boot-samples/spring-boot-sample-quartz/pom.xml index ae3d5d553c71..0317bf893975 100644 --- a/spring-boot-samples/spring-boot-sample-quartz/pom.xml +++ b/spring-boot-samples/spring-boot-sample-quartz/pom.xml @@ -15,6 +15,10 @@ ${basedir}/../.. + + org.springframework.boot + spring-boot-starter-actuator + org.springframework.boot spring-boot-starter-quartz