Skip to content

Commit 9c5c594

Browse files
committed
Add Quartz actuator endpoint
1 parent 303b812 commit 9c5c594

File tree

14 files changed

+657
-0
lines changed

14 files changed

+657
-0
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
[[quartz]]
2+
= Quartz (`quartz`)
3+
4+
The `quartz` endpoint provides information about the scheduled jobs that are managed by
5+
Quartz Scheduler.
6+
7+
8+
9+
[[quartz-report]]
10+
== Retrieving the Quartz report
11+
12+
To retrieve the Quartz, make a `GET` request to `/application/quartz`,
13+
as shown in the following curl-based example:
14+
15+
include::{snippets}quartz-report/curl-request.adoc[]
16+
17+
The resulting response is similar to the following:
18+
19+
include::{snippets}quartz-report/http-response.adoc[]
20+
21+
22+
23+
[[quartz-job]]
24+
== Retrieving the Quartz job details
25+
26+
To retrieve the Quartz, make a `GET` request to `/application/quartz/{group}/{name}`,
27+
as shown in the following curl-based example:
28+
29+
include::{snippets}quartz-job/curl-request.adoc[]
30+
31+
The preceding example retrieves the job with the `group` of `groupOne` and `name` of
32+
`jobOne`. The resulting response is similar to the following:
33+
34+
include::{snippets}quartz-job/http-response.adoc[]
35+
36+
37+
38+
[[quartz-job-response-structure]]
39+
=== Response Structure
40+
41+
The response contains details of the scheduled job. The following table describes the
42+
structure of the response:
43+
44+
[cols="2,1,3"]
45+
include::{snippets}quartz-job/response-fields.adoc[]

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/index.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ include::endpoints/logfile.adoc[leveloffset=+1]
6363
include::endpoints/loggers.adoc[leveloffset=+1]
6464
include::endpoints/metrics.adoc[leveloffset=+1]
6565
include::endpoints/prometheus.adoc[leveloffset=+1]
66+
include::endpoints/quartz.adoc[leveloffset=+1]
6667
include::endpoints/scheduledtasks.adoc[leveloffset=+1]
6768
include::endpoints/sessions.adoc[leveloffset=+1]
6869
include::endpoints/shutdown.adoc[leveloffset=+1]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.quartz;
18+
19+
import org.quartz.Scheduler;
20+
21+
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
22+
import org.springframework.boot.actuate.quartz.QuartzEndpoint;
23+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
24+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
25+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
26+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
27+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
28+
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
29+
import org.springframework.context.annotation.Bean;
30+
import org.springframework.context.annotation.Configuration;
31+
32+
/**
33+
* {@link EnableAutoConfiguration Auto-configuration} for {@link QuartzEndpoint}.
34+
*
35+
* @author Vedran Pavic
36+
* @since 2.0.0
37+
*/
38+
@Configuration
39+
@ConditionalOnClass(Scheduler.class)
40+
@AutoConfigureAfter(QuartzAutoConfiguration.class)
41+
public class QuartzEndpointAutoConfiguration {
42+
43+
@Bean
44+
@ConditionalOnBean(Scheduler.class)
45+
@ConditionalOnMissingBean
46+
@ConditionalOnEnabledEndpoint
47+
public QuartzEndpoint quartzEndpoint(Scheduler scheduler) {
48+
return new QuartzEndpoint(scheduler);
49+
}
50+
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Auto-configuration for actuator Quartz Scheduler concerns.
19+
*/
20+
package org.springframework.boot.actuate.autoconfigure.quartz;

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ org.springframework.boot.actuate.autoconfigure.management.ThreadDumpEndpointAuto
3232
org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration,\
3333
org.springframework.boot.actuate.autoconfigure.mongo.MongoHealthIndicatorAutoConfiguration,\
3434
org.springframework.boot.actuate.autoconfigure.neo4j.Neo4jHealthIndicatorAutoConfiguration,\
35+
org.springframework.boot.actuate.autoconfigure.quartz.QuartzEndpointAutoConfiguration,\
3536
org.springframework.boot.actuate.autoconfigure.redis.RedisHealthIndicatorAutoConfiguration,\
3637
org.springframework.boot.actuate.autoconfigure.scheduling.ScheduledTasksEndpointAutoConfiguration,\
3738
org.springframework.boot.actuate.autoconfigure.session.SessionsEndpointAutoConfiguration,\
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation;
18+
19+
import java.time.Instant;
20+
import java.util.Arrays;
21+
import java.util.Collections;
22+
import java.util.Date;
23+
import java.util.HashSet;
24+
import java.util.regex.Pattern;
25+
26+
import org.junit.Test;
27+
import org.quartz.Job;
28+
import org.quartz.JobBuilder;
29+
import org.quartz.JobDetail;
30+
import org.quartz.JobKey;
31+
import org.quartz.Scheduler;
32+
import org.quartz.SimpleScheduleBuilder;
33+
import org.quartz.Trigger;
34+
import org.quartz.TriggerBuilder;
35+
import org.quartz.impl.matchers.GroupMatcher;
36+
37+
import org.springframework.boot.actuate.quartz.QuartzEndpoint;
38+
import org.springframework.boot.test.mock.mockito.MockBean;
39+
import org.springframework.context.annotation.Bean;
40+
import org.springframework.context.annotation.Configuration;
41+
import org.springframework.context.annotation.Import;
42+
43+
import static org.mockito.BDDMockito.given;
44+
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
45+
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
46+
import static org.springframework.restdocs.operation.preprocess.Preprocessors.replacePattern;
47+
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
48+
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
49+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
50+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
51+
52+
/**
53+
* Tests for generating documentation describing the {@link QuartzEndpoint}.
54+
*
55+
* @author Vedran Pavic
56+
*/
57+
public class QuartzEndpointDocumentationTests extends AbstractEndpointDocumentationTests {
58+
59+
private static final JobDetail jobOne = JobBuilder.newJob(Job.class)
60+
.withIdentity("jobOne", "groupOne").withDescription("My first job").build();
61+
62+
private static final JobDetail jobTwo = JobBuilder.newJob(Job.class)
63+
.withIdentity("jobTwo", "groupOne").build();
64+
65+
private static final JobDetail jobThree = JobBuilder.newJob(Job.class)
66+
.withIdentity("jobThree", "groupTwo").build();
67+
68+
private static final Trigger triggerOne = TriggerBuilder.newTrigger().forJob(jobOne)
69+
.withIdentity("triggerOne").withDescription("My first trigger")
70+
.modifiedByCalendar("myCalendar")
71+
.startAt(Date.from(Instant.parse("2017-12-01T12:00:00Z")))
72+
.endAt(Date.from(Instant.parse("2017-12-01T12:30:00Z")))
73+
.withSchedule(SimpleScheduleBuilder.repeatMinutelyForever()).build();
74+
75+
private static final Trigger triggerTwo = TriggerBuilder.newTrigger().forJob(jobOne)
76+
.withIdentity("triggerTwo").withDescription("My second trigger")
77+
.modifiedByCalendar("myCalendar")
78+
.startAt(Date.from(Instant.parse("2017-12-01T00:00:00Z")))
79+
.endAt(Date.from(Instant.parse("2017-12-10T00:00:00Z")))
80+
.withSchedule(SimpleScheduleBuilder.repeatHourlyForever()).build();
81+
82+
@MockBean
83+
private Scheduler scheduler;
84+
85+
@Test
86+
public void quartzReport() throws Exception {
87+
String groupOne = jobOne.getKey().getGroup();
88+
String groupTwo = jobThree.getKey().getGroup();
89+
given(this.scheduler.getJobGroupNames())
90+
.willReturn(Arrays.asList(groupOne, groupTwo));
91+
given(this.scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupOne)))
92+
.willReturn(
93+
new HashSet<>(Arrays.asList(jobOne.getKey(), jobTwo.getKey())));
94+
given(this.scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupTwo)))
95+
.willReturn(Collections.singleton(jobThree.getKey()));
96+
this.mockMvc.perform(get("/application/quartz")).andExpect(status().isOk())
97+
.andDo(document("quartz/report"));
98+
}
99+
100+
@Test
101+
public void quartzJob() throws Exception {
102+
JobKey jobKey = jobOne.getKey();
103+
given(this.scheduler.getJobDetail(jobKey)).willReturn(jobOne);
104+
given(this.scheduler.getTriggersOfJob(jobKey))
105+
.willAnswer(invocation -> Arrays.asList(triggerOne, triggerTwo));
106+
this.mockMvc.perform(get("/application/quartz/groupOne/jobOne"))
107+
.andExpect(status().isOk())
108+
.andDo(document("quartz/job",
109+
preprocessResponse(replacePattern(
110+
Pattern.compile("org.quartz.Job"), "com.example.MyJob")),
111+
responseFields(
112+
fieldWithPath("jobGroup").description("Job group."),
113+
fieldWithPath("jobName").description("Job name."),
114+
fieldWithPath("description")
115+
.description("Job description, if any."),
116+
fieldWithPath("className").description("Job class."),
117+
fieldWithPath("triggers.[].triggerGroup")
118+
.description("Trigger group."),
119+
fieldWithPath("triggers.[].triggerName")
120+
.description("Trigger name."),
121+
fieldWithPath("triggers.[].description")
122+
.description("Trigger description, if any."),
123+
fieldWithPath("triggers.[].calendarName")
124+
.description("Trigger's calendar name, if any."),
125+
fieldWithPath("triggers.[].startTime")
126+
.description("Trigger's start time."),
127+
fieldWithPath("triggers.[].endTime")
128+
.description("Trigger's end time."),
129+
fieldWithPath("triggers.[].nextFireTime")
130+
.description("Trigger's next fire time."),
131+
fieldWithPath("triggers.[].previousFireTime").description(
132+
"Trigger's previous fire time, if any."),
133+
fieldWithPath("triggers.[].finalFireTime").description(
134+
"Trigger's final fire time, if any."))));
135+
}
136+
137+
@Configuration
138+
@Import(BaseDocumentationConfiguration.class)
139+
static class TestConfiguration {
140+
141+
@Bean
142+
public QuartzEndpoint endpoint(Scheduler scheduler) {
143+
return new QuartzEndpoint(scheduler);
144+
}
145+
146+
}
147+
148+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.quartz;
18+
19+
import org.junit.Test;
20+
import org.quartz.Scheduler;
21+
22+
import org.springframework.boot.actuate.quartz.QuartzEndpoint;
23+
import org.springframework.boot.autoconfigure.AutoConfigurations;
24+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
25+
import org.springframework.context.annotation.Bean;
26+
import org.springframework.context.annotation.Configuration;
27+
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
import static org.mockito.Mockito.mock;
30+
31+
/**
32+
* Tests for {@link QuartzEndpointAutoConfiguration}.
33+
*
34+
* @author Vedran Pavic
35+
*/
36+
public class QuartzEndpointAutoConfigurationTests {
37+
38+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
39+
.withConfiguration(
40+
AutoConfigurations.of(QuartzEndpointAutoConfiguration.class))
41+
.withUserConfiguration(QuartzConfiguration.class);
42+
43+
@Test
44+
public void runShouldHaveEndpointBean() {
45+
this.contextRunner.run(
46+
(context) -> assertThat(context).hasSingleBean(QuartzEndpoint.class));
47+
}
48+
49+
@Test
50+
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean()
51+
throws Exception {
52+
this.contextRunner.withPropertyValues("management.endpoint.quartz.enabled:false")
53+
.run((context) -> assertThat(context)
54+
.doesNotHaveBean(QuartzEndpoint.class));
55+
}
56+
57+
@Configuration
58+
static class QuartzConfiguration {
59+
60+
@Bean
61+
public Scheduler scheduler() {
62+
return mock(Scheduler.class);
63+
}
64+
65+
}
66+
67+
}

spring-boot-project/spring-boot-actuator/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@
106106
<artifactId>liquibase-core</artifactId>
107107
<optional>true</optional>
108108
</dependency>
109+
<dependency>
110+
<groupId>org.quartz-scheduler</groupId>
111+
<artifactId>quartz</artifactId>
112+
</dependency>
109113
<dependency>
110114
<groupId>org.springframework</groupId>
111115
<artifactId>spring-jdbc</artifactId>

0 commit comments

Comments
 (0)