Skip to content

Commit

Permalink
Configure a JerseyApplicationPath bean for the actuators
Browse files Browse the repository at this point in the history
This commit also ensures that Jersey-based actuator endpoints are
available before the user has configured a `ResourceConfig` bean

Fixes spring-projectsgh-15625
Fixes spring-projectsgh-15877
  • Loading branch information
mbhave committed Feb 9, 2019
1 parent c24f026 commit 26da45a
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
import java.util.List;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
Expand All @@ -40,15 +42,22 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.jersey.JerseyProperties;
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
import org.springframework.boot.autoconfigure.web.servlet.DefaultJerseyApplicationPath;
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* {@link ManagementContextConfiguration} for Jersey {@link Endpoint} concerns.
*
* @author Andy Wilkinson
* @author Phillip Webb
* @author Michael Simons
* @author Madhura Bhave
*/
@ManagementContextConfiguration
@ConditionalOnWebApplication(type = Type.SERVLET)
Expand All @@ -57,12 +66,6 @@
@ConditionalOnMissingBean(type = "org.springframework.web.servlet.DispatcherServlet")
class JerseyWebEndpointManagementContextConfiguration {

@ConditionalOnMissingBean(ResourceConfig.class)
@Bean
public ResourceConfig resourceConfig() {
return new ResourceConfig();
}

@Bean
public ResourceConfigCustomizer webEndpointRegistrar(
WebEndpointsSupplier webEndpointsSupplier,
Expand All @@ -85,4 +88,37 @@ public ResourceConfigCustomizer webEndpointRegistrar(
};
}

@Configuration
@ConditionalOnMissingBean(ResourceConfig.class)
@EnableConfigurationProperties(JerseyProperties.class)
static class ResourceConfigConfiguration {

@Bean
public ResourceConfig resourceConfig(
ObjectProvider<ResourceConfigCustomizer> resourceConfigCustomizers) {
ResourceConfig resourceConfig = new ResourceConfig();
resourceConfigCustomizers.orderedStream()
.forEach((customizer) -> customizer.customize(resourceConfig));
return resourceConfig;
}

@Bean
@ConditionalOnMissingBean
public JerseyApplicationPath jerseyApplicationPath(JerseyProperties properties,
ResourceConfig config) {
return new DefaultJerseyApplicationPath(properties.getApplicationPath(),
config);
}

@Bean
public ServletRegistrationBean<ServletContainer> jerseyServletRegistration(
ObjectProvider<ResourceConfigCustomizer> resourceConfigCustomizers,
JerseyApplicationPath jerseyApplicationPath) {
return new ServletRegistrationBean<>(
new ServletContainer(resourceConfig(resourceConfigCustomizers)),
jerseyApplicationPath.getUrlMapping());
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,43 +19,108 @@
import java.util.Collections;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.junit.Test;

import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
import org.springframework.boot.autoconfigure.web.servlet.DefaultJerseyApplicationPath;
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
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;
import static org.mockito.Mockito.verify;

/**
* Tests for {@link JerseyWebEndpointManagementContextConfiguration}.
*
* @author Michael Simons
* @author Madhura Bhave
*/
public class JerseyWebEndpointManagementContextConfigurationTests {

private final WebApplicationContextRunner runner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(WebEndpointAutoConfiguration.class,
JerseyWebEndpointManagementContextConfiguration.class));
JerseyWebEndpointManagementContextConfiguration.class))
.withUserConfiguration(WebEndpointsSupplierConfig.class);

@Test
public void resourceConfigIsAutoConfiguredWhenNeeded() {
this.runner.withUserConfiguration(WebEndpointsSupplierConfig.class).run(
this.runner.run(
(context) -> assertThat(context).hasSingleBean(ResourceConfig.class));
}

@Test
public void existingResourceConfigIsUsedWhenAvailable() {
this.runner.withUserConfiguration(WebEndpointsSupplierConfig.class,
ConfigWithResourceConfig.class).run((context) -> {
public void jerseyApplicationPathIsAutoConfiguredWhenNeeded() {
this.runner.run((context) -> assertThat(context)
.hasSingleBean(DefaultJerseyApplicationPath.class));
}

@Test
public void jerseyApplicationPathIsConditionalOnMissinBean() {
this.runner.withUserConfiguration(ConfigWithJerseyApplicationPath.class)
.run((context) -> {
assertThat(context).hasSingleBean(JerseyApplicationPath.class);
assertThat(context).hasBean("testJerseyApplicationPath");
});
}

@Test
@SuppressWarnings("unchecked")
public void servletRegistrationBeanIsAutoConfiguredWhenNeeded() {
this.runner.withPropertyValues("spring.jersey.application-path=/jersey")
.run((context) -> {
ServletRegistrationBean<ServletContainer> bean = context
.getBean(ServletRegistrationBean.class);
assertThat(bean.getUrlMappings()).containsExactly("/jersey/*");
});
}

@Test
public void existingResourceConfigBeanShouldNotAutoConfigureRelatedBeans() {
this.runner.withUserConfiguration(ConfigWithResourceConfig.class)
.run((context) -> {
assertThat(context).hasSingleBean(ResourceConfig.class);
assertThat(context).doesNotHaveBean(JerseyApplicationPath.class);
assertThat(context).doesNotHaveBean(ServletRegistrationBean.class);
assertThat(context).hasBean("customResourceConfig");
});
}

@Test
public void resourceConfigIsCustomizedWithResourceConfigCustomizerBean() {
this.runner.withUserConfiguration(CustomizerConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(ResourceConfig.class);
ResourceConfig config = context.getBean(ResourceConfig.class);
ResourceConfigCustomizer customizer = (ResourceConfigCustomizer) context
.getBean("testResourceConfigCustomizer");
verify(customizer).customize(config);
});
}

@Test
public void resourceConfigCustomizerBeanIsNotRequired() {
this.runner.run(
(context) -> assertThat(context).hasSingleBean(ResourceConfig.class));
}

@Configuration
static class CustomizerConfiguration {

@Bean
ResourceConfigCustomizer testResourceConfigCustomizer() {
return mock(ResourceConfigCustomizer.class);
}

}

@Configuration
static class WebEndpointsSupplierConfig {

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

}

@Configuration
static class ConfigWithJerseyApplicationPath {

@Bean
public JerseyApplicationPath testJerseyApplicationPath() {
return mock(JerseyApplicationPath.class);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,22 @@
* Integration tests for the Jersey actuator endpoints.
*
* @author Andy Wilkinson
* @author Madhura Bhave
*/
public class JerseyEndpointIntegrationTests {

@Test
public void linksAreProvidedToAllEndpointTypes() throws Exception {
public void linksAreProvidedToAllEndpointTypes() {
testJerseyEndpoints(new Class[] { EndpointsConfiguration.class,
ResourceConfigConfiguration.class });
}

@Test
public void actuatorEndpointsWhenUserProvidedResourceConfigBeanNotAvailable() {
testJerseyEndpoints(new Class[] { EndpointsConfiguration.class });
}

protected void testJerseyEndpoints(Class[] userConfigurations) {
FilteredClassLoader classLoader = new FilteredClassLoader(
DispatcherServlet.class);
new WebApplicationContextRunner(
Expand All @@ -59,7 +70,7 @@ public void linksAreProvidedToAllEndpointTypes() throws Exception {
WebEndpointAutoConfiguration.class,
ManagementContextAutoConfiguration.class,
BeansEndpointAutoConfiguration.class))
.withUserConfiguration(EndpointsConfiguration.class)
.withUserConfiguration(userConfigurations)
.withPropertyValues("management.endpoints.web.exposure.include:*",
"server.port:0")
.run((context) -> {
Expand Down Expand Up @@ -88,11 +99,6 @@ static class TestRestControllerEndpoint {
@Configuration
static class EndpointsConfiguration {

@Bean
ResourceConfig testResourceConfig() {
return new ResourceConfig();
}

@Bean
TestControllerEndpoint testControllerEndpoint() {
return new TestControllerEndpoint();
Expand All @@ -105,4 +111,14 @@ TestRestControllerEndpoint testRestControllerEndpoint() {

}

@Configuration
static class ResourceConfigConfiguration {

@Bean
ResourceConfig testResourceConfig() {
return new ResourceConfig();
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.ext.ContextResolver;

import com.fasterxml.jackson.databind.AnnotationIntrospector;
Expand All @@ -51,6 +50,7 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DefaultJerseyApplicationPath;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
Expand All @@ -60,10 +60,8 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ServletContextAware;

Expand Down Expand Up @@ -114,15 +112,8 @@ private void customize() {
@Bean
@ConditionalOnMissingBean
public JerseyApplicationPath jerseyApplicationPath() {
return this::resolveApplicationPath;
}

private String resolveApplicationPath() {
if (StringUtils.hasLength(this.jersey.getApplicationPath())) {
return this.jersey.getApplicationPath();
}
return findApplicationPath(AnnotationUtils.findAnnotation(
this.config.getApplication().getClass(), ApplicationPath.class));
return new DefaultJerseyApplicationPath(this.jersey.getApplicationPath(),
this.config);
}

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

private static String findApplicationPath(ApplicationPath annotation) {
// Jersey doesn't like to be the default servlet, so map to /* as a fallback
if (annotation == null) {
return "/*";
}
return annotation.value();
}

@Override
public void setServletContext(ServletContext servletContext) {
String servletRegistrationName = getServletRegistrationName();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2012-2018 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.autoconfigure.web.servlet;

import javax.ws.rs.ApplicationPath;

import org.glassfish.jersey.server.ResourceConfig;

import org.springframework.boot.autoconfigure.jersey.JerseyProperties;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;

/**
* Default implementation of {@link JerseyApplicationPath} that derives the path from
* {@link JerseyProperties} or the {@code @ApplicationPath} annotation.
*
* @author Madhura Bhave
*/
public class DefaultJerseyApplicationPath implements JerseyApplicationPath {

private final String applicationPath;

private final ResourceConfig config;

public DefaultJerseyApplicationPath(String applicationPath, ResourceConfig config) {
this.applicationPath = applicationPath;
this.config = config;
}

@Override
public String getPath() {
return resolveApplicationPath();
}

private String resolveApplicationPath() {
if (StringUtils.hasLength(this.applicationPath)) {
return this.applicationPath;
}
return findApplicationPath(AnnotationUtils.findAnnotation(
this.config.getApplication().getClass(), ApplicationPath.class));
}

private static String findApplicationPath(ApplicationPath annotation) {
// Jersey doesn't like to be the default servlet, so map to /* as a fallback
if (annotation == null) {
return "/*";
}
return annotation.value();
}

}

0 comments on commit 26da45a

Please sign in to comment.