Skip to content

Commit 26da45a

Browse files
committed
Configure a JerseyApplicationPath bean for the actuators
This commit also ensures that Jersey-based actuator endpoints are available before the user has configured a `ResourceConfig` bean Fixes gh-15625 Fixes gh-15877
1 parent c24f026 commit 26da45a

File tree

5 files changed

+212
-38
lines changed

5 files changed

+212
-38
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/jersey/JerseyWebEndpointManagementContextConfiguration.java

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
import java.util.List;
2424

2525
import org.glassfish.jersey.server.ResourceConfig;
26+
import org.glassfish.jersey.servlet.ServletContainer;
2627

28+
import org.springframework.beans.factory.ObjectProvider;
2729
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
2830
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
2931
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
@@ -40,15 +42,22 @@
4042
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
4143
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
4244
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
45+
import org.springframework.boot.autoconfigure.jersey.JerseyProperties;
4346
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
47+
import org.springframework.boot.autoconfigure.web.servlet.DefaultJerseyApplicationPath;
48+
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
49+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
50+
import org.springframework.boot.web.servlet.ServletRegistrationBean;
4451
import org.springframework.context.annotation.Bean;
52+
import org.springframework.context.annotation.Configuration;
4553

4654
/**
4755
* {@link ManagementContextConfiguration} for Jersey {@link Endpoint} concerns.
4856
*
4957
* @author Andy Wilkinson
5058
* @author Phillip Webb
5159
* @author Michael Simons
60+
* @author Madhura Bhave
5261
*/
5362
@ManagementContextConfiguration
5463
@ConditionalOnWebApplication(type = Type.SERVLET)
@@ -57,12 +66,6 @@
5766
@ConditionalOnMissingBean(type = "org.springframework.web.servlet.DispatcherServlet")
5867
class JerseyWebEndpointManagementContextConfiguration {
5968

60-
@ConditionalOnMissingBean(ResourceConfig.class)
61-
@Bean
62-
public ResourceConfig resourceConfig() {
63-
return new ResourceConfig();
64-
}
65-
6669
@Bean
6770
public ResourceConfigCustomizer webEndpointRegistrar(
6871
WebEndpointsSupplier webEndpointsSupplier,
@@ -85,4 +88,37 @@ public ResourceConfigCustomizer webEndpointRegistrar(
8588
};
8689
}
8790

91+
@Configuration
92+
@ConditionalOnMissingBean(ResourceConfig.class)
93+
@EnableConfigurationProperties(JerseyProperties.class)
94+
static class ResourceConfigConfiguration {
95+
96+
@Bean
97+
public ResourceConfig resourceConfig(
98+
ObjectProvider<ResourceConfigCustomizer> resourceConfigCustomizers) {
99+
ResourceConfig resourceConfig = new ResourceConfig();
100+
resourceConfigCustomizers.orderedStream()
101+
.forEach((customizer) -> customizer.customize(resourceConfig));
102+
return resourceConfig;
103+
}
104+
105+
@Bean
106+
@ConditionalOnMissingBean
107+
public JerseyApplicationPath jerseyApplicationPath(JerseyProperties properties,
108+
ResourceConfig config) {
109+
return new DefaultJerseyApplicationPath(properties.getApplicationPath(),
110+
config);
111+
}
112+
113+
@Bean
114+
public ServletRegistrationBean<ServletContainer> jerseyServletRegistration(
115+
ObjectProvider<ResourceConfigCustomizer> resourceConfigCustomizers,
116+
JerseyApplicationPath jerseyApplicationPath) {
117+
return new ServletRegistrationBean<>(
118+
new ServletContainer(resourceConfig(resourceConfigCustomizers)),
119+
jerseyApplicationPath.getUrlMapping());
120+
}
121+
122+
}
123+
88124
}

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

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,43 +19,108 @@
1919
import java.util.Collections;
2020

2121
import org.glassfish.jersey.server.ResourceConfig;
22+
import org.glassfish.jersey.servlet.ServletContainer;
2223
import org.junit.Test;
2324

2425
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
2526
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
2627
import org.springframework.boot.autoconfigure.AutoConfigurations;
28+
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
29+
import org.springframework.boot.autoconfigure.web.servlet.DefaultJerseyApplicationPath;
30+
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
2731
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
32+
import org.springframework.boot.web.servlet.ServletRegistrationBean;
2833
import org.springframework.context.annotation.Bean;
2934
import org.springframework.context.annotation.Configuration;
3035

3136
import static org.assertj.core.api.Assertions.assertThat;
37+
import static org.mockito.Mockito.mock;
38+
import static org.mockito.Mockito.verify;
3239

3340
/**
3441
* Tests for {@link JerseyWebEndpointManagementContextConfiguration}.
3542
*
3643
* @author Michael Simons
44+
* @author Madhura Bhave
3745
*/
3846
public class JerseyWebEndpointManagementContextConfigurationTests {
3947

4048
private final WebApplicationContextRunner runner = new WebApplicationContextRunner()
4149
.withConfiguration(AutoConfigurations.of(WebEndpointAutoConfiguration.class,
42-
JerseyWebEndpointManagementContextConfiguration.class));
50+
JerseyWebEndpointManagementContextConfiguration.class))
51+
.withUserConfiguration(WebEndpointsSupplierConfig.class);
4352

4453
@Test
4554
public void resourceConfigIsAutoConfiguredWhenNeeded() {
46-
this.runner.withUserConfiguration(WebEndpointsSupplierConfig.class).run(
55+
this.runner.run(
4756
(context) -> assertThat(context).hasSingleBean(ResourceConfig.class));
4857
}
4958

5059
@Test
51-
public void existingResourceConfigIsUsedWhenAvailable() {
52-
this.runner.withUserConfiguration(WebEndpointsSupplierConfig.class,
53-
ConfigWithResourceConfig.class).run((context) -> {
60+
public void jerseyApplicationPathIsAutoConfiguredWhenNeeded() {
61+
this.runner.run((context) -> assertThat(context)
62+
.hasSingleBean(DefaultJerseyApplicationPath.class));
63+
}
64+
65+
@Test
66+
public void jerseyApplicationPathIsConditionalOnMissinBean() {
67+
this.runner.withUserConfiguration(ConfigWithJerseyApplicationPath.class)
68+
.run((context) -> {
69+
assertThat(context).hasSingleBean(JerseyApplicationPath.class);
70+
assertThat(context).hasBean("testJerseyApplicationPath");
71+
});
72+
}
73+
74+
@Test
75+
@SuppressWarnings("unchecked")
76+
public void servletRegistrationBeanIsAutoConfiguredWhenNeeded() {
77+
this.runner.withPropertyValues("spring.jersey.application-path=/jersey")
78+
.run((context) -> {
79+
ServletRegistrationBean<ServletContainer> bean = context
80+
.getBean(ServletRegistrationBean.class);
81+
assertThat(bean.getUrlMappings()).containsExactly("/jersey/*");
82+
});
83+
}
84+
85+
@Test
86+
public void existingResourceConfigBeanShouldNotAutoConfigureRelatedBeans() {
87+
this.runner.withUserConfiguration(ConfigWithResourceConfig.class)
88+
.run((context) -> {
5489
assertThat(context).hasSingleBean(ResourceConfig.class);
90+
assertThat(context).doesNotHaveBean(JerseyApplicationPath.class);
91+
assertThat(context).doesNotHaveBean(ServletRegistrationBean.class);
5592
assertThat(context).hasBean("customResourceConfig");
5693
});
5794
}
5895

96+
@Test
97+
public void resourceConfigIsCustomizedWithResourceConfigCustomizerBean() {
98+
this.runner.withUserConfiguration(CustomizerConfiguration.class)
99+
.run((context) -> {
100+
assertThat(context).hasSingleBean(ResourceConfig.class);
101+
ResourceConfig config = context.getBean(ResourceConfig.class);
102+
ResourceConfigCustomizer customizer = (ResourceConfigCustomizer) context
103+
.getBean("testResourceConfigCustomizer");
104+
verify(customizer).customize(config);
105+
});
106+
}
107+
108+
@Test
109+
public void resourceConfigCustomizerBeanIsNotRequired() {
110+
this.runner.run(
111+
(context) -> assertThat(context).hasSingleBean(ResourceConfig.class));
112+
}
113+
114+
@Configuration
115+
static class CustomizerConfiguration {
116+
117+
@Bean
118+
ResourceConfigCustomizer testResourceConfigCustomizer() {
119+
return mock(ResourceConfigCustomizer.class);
120+
}
121+
122+
}
123+
59124
@Configuration
60125
static class WebEndpointsSupplierConfig {
61126

@@ -76,4 +141,14 @@ public ResourceConfig customResourceConfig() {
76141

77142
}
78143

144+
@Configuration
145+
static class ConfigWithJerseyApplicationPath {
146+
147+
@Bean
148+
public JerseyApplicationPath testJerseyApplicationPath() {
149+
return mock(JerseyApplicationPath.class);
150+
}
151+
152+
}
153+
79154
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/JerseyEndpointIntegrationTests.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,22 @@
4141
* Integration tests for the Jersey actuator endpoints.
4242
*
4343
* @author Andy Wilkinson
44+
* @author Madhura Bhave
4445
*/
4546
public class JerseyEndpointIntegrationTests {
4647

4748
@Test
48-
public void linksAreProvidedToAllEndpointTypes() throws Exception {
49+
public void linksAreProvidedToAllEndpointTypes() {
50+
testJerseyEndpoints(new Class[] { EndpointsConfiguration.class,
51+
ResourceConfigConfiguration.class });
52+
}
53+
54+
@Test
55+
public void actuatorEndpointsWhenUserProvidedResourceConfigBeanNotAvailable() {
56+
testJerseyEndpoints(new Class[] { EndpointsConfiguration.class });
57+
}
58+
59+
protected void testJerseyEndpoints(Class[] userConfigurations) {
4960
FilteredClassLoader classLoader = new FilteredClassLoader(
5061
DispatcherServlet.class);
5162
new WebApplicationContextRunner(
@@ -59,7 +70,7 @@ public void linksAreProvidedToAllEndpointTypes() throws Exception {
5970
WebEndpointAutoConfiguration.class,
6071
ManagementContextAutoConfiguration.class,
6172
BeansEndpointAutoConfiguration.class))
62-
.withUserConfiguration(EndpointsConfiguration.class)
73+
.withUserConfiguration(userConfigurations)
6374
.withPropertyValues("management.endpoints.web.exposure.include:*",
6475
"server.port:0")
6576
.run((context) -> {
@@ -88,11 +99,6 @@ static class TestRestControllerEndpoint {
8899
@Configuration
89100
static class EndpointsConfiguration {
90101

91-
@Bean
92-
ResourceConfig testResourceConfig() {
93-
return new ResourceConfig();
94-
}
95-
96102
@Bean
97103
TestControllerEndpoint testControllerEndpoint() {
98104
return new TestControllerEndpoint();
@@ -105,4 +111,14 @@ TestRestControllerEndpoint testRestControllerEndpoint() {
105111

106112
}
107113

114+
@Configuration
115+
static class ResourceConfigConfiguration {
116+
117+
@Bean
118+
ResourceConfig testResourceConfig() {
119+
return new ResourceConfig();
120+
}
121+
122+
}
123+
108124
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import javax.servlet.ServletContext;
2525
import javax.servlet.ServletException;
2626
import javax.servlet.ServletRegistration;
27-
import javax.ws.rs.ApplicationPath;
2827
import javax.ws.rs.ext.ContextResolver;
2928

3029
import com.fasterxml.jackson.databind.AnnotationIntrospector;
@@ -51,6 +50,7 @@
5150
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
5251
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
5352
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
53+
import org.springframework.boot.autoconfigure.web.servlet.DefaultJerseyApplicationPath;
5454
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
5555
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
5656
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -60,10 +60,8 @@
6060
import org.springframework.context.annotation.Bean;
6161
import org.springframework.context.annotation.Configuration;
6262
import org.springframework.core.Ordered;
63-
import org.springframework.core.annotation.AnnotationUtils;
6463
import org.springframework.core.annotation.Order;
6564
import org.springframework.util.ClassUtils;
66-
import org.springframework.util.StringUtils;
6765
import org.springframework.web.WebApplicationInitializer;
6866
import org.springframework.web.context.ServletContextAware;
6967

@@ -114,15 +112,8 @@ private void customize() {
114112
@Bean
115113
@ConditionalOnMissingBean
116114
public JerseyApplicationPath jerseyApplicationPath() {
117-
return this::resolveApplicationPath;
118-
}
119-
120-
private String resolveApplicationPath() {
121-
if (StringUtils.hasLength(this.jersey.getApplicationPath())) {
122-
return this.jersey.getApplicationPath();
123-
}
124-
return findApplicationPath(AnnotationUtils.findAnnotation(
125-
this.config.getApplication().getClass(), ApplicationPath.class));
115+
return new DefaultJerseyApplicationPath(this.jersey.getApplicationPath(),
116+
this.config);
126117
}
127118

128119
@Bean
@@ -171,14 +162,6 @@ private void addInitParameters(DynamicRegistrationBean<?> registration) {
171162
this.jersey.getInit().forEach(registration::addInitParameter);
172163
}
173164

174-
private static String findApplicationPath(ApplicationPath annotation) {
175-
// Jersey doesn't like to be the default servlet, so map to /* as a fallback
176-
if (annotation == null) {
177-
return "/*";
178-
}
179-
return annotation.value();
180-
}
181-
182165
@Override
183166
public void setServletContext(ServletContext servletContext) {
184167
String servletRegistrationName = getServletRegistrationName();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2012-2018 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+
package org.springframework.boot.autoconfigure.web.servlet;
17+
18+
import javax.ws.rs.ApplicationPath;
19+
20+
import org.glassfish.jersey.server.ResourceConfig;
21+
22+
import org.springframework.boot.autoconfigure.jersey.JerseyProperties;
23+
import org.springframework.core.annotation.AnnotationUtils;
24+
import org.springframework.util.StringUtils;
25+
26+
/**
27+
* Default implementation of {@link JerseyApplicationPath} that derives the path from
28+
* {@link JerseyProperties} or the {@code @ApplicationPath} annotation.
29+
*
30+
* @author Madhura Bhave
31+
*/
32+
public class DefaultJerseyApplicationPath implements JerseyApplicationPath {
33+
34+
private final String applicationPath;
35+
36+
private final ResourceConfig config;
37+
38+
public DefaultJerseyApplicationPath(String applicationPath, ResourceConfig config) {
39+
this.applicationPath = applicationPath;
40+
this.config = config;
41+
}
42+
43+
@Override
44+
public String getPath() {
45+
return resolveApplicationPath();
46+
}
47+
48+
private String resolveApplicationPath() {
49+
if (StringUtils.hasLength(this.applicationPath)) {
50+
return this.applicationPath;
51+
}
52+
return findApplicationPath(AnnotationUtils.findAnnotation(
53+
this.config.getApplication().getClass(), ApplicationPath.class));
54+
}
55+
56+
private static String findApplicationPath(ApplicationPath annotation) {
57+
// Jersey doesn't like to be the default servlet, so map to /* as a fallback
58+
if (annotation == null) {
59+
return "/*";
60+
}
61+
return annotation.value();
62+
}
63+
64+
}

0 commit comments

Comments
 (0)