Skip to content

Commit 3f1e125

Browse files
committed
Polish "Add Quartz actuator endpoint"
See spring-projectsgh-10364
1 parent e5216f0 commit 3f1e125

File tree

13 files changed

+421
-169
lines changed

13 files changed

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

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

Lines changed: 0 additions & 45 deletions
This file was deleted.

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/quartz/QuartzEndpointAutoConfiguration.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* https://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -20,6 +20,7 @@
2020

2121
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
2222
import org.springframework.boot.actuate.quartz.QuartzEndpoint;
23+
import org.springframework.boot.actuate.quartz.QuartzEndpointWebExtension;
2324
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
2425
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2526
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -35,7 +36,7 @@
3536
* @author Vedran Pavic
3637
* @since 2.0.0
3738
*/
38-
@Configuration
39+
@Configuration(proxyBeanMethods = false)
3940
@ConditionalOnClass(Scheduler.class)
4041
@AutoConfigureAfter(QuartzAutoConfiguration.class)
4142
@ConditionalOnAvailableEndpoint(endpoint = QuartzEndpoint.class)
@@ -48,4 +49,11 @@ public QuartzEndpoint quartzEndpoint(Scheduler scheduler) {
4849
return new QuartzEndpoint(scheduler);
4950
}
5051

52+
@Bean
53+
@ConditionalOnBean(QuartzEndpoint.class)
54+
@ConditionalOnMissingBean
55+
public QuartzEndpointWebExtension quartzEndpointWebExtension(QuartzEndpoint endpoint) {
56+
return new QuartzEndpointWebExtension(endpoint);
57+
}
58+
5159
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/quartz/package-info.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* https://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/QuartzEndpointDocumentationTests.java

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* https://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -23,7 +23,7 @@
2323
import java.util.HashSet;
2424
import java.util.regex.Pattern;
2525

26-
import org.junit.Test;
26+
import org.junit.jupiter.api.Test;
2727
import org.quartz.Job;
2828
import org.quartz.JobBuilder;
2929
import org.quartz.JobDetail;
@@ -35,6 +35,7 @@
3535
import org.quartz.impl.matchers.GroupMatcher;
3636

3737
import org.springframework.boot.actuate.quartz.QuartzEndpoint;
38+
import org.springframework.boot.actuate.quartz.QuartzEndpointWebExtension;
3839
import org.springframework.boot.test.mock.mockito.MockBean;
3940
import org.springframework.context.annotation.Bean;
4041
import org.springframework.context.annotation.Configuration;
@@ -54,7 +55,7 @@
5455
*
5556
* @author Vedran Pavic
5657
*/
57-
public class QuartzEndpointDocumentationTests extends MockMvcEndpointDocumentationTests {
58+
class QuartzEndpointDocumentationTests extends MockMvcEndpointDocumentationTests {
5859

5960
private static final JobDetail jobOne = JobBuilder.newJob(Job.class).withIdentity("jobOne", "groupOne")
6061
.withDescription("My first job").build();
@@ -79,50 +80,59 @@ public class QuartzEndpointDocumentationTests extends MockMvcEndpointDocumentati
7980
private Scheduler scheduler;
8081

8182
@Test
82-
public void quartzReport() throws Exception {
83+
void quartzReport() throws Exception {
8384
String groupOne = jobOne.getKey().getGroup();
8485
String groupTwo = jobThree.getKey().getGroup();
8586
given(this.scheduler.getJobGroupNames()).willReturn(Arrays.asList(groupOne, groupTwo));
8687
given(this.scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupOne)))
8788
.willReturn(new HashSet<>(Arrays.asList(jobOne.getKey(), jobTwo.getKey())));
8889
given(this.scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupTwo)))
8990
.willReturn(Collections.singleton(jobThree.getKey()));
90-
this.mockMvc.perform(get("/application/quartz")).andExpect(status().isOk()).andDo(document("quartz/report"));
91+
this.mockMvc.perform(get("/actuator/quartz")).andExpect(status().isOk()).andDo(document("quartz/report"));
9192
}
9293

9394
@Test
94-
public void quartzJob() throws Exception {
95+
void quartzJob() throws Exception {
9596
JobKey jobKey = jobOne.getKey();
9697
given(this.scheduler.getJobDetail(jobKey)).willReturn(jobOne);
97-
given(this.scheduler.getTriggersOfJob(jobKey)).willAnswer(invocation -> Arrays.asList(triggerOne, triggerTwo));
98-
this.mockMvc.perform(get("/application/quartz/groupOne/jobOne")).andExpect(status().isOk()).andDo(document(
98+
given(this.scheduler.getTriggersOfJob(jobKey))
99+
.willAnswer((invocation) -> Arrays.asList(triggerOne, triggerTwo));
100+
this.mockMvc.perform(get("/actuator/quartz/jobs/groupOne/jobOne")).andExpect(status().isOk()).andDo(document(
99101
"quartz/job",
100102
preprocessResponse(replacePattern(Pattern.compile("org.quartz.Job"), "com.example.MyJob")),
101-
responseFields(fieldWithPath("jobGroup").description("Job group."),
102-
fieldWithPath("jobName").description("Job name."),
103-
fieldWithPath("description").description("Job description, if any."),
103+
responseFields(fieldWithPath("group").description("Job group."),
104+
fieldWithPath("name").description("Job name."),
105+
fieldWithPath("description").description("Job description, if any.").optional(),
104106
fieldWithPath("className").description("Job class."),
105-
fieldWithPath("triggers.[].triggerGroup").description("Trigger group."),
106-
fieldWithPath("triggers.[].triggerName").description("Trigger name."),
107-
fieldWithPath("triggers.[].description").description("Trigger description, if any."),
108-
fieldWithPath("triggers.[].calendarName").description("Trigger's calendar name, if any."),
107+
fieldWithPath("triggers.[].group").description("Trigger group."),
108+
fieldWithPath("triggers.[].name").description("Trigger name."),
109+
fieldWithPath("triggers.[].description").description("Trigger description, if any.").optional(),
110+
fieldWithPath("triggers.[].calendarName").description("Trigger's calendar name, if any.")
111+
.optional(),
109112
fieldWithPath("triggers.[].startTime").description("Trigger's start time."),
110113
fieldWithPath("triggers.[].endTime").description("Trigger's end time."),
111-
fieldWithPath("triggers.[].nextFireTime").description("Trigger's next fire time."),
112-
fieldWithPath("triggers.[].previousFireTime")
113-
.description("Trigger's previous fire time, if any."),
114-
fieldWithPath("triggers.[].finalFireTime").description("Trigger's final fire time, if any."))));
114+
fieldWithPath("triggers.[].nextFireTime").type(Date.class)
115+
.description("Trigger's next fire time, if any.").optional(),
116+
fieldWithPath("triggers.[].previousFireTime").type(Date.class)
117+
.description("Trigger's previous fire time, if any.").optional(),
118+
fieldWithPath("triggers.[].finalFireTime").type(Date.class)
119+
.description("Trigger's final fire time, if any.").optional())));
115120
}
116121

117-
@Configuration
122+
@Configuration(proxyBeanMethods = false)
118123
@Import(BaseDocumentationConfiguration.class)
119124
static class TestConfiguration {
120125

121126
@Bean
122-
public QuartzEndpoint endpoint(Scheduler scheduler) {
127+
QuartzEndpoint endpoint(Scheduler scheduler) {
123128
return new QuartzEndpoint(scheduler);
124129
}
125130

131+
@Bean
132+
QuartzEndpointWebExtension endpointWebExtension(QuartzEndpoint endpoint) {
133+
return new QuartzEndpointWebExtension(endpoint);
134+
}
135+
126136
}
127137

128138
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/quartz/QuartzEndpointAutoConfigurationTests.java

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* https://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -16,7 +16,7 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.quartz;
1818

19-
import org.junit.Test;
19+
import org.junit.jupiter.api.Test;
2020
import org.quartz.Scheduler;
2121

2222
import org.springframework.boot.actuate.quartz.QuartzEndpoint;
@@ -32,30 +32,59 @@
3232
* Tests for {@link QuartzEndpointAutoConfiguration}.
3333
*
3434
* @author Vedran Pavic
35+
* @author Stephane Nicoll
3536
*/
36-
public class QuartzEndpointAutoConfigurationTests {
37+
class QuartzEndpointAutoConfigurationTests {
3738

3839
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
39-
.withConfiguration(AutoConfigurations.of(QuartzEndpointAutoConfiguration.class))
40-
.withUserConfiguration(QuartzConfiguration.class);
40+
.withConfiguration(AutoConfigurations.of(QuartzEndpointAutoConfiguration.class));
4141

4242
@Test
43-
public void runShouldHaveEndpointBean() {
44-
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(QuartzEndpoint.class));
43+
void endpointIsAutoConfigured() {
44+
this.contextRunner.withBean(Scheduler.class, () -> mock(Scheduler.class))
45+
.withPropertyValues("management.endpoints.web.exposure.include=quartz")
46+
.run((context) -> assertThat(context).hasSingleBean(QuartzEndpoint.class));
4547
}
4648

4749
@Test
48-
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointBean() throws Exception {
49-
this.contextRunner.withPropertyValues("management.endpoint.quartz.enabled:false")
50+
void endpointIsNotAutoConfiguredIfSchedulerIsNotAvailable() {
51+
this.contextRunner.withPropertyValues("management.endpoints.web.exposure.include=quartz")
5052
.run((context) -> assertThat(context).doesNotHaveBean(QuartzEndpoint.class));
5153
}
5254

53-
@Configuration
54-
static class QuartzConfiguration {
55+
@Test
56+
void endpointNotAutoConfiguredWhenNotExposed() {
57+
this.contextRunner.withBean(Scheduler.class, () -> mock(Scheduler.class))
58+
.run((context) -> assertThat(context).doesNotHaveBean(QuartzEndpoint.class));
59+
}
60+
61+
@Test
62+
void endpointCanBeDisabled() {
63+
this.contextRunner.withBean(Scheduler.class, () -> mock(Scheduler.class))
64+
.withPropertyValues("management.endpoint.quartz.enabled:false")
65+
.run((context) -> assertThat(context).doesNotHaveBean(QuartzEndpoint.class));
66+
}
67+
68+
@Test
69+
void endpointBacksOffWhenUserProvidedEndpointIsPresent() {
70+
this.contextRunner.withUserConfiguration(CustomEndpointConfiguration.class)
71+
.run((context) -> assertThat(context).hasSingleBean(QuartzEndpoint.class).hasBean("customEndpoint"));
72+
}
73+
74+
@Configuration(proxyBeanMethods = false)
75+
static class CustomEndpointConfiguration {
5576

5677
@Bean
57-
public Scheduler scheduler() {
58-
return mock(Scheduler.class);
78+
CustomEndpoint customEndpoint() {
79+
return new CustomEndpoint();
80+
}
81+
82+
}
83+
84+
private static final class CustomEndpoint extends QuartzEndpoint {
85+
86+
private CustomEndpoint() {
87+
super(mock(Scheduler.class));
5988
}
6089

6190
}

0 commit comments

Comments
 (0)