From decaacddce9a0e46b9196af1693500112e5cc66e Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Mon, 29 Oct 2018 13:20:21 -0700 Subject: [PATCH] Account for application path for Jersey servlet endpoints Closes gh-14895 --- ...ndpointManagementContextConfiguration.java | 13 ++- ...stractEndpointRequestIntegrationTests.java | 2 +- ...JerseyEndpointRequestIntegrationTests.java | 40 ++++++++ .../jersey/JerseyAutoConfiguration.java | 40 ++++---- .../servlet/JerseyRequestMatcherProvider.java | 43 +++++++++ ...questMatcherProviderAutoConfiguration.java | 37 ++++++-- .../web/servlet/JerseyApplicationPath.java | 89 ++++++++++++++++++ ...MatcherProviderAutoConfigurationTests.java | 71 +++++++++++--- .../servlet/JerseyApplicationPathTests.java | 93 +++++++++++++++++++ 9 files changed, 386 insertions(+), 42 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/JerseyRequestMatcherProvider.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/JerseyApplicationPath.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/JerseyApplicationPathTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java index dec2e0840f8..b4ba124b143 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClas import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath; +import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -82,11 +83,21 @@ public class ServletEndpointManagementContextConfiguration { @ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet") public static class JerseyServletEndpointManagementContextConfiguration { + private final ApplicationContext context; + + public JerseyServletEndpointManagementContextConfiguration( + ApplicationContext context) { + this.context = context; + } + @Bean public ServletEndpointRegistrar servletEndpointRegistrar( WebEndpointProperties properties, ServletEndpointsSupplier servletEndpointsSupplier) { - return new ServletEndpointRegistrar(properties.getBasePath(), + JerseyApplicationPath jerseyApplicationPath = this.context + .getBean(JerseyApplicationPath.class); + return new ServletEndpointRegistrar( + jerseyApplicationPath.getRelativePath(properties.getBasePath()), servletEndpointsSupplier.getEndpoints()); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/AbstractEndpointRequestIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/AbstractEndpointRequestIntegrationTests.java index d468fe63a53..41683c2dd88 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/AbstractEndpointRequestIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/AbstractEndpointRequestIntegrationTests.java @@ -156,7 +156,7 @@ public abstract class AbstractEndpointRequestIntegrationTests { } - interface TestPathMappedEndpoint + public interface TestPathMappedEndpoint extends ExposableEndpoint, PathMappedEndpoint { } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/JerseyEndpointRequestIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/JerseyEndpointRequestIntegrationTests.java index 9ddd2330fe5..52f8038b97d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/JerseyEndpointRequestIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/JerseyEndpointRequestIntegrationTests.java @@ -23,6 +23,7 @@ import java.util.List; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.model.Resource; +import org.junit.Test; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; @@ -41,12 +42,14 @@ import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfi import org.springframework.boot.autoconfigure.security.servlet.SecurityRequestMatcherProviderAutoConfiguration; import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.test.web.reactive.server.WebTestClient; /** * Integration tests for {@link EndpointRequest} with Jersey. @@ -60,6 +63,8 @@ public class JerseyEndpointRequestIntegrationTests protected WebApplicationContextRunner getContextRunner() { return new WebApplicationContextRunner( AnnotationConfigServletWebServerApplicationContext::new) + .withClassLoader(new FilteredClassLoader( + "org.springframework.web.servlet.DispatcherServlet")) .withUserConfiguration(JerseyEndpointConfiguration.class, SecurityConfiguration.class, BaseConfiguration.class) .withConfiguration(AutoConfigurations.of( @@ -70,6 +75,41 @@ public class JerseyEndpointRequestIntegrationTests JerseyAutoConfiguration.class)); } + @Test + public void toLinksWhenApplicationPathSetShouldMatch() { + getContextRunner().withPropertyValues("spring.jersey.application-path=/admin") + .run((context) -> { + WebTestClient webTestClient = getWebTestClient(context); + webTestClient.get().uri("/admin/actuator/").exchange().expectStatus() + .isOk(); + webTestClient.get().uri("/admin/actuator").exchange().expectStatus() + .isOk(); + }); + } + + @Test + public void toEndpointWhenApplicationPathSetShouldMatch() { + getContextRunner().withPropertyValues("spring.jersey.application-path=/admin") + .run((context) -> { + WebTestClient webTestClient = getWebTestClient(context); + webTestClient.get().uri("/admin/actuator/e1").exchange() + .expectStatus().isOk(); + }); + } + + @Test + public void toAnyEndpointWhenApplicationPathSetShouldMatch() { + getContextRunner().withPropertyValues("spring.jersey.application-path=/admin", + "spring.security.user.password=password").run((context) -> { + WebTestClient webTestClient = getWebTestClient(context); + webTestClient.get().uri("/admin/actuator/e2").exchange() + .expectStatus().isUnauthorized(); + webTestClient.get().uri("/admin/actuator/e2") + .header("Authorization", getBasicAuth()).exchange() + .expectStatus().isOk(); + }); + } + @Configuration @EnableConfigurationProperties(WebEndpointProperties.class) static class JerseyEndpointConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java index fd8a54779f5..f3cf0bab3b3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java @@ -16,7 +16,7 @@ package org.springframework.boot.autoconfigure.jersey; -import java.util.Arrays; +import java.util.Collections; import java.util.EnumSet; import java.util.List; @@ -53,6 +53,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.DynamicRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean; @@ -96,8 +97,6 @@ public class JerseyAutoConfiguration implements ServletContextAware { private final List customizers; - private String path; - public JerseyAutoConfiguration(JerseyProperties jersey, ResourceConfig config, ObjectProvider> customizers) { this.jersey = jersey; @@ -107,16 +106,15 @@ public class JerseyAutoConfiguration implements ServletContextAware { @PostConstruct public void path() { - resolveApplicationPath(); customize(); } - private void resolveApplicationPath() { + private String resolveApplicationPath() { if (StringUtils.hasLength(this.jersey.getApplicationPath())) { - this.path = parseApplicationPath(this.jersey.getApplicationPath()); + return this.jersey.getApplicationPath(); } else { - this.path = findApplicationPath(AnnotationUtils.findAnnotation( + return findApplicationPath(AnnotationUtils.findAnnotation( this.config.getApplication().getClass(), ApplicationPath.class)); } } @@ -130,6 +128,12 @@ public class JerseyAutoConfiguration implements ServletContextAware { } } + @Bean + @ConditionalOnMissingBean + public JerseyApplicationPath jerseyApplicationPath() { + return this::resolveApplicationPath; + } + @Bean @ConditionalOnMissingBean public FilterRegistrationBean requestContextFilter() { @@ -143,13 +147,15 @@ public class JerseyAutoConfiguration implements ServletContextAware { @Bean @ConditionalOnMissingBean(name = "jerseyFilterRegistration") @ConditionalOnProperty(prefix = "spring.jersey", name = "type", havingValue = "filter") - public FilterRegistrationBean jerseyFilterRegistration() { + public FilterRegistrationBean jerseyFilterRegistration( + JerseyApplicationPath applicationPath) { FilterRegistrationBean registration = new FilterRegistrationBean<>(); registration.setFilter(new ServletContainer(this.config)); - registration.setUrlPatterns(Arrays.asList(this.path)); + registration.setUrlPatterns( + Collections.singletonList(applicationPath.getUrlMapping())); registration.setOrder(this.jersey.getFilter().getOrder()); registration.addInitParameter(ServletProperties.FILTER_CONTEXT_PATH, - stripPattern(this.path)); + stripPattern(applicationPath.getPath())); addInitParameters(registration); registration.setName("jerseyFilter"); registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class)); @@ -166,9 +172,10 @@ public class JerseyAutoConfiguration implements ServletContextAware { @Bean @ConditionalOnMissingBean(name = "jerseyServletRegistration") @ConditionalOnProperty(prefix = "spring.jersey", name = "type", havingValue = "servlet", matchIfMissing = true) - public ServletRegistrationBean jerseyServletRegistration() { + public ServletRegistrationBean jerseyServletRegistration( + JerseyApplicationPath applicationPath) { ServletRegistrationBean registration = new ServletRegistrationBean<>( - new ServletContainer(this.config), this.path); + new ServletContainer(this.config), applicationPath.getUrlMapping()); addInitParameters(registration); registration.setName(getServletRegistrationName()); registration.setLoadOnStartup(this.jersey.getServlet().getLoadOnStartup()); @@ -188,14 +195,7 @@ public class JerseyAutoConfiguration implements ServletContextAware { if (annotation == null) { return "/*"; } - return parseApplicationPath(annotation.value()); - } - - private static String parseApplicationPath(String applicationPath) { - if (!applicationPath.startsWith("/")) { - applicationPath = "/" + applicationPath; - } - return applicationPath.equals("/") ? "/*" : applicationPath + "/*"; + return annotation.value(); } @Override diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/JerseyRequestMatcherProvider.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/JerseyRequestMatcherProvider.java new file mode 100644 index 00000000000..41324062c68 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/JerseyRequestMatcherProvider.java @@ -0,0 +1,43 @@ +/* + * 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.security.servlet; + +import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; + +/** + * {@link RequestMatcherProvider} that provides an {@link AntPathRequestMatcher} that can + * be used for Jersey applications. + * + * @author Madhura Bhave + * @since 2.0.7 + */ +public class JerseyRequestMatcherProvider implements RequestMatcherProvider { + + private final JerseyApplicationPath jerseyApplicationPath; + + public JerseyRequestMatcherProvider(JerseyApplicationPath jerseyApplicationPath) { + this.jerseyApplicationPath = jerseyApplicationPath; + } + + @Override + public RequestMatcher getRequestMatcher(String pattern) { + return new AntPathRequestMatcher( + this.jerseyApplicationPath.getRelativePath(pattern)); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/SecurityRequestMatcherProviderAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/SecurityRequestMatcherProviderAutoConfiguration.java index f0a01b53e30..5f786aae2ef 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/SecurityRequestMatcherProviderAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/SecurityRequestMatcherProviderAutoConfiguration.java @@ -15,9 +15,13 @@ */ package org.springframework.boot.autoconfigure.security.servlet; +import org.glassfish.jersey.server.ResourceConfig; + import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.web.util.matcher.RequestMatcher; @@ -31,15 +35,36 @@ import org.springframework.web.servlet.handler.HandlerMappingIntrospector; * @since 2.0.5 */ @Configuration -@ConditionalOnClass({ RequestMatcher.class, DispatcherServlet.class }) +@ConditionalOnClass({ RequestMatcher.class }) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) -@ConditionalOnBean(HandlerMappingIntrospector.class) public class SecurityRequestMatcherProviderAutoConfiguration { - @Bean - public RequestMatcherProvider requestMatcherProvider( - HandlerMappingIntrospector introspector) { - return new MvcRequestMatcherProvider(introspector); + @Configuration + @ConditionalOnClass(DispatcherServlet.class) + @ConditionalOnBean(HandlerMappingIntrospector.class) + public static class MvcRequestMatcherConfiguration { + + @Bean + @ConditionalOnClass(DispatcherServlet.class) + public RequestMatcherProvider requestMatcherProvider( + HandlerMappingIntrospector introspector) { + return new MvcRequestMatcherProvider(introspector); + } + + } + + @Configuration + @ConditionalOnClass(ResourceConfig.class) + @ConditionalOnMissingClass("org.springframework.web.servlet.DispatcherServlet") + @ConditionalOnBean(JerseyApplicationPath.class) + public static class JerseyRequestMatcherConfiguration { + + @Bean + public RequestMatcherProvider requestMatcherProvider( + JerseyApplicationPath applicationPath) { + return new JerseyRequestMatcherProvider(applicationPath); + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/JerseyApplicationPath.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/JerseyApplicationPath.java new file mode 100644 index 00000000000..67795323415 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/JerseyApplicationPath.java @@ -0,0 +1,89 @@ +/* + * 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 org.springframework.boot.web.servlet.ServletRegistrationBean; + +/** + * Interface that can be used by auto-configurations that need path details Jersey's + * application path that serves as the base URI for the application. + * + * @author Madhura Bhave + * @since 2.0.7 + */ +@FunctionalInterface +public interface JerseyApplicationPath { + + /** + * Returns the configured path of the application. + * @return the configured path + */ + String getPath(); + + /** + * Return a form of the given path that's relative to the Jersey application path. + * @param path the path to make relative + * @return the relative path + */ + default String getRelativePath(String path) { + String prefix = getPrefix(); + if (!path.startsWith("/")) { + path = "/" + path; + } + return prefix + path; + } + + /** + * Return a cleaned up version of the path that can be used as a prefix for URLs. The + * resulting path will have path will not have a trailing slash. + * @return the prefix + * @see #getRelativePath(String) + */ + default String getPrefix() { + String result = getPath(); + int index = result.indexOf('*'); + if (index != -1) { + result = result.substring(0, index); + } + if (result.endsWith("/")) { + result = result.substring(0, result.length() - 1); + } + return result; + } + + /** + * Return a URL mapping pattern that can be used with a + * {@link ServletRegistrationBean} to map Jersey's servlet. + * @return the path as a servlet URL mapping + */ + default String getUrlMapping() { + String path = getPath(); + if (!path.startsWith("/")) { + path = "/" + path; + } + if (path.equals("/")) { + return "/*"; + } + if (path.contains("*")) { + return path; + } + if (path.endsWith("/")) { + return path + "*"; + } + return path + "/*"; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/SecurityRequestMatcherProviderAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/SecurityRequestMatcherProviderAutoConfigurationTests.java index 74bafb9ad70..deaf49c3b52 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/SecurityRequestMatcherProviderAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/SecurityRequestMatcherProviderAutoConfigurationTests.java @@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.security.servlet; import org.junit.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; @@ -36,23 +37,44 @@ public class SecurityRequestMatcherProviderAutoConfigurationTests { private WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() .withConfiguration(AutoConfigurations - .of(SecurityRequestMatcherProviderAutoConfiguration.class)) - .withUserConfiguration(TestConfiguration.class); + .of(SecurityRequestMatcherProviderAutoConfiguration.class)); + + @Test + public void configurationConditionalOnWebApplication() { + new ApplicationContextRunner() + .withConfiguration(AutoConfigurations + .of(SecurityRequestMatcherProviderAutoConfiguration.class)) + .withUserConfiguration(TestMvcConfiguration.class) + .run((context) -> assertThat(context) + .doesNotHaveBean(RequestMatcherProvider.class)); + } + + @Test + public void configurationConditionalOnRequestMatcherClass() { + this.contextRunner + .withClassLoader(new FilteredClassLoader( + "org.springframework.security.web.util.matcher.RequestMatcher")) + .run((context) -> assertThat(context) + .doesNotHaveBean(RequestMatcherProvider.class)); + } @Test public void registersMvcRequestMatcherProviderIfMvcPresent() { - this.contextRunner.run((context) -> assertThat(context) - .hasSingleBean(MvcRequestMatcherProvider.class)); + this.contextRunner.withUserConfiguration(TestMvcConfiguration.class) + .run((context) -> assertThat(context) + .getBean(RequestMatcherProvider.class) + .isInstanceOf(MvcRequestMatcherProvider.class)); } @Test - public void mvcRequestMatcherProviderConditionalOnWebApplication() { - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations - .of(SecurityRequestMatcherProviderAutoConfiguration.class)) - .withUserConfiguration(TestConfiguration.class) + public void registersRequestMatcherForJerseyProviderIfJerseyPresentAndMvcAbsent() { + this.contextRunner + .withClassLoader(new FilteredClassLoader( + "org.springframework.web.servlet.DispatcherServlet")) + .withUserConfiguration(TestJerseyConfiguration.class) .run((context) -> assertThat(context) - .doesNotHaveBean(MvcRequestMatcherProvider.class)); + .getBean(RequestMatcherProvider.class) + .isInstanceOf(JerseyRequestMatcherProvider.class)); } @Test @@ -65,12 +87,12 @@ public class SecurityRequestMatcherProviderAutoConfigurationTests { } @Test - public void mvcRequestMatcherProviderConditionalOnRequestMatcherClass() { + public void jerseyRequestMatcherProviderConditionalOnResourceConfigClass() { this.contextRunner .withClassLoader(new FilteredClassLoader( - "org.springframework.security.web.util.matcher.RequestMatcher")) + "org.glassfish.jersey.server.ResourceConfig")) .run((context) -> assertThat(context) - .doesNotHaveBean(MvcRequestMatcherProvider.class)); + .doesNotHaveBean(JerseyRequestMatcherProvider.class)); } @Test @@ -82,8 +104,19 @@ public class SecurityRequestMatcherProviderAutoConfigurationTests { .doesNotHaveBean(MvcRequestMatcherProvider.class)); } + @Test + public void jerseyRequestMatcherProviderConditionalOnJerseyApplicationPathBean() { + new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations + .of(SecurityRequestMatcherProviderAutoConfiguration.class)) + .withClassLoader(new FilteredClassLoader( + "org.springframework.web.servlet.DispatcherServlet")) + .run((context) -> assertThat(context) + .doesNotHaveBean(JerseyRequestMatcherProvider.class)); + } + @Configuration - static class TestConfiguration { + static class TestMvcConfiguration { @Bean public HandlerMappingIntrospector introspector() { @@ -92,4 +125,14 @@ public class SecurityRequestMatcherProviderAutoConfigurationTests { } + @Configuration + static class TestJerseyConfiguration { + + @Bean + public JerseyApplicationPath jerseyApplicationPath() { + return () -> "/admin"; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/JerseyApplicationPathTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/JerseyApplicationPathTests.java new file mode 100644 index 00000000000..dfc8d339396 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/JerseyApplicationPathTests.java @@ -0,0 +1,93 @@ +/* + * 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 org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link JerseyApplicationPath}. + * + * @author Madhura Bhave + */ +public class JerseyApplicationPathTests { + + @Test + public void getRelativePathReturnsRelativePath() { + assertThat(((JerseyApplicationPath) () -> "spring").getRelativePath("boot")) + .isEqualTo("spring/boot"); + assertThat(((JerseyApplicationPath) () -> "spring/").getRelativePath("boot")) + .isEqualTo("spring/boot"); + assertThat(((JerseyApplicationPath) () -> "spring").getRelativePath("/boot")) + .isEqualTo("spring/boot"); + assertThat(((JerseyApplicationPath) () -> "spring/*").getRelativePath("/boot")) + .isEqualTo("spring/boot"); + } + + @Test + public void getPrefixWhenHasSimplePathReturnPath() { + assertThat(((JerseyApplicationPath) () -> "spring").getPrefix()) + .isEqualTo("spring"); + } + + @Test + public void getPrefixWhenHasPatternRemovesPattern() { + assertThat(((JerseyApplicationPath) () -> "spring/*.do").getPrefix()) + .isEqualTo("spring"); + } + + @Test + public void getPrefixWhenPathEndsWithSlashRemovesSlash() { + assertThat(((JerseyApplicationPath) () -> "spring/").getPrefix()) + .isEqualTo("spring"); + } + + @Test + public void getUrlMappingWhenPathIsEmptyReturnsSlash() { + assertThat(((JerseyApplicationPath) () -> "").getUrlMapping()).isEqualTo("/*"); + } + + @Test + public void getUrlMappingWhenPathIsSlashReturnsSlash() { + assertThat(((JerseyApplicationPath) () -> "/").getUrlMapping()).isEqualTo("/*"); + } + + @Test + public void getUrlMappingWhenPathContainsStarReturnsPath() { + assertThat(((JerseyApplicationPath) () -> "/spring/*.do").getUrlMapping()) + .isEqualTo("/spring/*.do"); + } + + @Test + public void getUrlMappingWhenHasPathNotEndingSlashReturnsSlashStarPattern() { + assertThat(((JerseyApplicationPath) () -> "/spring/boot").getUrlMapping()) + .isEqualTo("/spring/boot/*"); + } + + @Test + public void getUrlMappingWhenHasPathDoesNotStartWithSlashPrependsSlash() { + assertThat(((JerseyApplicationPath) () -> "spring/boot").getUrlMapping()) + .isEqualTo("/spring/boot/*"); + } + + @Test + public void getUrlMappingWhenHasPathEndingWithSlashReturnsSlashStarPattern() { + assertThat(((JerseyApplicationPath) () -> "/spring/boot/").getUrlMapping()) + .isEqualTo("/spring/boot/*"); + } + +}