Skip to content

Commit ecf3934

Browse files
mbhavephilwebb
andcommitted
Skip management context ResourceConfigCustomizers
Update `JerseyManagementContextConfiguration` so that customizer beans are not longer applied. The endpoint resource endpoints are now added with a registrar bean `@PostConstruct` method. Prior to this commit, when running the management server on a different port a `Resource` added by a customizer could be added two different `ResourceConfig` instance. This breaks the singleton contract expected by Jersey. Fixes gh-17801 Co-authored-by: Phillip Webb <[email protected]>
1 parent 3a63179 commit ecf3934

File tree

7 files changed

+170
-71
lines changed

7 files changed

+170
-71
lines changed

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

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,23 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.Collection;
21-
import java.util.Collections;
2221
import java.util.HashSet;
2322
import java.util.List;
2423

24+
import javax.annotation.PostConstruct;
25+
2526
import org.glassfish.jersey.server.ResourceConfig;
27+
import org.glassfish.jersey.server.model.Resource;
2628

29+
import org.springframework.beans.factory.ObjectProvider;
2730
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
2831
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
2932
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
3033
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
3134
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
3235
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
3336
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
37+
import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint;
3438
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
3539
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
3640
import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier;
@@ -59,21 +63,69 @@
5963
class JerseyWebEndpointManagementContextConfiguration {
6064

6165
@Bean
62-
public ResourceConfigCustomizer webEndpointRegistrar(WebEndpointsSupplier webEndpointsSupplier,
66+
JerseyWebEndpointsResourcesRegistrar jerseyWebEndpointsResourcesRegistrar(
67+
ObjectProvider<ResourceConfig> resourceConfig, WebEndpointsSupplier webEndpointsSupplier,
6368
ServletEndpointsSupplier servletEndpointsSupplier, EndpointMediaTypes endpointMediaTypes,
6469
WebEndpointProperties webEndpointProperties) {
65-
List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>();
66-
allEndpoints.addAll(webEndpointsSupplier.getEndpoints());
67-
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
68-
return (resourceConfig) -> {
70+
return new JerseyWebEndpointsResourcesRegistrar(resourceConfig.getIfAvailable(), webEndpointsSupplier,
71+
servletEndpointsSupplier, endpointMediaTypes, webEndpointProperties.getBasePath());
72+
}
73+
74+
/**
75+
* Register endpoints with the {@link ResourceConfig}. The
76+
* {@link ResourceConfigCustomizer} cannot be used because we don't want to apply
77+
*/
78+
static class JerseyWebEndpointsResourcesRegistrar {
79+
80+
private final ResourceConfig resourceConfig;
81+
82+
private final WebEndpointsSupplier webEndpointsSupplier;
83+
84+
private final ServletEndpointsSupplier servletEndpointsSupplier;
85+
86+
private final EndpointMediaTypes mediaTypes;
87+
88+
private final String basePath;
89+
90+
JerseyWebEndpointsResourcesRegistrar(ResourceConfig resourceConfig, WebEndpointsSupplier webEndpointsSupplier,
91+
ServletEndpointsSupplier servletEndpointsSupplier, EndpointMediaTypes endpointMediaTypes,
92+
String basePath) {
93+
super();
94+
this.resourceConfig = resourceConfig;
95+
this.webEndpointsSupplier = webEndpointsSupplier;
96+
this.servletEndpointsSupplier = servletEndpointsSupplier;
97+
this.mediaTypes = endpointMediaTypes;
98+
this.basePath = basePath;
99+
}
100+
101+
@PostConstruct
102+
void register() {
103+
// We can't easily use @ConditionalOnBean because @AutoConfigureBefore is
104+
// not an option for management contexts. Instead we manually check if
105+
// the resource config bean exists
106+
if (this.resourceConfig == null) {
107+
return;
108+
}
109+
Collection<ExposableWebEndpoint> webEndpoints = this.webEndpointsSupplier.getEndpoints();
110+
Collection<ExposableServletEndpoint> servletEndpoints = this.servletEndpointsSupplier.getEndpoints();
111+
EndpointLinksResolver linksResolver = getLinksResolver(webEndpoints, servletEndpoints);
112+
EndpointMapping mapping = new EndpointMapping(this.basePath);
69113
JerseyEndpointResourceFactory resourceFactory = new JerseyEndpointResourceFactory();
70-
String basePath = webEndpointProperties.getBasePath();
71-
EndpointMapping endpointMapping = new EndpointMapping(basePath);
72-
Collection<ExposableWebEndpoint> webEndpoints = Collections
73-
.unmodifiableCollection(webEndpointsSupplier.getEndpoints());
74-
resourceConfig.registerResources(new HashSet<>(resourceFactory.createEndpointResources(endpointMapping,
75-
webEndpoints, endpointMediaTypes, new EndpointLinksResolver(allEndpoints, basePath))));
76-
};
114+
register(resourceFactory.createEndpointResources(mapping, webEndpoints, this.mediaTypes, linksResolver));
115+
}
116+
117+
private EndpointLinksResolver getLinksResolver(Collection<ExposableWebEndpoint> webEndpoints,
118+
Collection<ExposableServletEndpoint> servletEndpoints) {
119+
List<ExposableEndpoint<?>> endpoints = new ArrayList<>(webEndpoints.size() + servletEndpoints.size());
120+
endpoints.addAll(webEndpoints);
121+
endpoints.addAll(servletEndpoints);
122+
return new EndpointLinksResolver(endpoints, this.basePath);
123+
}
124+
125+
private void register(Collection<Resource> resources) {
126+
this.resourceConfig.registerResources(new HashSet<>(resources));
127+
}
128+
77129
}
78130

79131
}

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
import org.glassfish.jersey.server.ResourceConfig;
1919
import org.glassfish.jersey.servlet.ServletContainer;
2020

21-
import org.springframework.beans.factory.ObjectProvider;
22-
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
2321
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
2422
import org.springframework.boot.web.servlet.ServletRegistrationBean;
2523
import org.springframework.context.annotation.Bean;
@@ -41,10 +39,8 @@ public ServletRegistrationBean<ServletContainer> jerseyServletRegistration(
4139
}
4240

4341
@Bean
44-
public ResourceConfig resourceConfig(ObjectProvider<ResourceConfigCustomizer> resourceConfigCustomizers) {
45-
ResourceConfig resourceConfig = new ResourceConfig();
46-
resourceConfigCustomizers.orderedStream().forEach((customizer) -> customizer.customize(resourceConfig));
47-
return resourceConfig;
42+
public ResourceConfig resourceConfig() {
43+
return new ResourceConfig();
4844
}
4945

5046
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@
3838
* @since 2.1.0
3939
*/
4040
@ManagementContextConfiguration(ManagementContextType.SAME)
41-
@ConditionalOnMissingBean(ResourceConfig.class)
4241
@Import(JerseyManagementContextConfiguration.class)
4342
@EnableConfigurationProperties(JerseyProperties.class)
43+
@ConditionalOnMissingBean(ResourceConfig.class)
4444
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
4545
@ConditionalOnClass(ResourceConfig.class)
4646
@ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet")

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222
import org.junit.Test;
2323

2424
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
25+
import org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey.JerseyWebEndpointManagementContextConfiguration.JerseyWebEndpointsResourcesRegistrar;
2526
import org.springframework.boot.actuate.autoconfigure.web.jersey.JerseySameManagementContextConfiguration;
2627
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
2728
import org.springframework.boot.autoconfigure.AutoConfigurations;
28-
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
2929
import org.springframework.boot.test.context.FilteredClassLoader;
3030
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
3131
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
@@ -45,11 +45,11 @@ public class JerseyWebEndpointManagementContextConfigurationTests {
4545
private final WebApplicationContextRunner runner = new WebApplicationContextRunner()
4646
.withConfiguration(AutoConfigurations.of(WebEndpointAutoConfiguration.class,
4747
JerseyWebEndpointManagementContextConfiguration.class))
48-
.withUserConfiguration(WebEndpointsSupplierConfig.class);
48+
.withUserConfiguration(ResourceConfig.class, WebEndpointsSupplierConfig.class);
4949

5050
@Test
51-
public void resourceConfigCustomizerForEndpointsIsAutoConfigured() {
52-
this.runner.run((context) -> assertThat(context).hasSingleBean(ResourceConfigCustomizer.class));
51+
public void jerseyWebEndpointsResourcesRegistrarForEndpointsIsAutoConfigured() {
52+
this.runner.run((context) -> assertThat(context).hasSingleBean(JerseyWebEndpointsResourcesRegistrar.class));
5353
}
5454

5555
@Test

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

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,15 @@
2222
import org.junit.runner.RunWith;
2323

2424
import org.springframework.boot.autoconfigure.AutoConfigurations;
25-
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
2625
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
2726
import org.springframework.boot.test.context.FilteredClassLoader;
2827
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
2928
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
3029
import org.springframework.boot.testsupport.runner.classpath.ClassPathExclusions;
3130
import org.springframework.boot.testsupport.runner.classpath.ModifiedClassPathRunner;
3231
import org.springframework.boot.web.servlet.ServletRegistrationBean;
33-
import org.springframework.context.annotation.Bean;
34-
import org.springframework.context.annotation.Configuration;
3532

3633
import static org.assertj.core.api.Assertions.assertThat;
37-
import static org.mockito.Mockito.mock;
38-
import static org.mockito.Mockito.verify;
3934

4035
/**
4136
* Tests for {@link JerseyChildManagementContextConfiguration}.
@@ -64,16 +59,6 @@ public void autoConfigurationIsConditionalOnClassResourceConfig() {
6459
.run((context) -> assertThat(context).doesNotHaveBean(JerseySameManagementContextConfiguration.class));
6560
}
6661

67-
@Test
68-
public void resourceConfigIsCustomizedWithResourceConfigCustomizerBean() {
69-
this.contextRunner.withUserConfiguration(CustomizerConfiguration.class).run((context) -> {
70-
assertThat(context).hasSingleBean(ResourceConfig.class);
71-
ResourceConfig config = context.getBean(ResourceConfig.class);
72-
ResourceConfigCustomizer customizer = context.getBean(ResourceConfigCustomizer.class);
73-
verify(customizer).customize(config);
74-
});
75-
}
76-
7762
@Test
7863
public void jerseyApplicationPathIsAutoConfigured() {
7964
this.contextRunner.run((context) -> {
@@ -96,14 +81,4 @@ public void resourceConfigCustomizerBeanIsNotRequired() {
9681
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ResourceConfig.class));
9782
}
9883

99-
@Configuration
100-
static class CustomizerConfiguration {
101-
102-
@Bean
103-
ResourceConfigCustomizer resourceConfigCustomizer() {
104-
return mock(ResourceConfigCustomizer.class);
105-
}
106-
107-
}
108-
10984
}

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

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import org.junit.runner.RunWith;
2222

2323
import org.springframework.boot.autoconfigure.AutoConfigurations;
24-
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
2524
import org.springframework.boot.autoconfigure.web.servlet.DefaultJerseyApplicationPath;
2625
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
2726
import org.springframework.boot.test.context.FilteredClassLoader;
@@ -35,7 +34,6 @@
3534

3635
import static org.assertj.core.api.Assertions.assertThat;
3736
import static org.mockito.Mockito.mock;
38-
import static org.mockito.Mockito.verify;
3937

4038
/**
4139
* Tests for {@link JerseySameManagementContextConfiguration}.
@@ -63,16 +61,6 @@ public void autoConfigurationIsConditionalOnClassResourceConfig() {
6361
.run((context) -> assertThat(context).doesNotHaveBean(JerseySameManagementContextConfiguration.class));
6462
}
6563

66-
@Test
67-
public void resourceConfigIsCustomizedWithResourceConfigCustomizerBean() {
68-
this.contextRunner.withUserConfiguration(CustomizerConfiguration.class).run((context) -> {
69-
assertThat(context).hasSingleBean(ResourceConfig.class);
70-
ResourceConfig config = context.getBean(ResourceConfig.class);
71-
ResourceConfigCustomizer customizer = context.getBean(ResourceConfigCustomizer.class);
72-
verify(customizer).customize(config);
73-
});
74-
}
75-
7664
@Test
7765
public void jerseyApplicationPathIsAutoConfiguredWhenNeeded() {
7866
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(DefaultJerseyApplicationPath.class));
@@ -125,14 +113,4 @@ public ResourceConfig customResourceConfig() {
125113

126114
}
127115

128-
@Configuration
129-
static class CustomizerConfiguration {
130-
131-
@Bean
132-
ResourceConfigCustomizer resourceConfigCustomizer() {
133-
return mock(ResourceConfigCustomizer.class);
134-
}
135-
136-
}
137-
138116
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2012-2019 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+
* https://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 sample.jersey;
18+
19+
import javax.ws.rs.GET;
20+
import javax.ws.rs.Path;
21+
22+
import org.glassfish.jersey.server.ResourceConfig;
23+
import org.junit.Test;
24+
import org.junit.runner.RunWith;
25+
26+
import org.springframework.beans.factory.annotation.Autowired;
27+
import org.springframework.boot.actuate.autoconfigure.web.server.LocalManagementPort;
28+
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
29+
import org.springframework.boot.test.context.SpringBootTest;
30+
import org.springframework.boot.test.context.TestConfiguration;
31+
import org.springframework.boot.test.web.client.TestRestTemplate;
32+
import org.springframework.boot.web.server.LocalServerPort;
33+
import org.springframework.context.annotation.Bean;
34+
import org.springframework.http.HttpStatus;
35+
import org.springframework.http.ResponseEntity;
36+
import org.springframework.test.context.junit4.SpringRunner;
37+
38+
import static org.assertj.core.api.Assertions.assertThat;
39+
40+
/**
41+
* Integration tests for separate management and main service ports.
42+
*
43+
* @author Madhura Bhave
44+
*/
45+
@RunWith(SpringRunner.class)
46+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = "management.server.port=0")
47+
public class JerseyManagementPortTests {
48+
49+
@LocalServerPort
50+
private int port;
51+
52+
@LocalManagementPort
53+
private int managementPort;
54+
55+
@Autowired
56+
private TestRestTemplate testRestTemplate;
57+
58+
@Test
59+
public void resourceShouldBeAvailableOnMainPort() {
60+
ResponseEntity<String> entity = this.testRestTemplate.getForEntity("http://localhost:" + this.port + "/test",
61+
String.class);
62+
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
63+
assertThat(entity.getBody()).contains("test");
64+
}
65+
66+
@Test
67+
public void resourceShouldNotBeAvailableOnManagementPort() {
68+
ResponseEntity<String> entity = this.testRestTemplate
69+
.getForEntity("http://localhost:" + this.managementPort + "/test", String.class);
70+
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
71+
}
72+
73+
@TestConfiguration
74+
static class ResourceConfigConfiguration {
75+
76+
@Bean
77+
public ResourceConfigCustomizer customizer() {
78+
return new ResourceConfigCustomizer() {
79+
@Override
80+
public void customize(ResourceConfig config) {
81+
config.register(TestEndpoint.class);
82+
}
83+
};
84+
}
85+
86+
@Path("/test")
87+
public static class TestEndpoint {
88+
89+
@GET
90+
public String test() {
91+
return "test";
92+
}
93+
94+
}
95+
96+
}
97+
98+
}

0 commit comments

Comments
 (0)