From de95012635460ec40b9783ea9c501aa0b7157564 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 14 Jul 2015 17:12:52 +0100 Subject: [PATCH] Workaround problems with order of endpoint handler mapping When Spring Data REST is owning the home page it has its own HandlerMapping with a fix (relatively) low priority. The /links endpoint wants to own the home page as well, and our handler mapping has a high priority for good reasons. This change addresses the issue by checking if Spring Data REST is configured and if the management context path (or more specifically, the links endpoint) is the same as the home page. Fixes gh-3486 --- spring-boot-actuator/pom.xml | 15 +- .../EndpointWebMvcAutoConfiguration.java | 21 +-- ...ermediaManagementContextConfiguration.java | 129 ++++++++++++++++-- .../endpoint/mvc/EndpointHandlerMapping.java | 2 +- .../EndpointMvcIntegrationTests.java | 43 ++++-- ...cationCustomLinksPathIntegrationTests.java | 82 +++++++++++ ...ApplicationSharedRootIntegrationTests.java | 91 ++++++++++++ 7 files changed, 344 insertions(+), 39 deletions(-) create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/sample/hypermedia/jpa/SampleHypermediaJpaApplicationCustomLinksPathIntegrationTests.java create mode 100644 spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/sample/hypermedia/jpa/SampleHypermediaJpaApplicationSharedRootIntegrationTests.java diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index d7441c9489..fffaee3a60 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -45,22 +45,22 @@ com.fasterxml.jackson.dataformat jackson-dataformat-xml - true + true org.springframework.hateoas spring-hateoas - true + true org.springframework.plugin spring-plugin-core - true + true org.webjars hal-browser - true + true com.google.guava @@ -161,6 +161,13 @@ org.springframework.data spring-data-solr true + + + + wstx-asl + org.codehaus.woodstox + + org.springframework.integration diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java index 41555c55f6..40f4f46208 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java @@ -42,6 +42,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration; import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration; import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; @@ -90,11 +91,11 @@ import org.springframework.web.servlet.DispatcherServlet; @ConditionalOnClass({ Servlet.class, DispatcherServlet.class }) @ConditionalOnWebApplication @AutoConfigureAfter({ PropertyPlaceholderAutoConfiguration.class, - EmbeddedServletContainerAutoConfiguration.class, WebMvcAutoConfiguration.class, - ManagementServerPropertiesAutoConfiguration.class, - HypermediaAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class }) + EmbeddedServletContainerAutoConfiguration.class, WebMvcAutoConfiguration.class, + ManagementServerPropertiesAutoConfiguration.class, RepositoryRestMvcAutoConfiguration.class, + HypermediaAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class }) public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, - BeanFactoryAware, SmartInitializingSingleton { +BeanFactoryAware, SmartInitializingSingleton { private static final Log logger = LogFactory .getLog(EndpointWebMvcAutoConfiguration.class); @@ -129,7 +130,7 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, if (managementPort == ManagementServerPort.DIFFERENT && this.applicationContext instanceof EmbeddedWebApplicationContext && ((EmbeddedWebApplicationContext) this.applicationContext) - .getEmbeddedServletContainer() != null) { + .getEmbeddedServletContainer() != null) { createChildManagementContext(); } if (managementPort == ManagementServerPort.SAME @@ -149,7 +150,7 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, EmbeddedServletContainerAutoConfiguration.class, DispatcherServletAutoConfiguration.class); CloseEventPropagationListener - .addIfPossible(this.applicationContext, childContext); + .addIfPossible(this.applicationContext, childContext); try { childContext.refresh(); managementContextResolver().setApplicationContext(childContext); @@ -221,7 +222,7 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { + throws ServletException, IOException { if (this.properties == null) { this.properties = this.applicationContext .getBean(ManagementServerProperties.class); @@ -240,7 +241,7 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, * parent to a child. */ private static class CloseEventPropagationListener implements - ApplicationListener { + ApplicationListener { private final ApplicationContext parentContext; @@ -275,7 +276,7 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, } private static class OnManagementMvcCondition extends SpringBootCondition implements - ConfigurationCondition { + ConfigurationCondition { @Override public ConfigurationPhase getConfigurationPhase() { @@ -324,7 +325,7 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, return ((managementPort == null) || (serverPort == null && managementPort.equals(8080)) || (managementPort != 0 && managementPort.equals(serverPort)) ? SAME - : DIFFERENT); + : DIFFERENT); } private static Integer getPortProperty(Environment environment, String prefix) { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcHypermediaManagementContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcHypermediaManagementContextConfiguration.java index 243a3e03d4..317cc81c9a 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcHypermediaManagementContextConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcHypermediaManagementContextConfiguration.java @@ -16,6 +16,8 @@ package org.springframework.boot.actuate.autoconfigure; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; + import java.io.IOException; import java.lang.reflect.Type; import java.util.Map; @@ -23,7 +25,11 @@ import java.util.concurrent.ConcurrentHashMap; import javax.annotation.PostConstruct; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.actuate.endpoint.mvc.ActuatorDocsEndpoint; import org.springframework.boot.actuate.endpoint.mvc.HalBrowserEndpoint; import org.springframework.boot.actuate.endpoint.mvc.HypermediaDisabled; @@ -41,6 +47,7 @@ import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.autoconfigure.web.HttpMessageConverters; import org.springframework.boot.autoconfigure.web.ResourceProperties; import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ConditionContext; @@ -48,7 +55,9 @@ import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.core.MethodParameter; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.core.type.MethodMetadata; import org.springframework.hateoas.Link; import org.springframework.hateoas.Resource; import org.springframework.hateoas.ResourceSupport; @@ -61,6 +70,7 @@ import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import org.springframework.util.TypeUtils; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -74,8 +84,6 @@ import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; -import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; - /** * Configuration for hypermedia in HTTP endpoints. * @@ -128,7 +136,7 @@ public class EndpointWebMvcHypermediaManagementContextConfiguration { @Configuration("EndpointHypermediaAutoConfiguration.MissingResourceCondition") @ConditionalOnResource(resources = "classpath:/META-INF/spring-data-rest/hal-browser/index.html") protected static class MissingSpringDataRestResourceCondition extends - SpringBootCondition { + SpringBootCondition { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, @@ -149,12 +157,106 @@ public class EndpointWebMvcHypermediaManagementContextConfiguration { @ConditionalOnProperty(value = "endpoints.links.enabled", matchIfMissing = true) public static class LinksConfiguration { + @Autowired + private ListableBeanFactory beanFactory; + + @Autowired + private ManagementServerProperties management; + @Bean + @Conditional(NotSpringDataRestHomePageCondition.class) public LinksMvcEndpoint linksMvcEndpoint(ResourceProperties resources) { - String defaultPath = (resources.getWelcomePage() != null ? "/links" : ""); + String defaultPath = getDefaultPath(resources); return new LinksMvcEndpoint(defaultPath); } + private String getDefaultPath(ResourceProperties resources) { + return resources.getWelcomePage() != null ? "/links" : ""; + } + + private static class NotSpringDataRestHomePageCondition extends + SpringBootCondition { + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + if (!ClassUtils + .isPresent( + "org.springframework.data.rest.core.config.RepositoryRestConfiguration", + null)) { + return ConditionOutcome.match("Spring Data REST is not present"); + } + Class type = ClassUtils + .resolveClassName( + "org.springframework.data.rest.core.config.RepositoryRestConfiguration", + null); + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + if (beanFactory.getBeanNamesForType(type, true, false).length == 0) { + return ConditionOutcome.match("Spring Data REST is not configured"); + } + Environment environment = context.getEnvironment(); + String path = getProperty(environment, "management.", "contextPath"); + if (path == null + && hasCustomBeanDefinition(beanFactory, + ManagementServerProperties.class, + ManagementServerPropertiesAutoConfiguration.class)) { + ManagementServerProperties bean = beanFactory + .getBean(ManagementServerProperties.class); + path = bean.getContextPath(); + } + if (isHome(path)) { + path = getProperty(environment, "endpoints.links.", "path"); + if (isHome(path)) { + return ConditionOutcome + .noMatch("Management context path is home and so is links path"); + } + else { + return ConditionOutcome + .match("Management context path is home but links path is not: '" + + path + "'"); + } + } + else { + // N.B. we don't cover the case where the user has Spring Data REST + // but changes *its* home page - you'd have to instantiate the + // RepositoryRestConfiguration and look at it's basePath for that. + return ConditionOutcome + .match("Management context path is not home: '" + path + "'"); + } + } + + private static boolean isHome(String path) { + return path == null || "".equals(path) || "/".equals(path); + } + + private static String getProperty(Environment environment, String prefix, + String name) { + RelaxedPropertyResolver resolver = new RelaxedPropertyResolver( + environment, prefix); + return resolver.getProperty(name, String.class); + } + + private static boolean hasCustomBeanDefinition( + ConfigurableListableBeanFactory beanFactory, Class type, + Class configClass) { + String[] names = beanFactory.getBeanNamesForType(type, true, false); + if (names == null || names.length != 1) { + return false; + } + BeanDefinition definition = beanFactory.getBeanDefinition(names[0]); + if (definition instanceof AnnotatedBeanDefinition) { + MethodMetadata factoryMethodMetadata = ((AnnotatedBeanDefinition) definition) + .getFactoryMethodMetadata(); + if (factoryMethodMetadata != null) { + String className = factoryMethodMetadata.getDeclaringClassName(); + return !configClass.getName().equals(className); + } + } + return true; + } + + } + /** * Controller advice that adds links to the home page and/or the management * context path. The home page is enhanced if it is composed already of a @@ -166,7 +268,7 @@ public class EndpointWebMvcHypermediaManagementContextConfiguration { @Autowired private MvcEndpoints endpoints; - @Autowired + @Autowired(required = false) private LinksMvcEndpoint linksEndpoint; @Autowired @@ -200,7 +302,7 @@ public class EndpointWebMvcHypermediaManagementContextConfiguration { public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, - ServerHttpRequest request, ServerHttpResponse response) { + ServerHttpRequest request, ServerHttpResponse response) { if (request instanceof ServletServerHttpRequest) { beforeBodyWrite(body, (ServletServerHttpRequest) request); } @@ -217,8 +319,8 @@ public class EndpointWebMvcHypermediaManagementContextConfiguration { if (isHomePage(path) && hasManagementPath()) { String rel = this.management.getContextPath().substring(1); resource.add(linkTo( - EndpointWebMvcHypermediaManagementContextConfiguration.class).slash( - this.management.getContextPath()).withRel(rel)); + EndpointWebMvcHypermediaManagementContextConfiguration.class) + .slash(this.management.getContextPath()).withRel(rel)); } else { this.linksEnhancer.addEndpointLinks(resource, ""); @@ -236,8 +338,9 @@ public class EndpointWebMvcHypermediaManagementContextConfiguration { } private boolean isLinksPath(String path) { - return (this.management.getContextPath() + this.linksEndpoint.getPath()) - .equals(path); + return this.linksEndpoint != null + && (this.management.getContextPath() + this.linksEndpoint + .getPath()).equals(path); } private boolean isHomePage(String path) { @@ -280,7 +383,7 @@ public class EndpointWebMvcHypermediaManagementContextConfiguration { public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, - ServerHttpRequest request, ServerHttpResponse response) { + ServerHttpRequest request, ServerHttpResponse response) { if (request instanceof ServletServerHttpRequest) { return beforeBodyWrite(body, returnType, selectedContentType, selectedConverterType, (ServletServerHttpRequest) request, @@ -292,7 +395,7 @@ public class EndpointWebMvcHypermediaManagementContextConfiguration { private Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, - ServletServerHttpRequest request, ServerHttpResponse response) { + ServletServerHttpRequest request, ServerHttpResponse response) { if (body == null || body instanceof Resource) { // Assume it already was handled or it already has its links return body; @@ -317,7 +420,7 @@ public class EndpointWebMvcHypermediaManagementContextConfiguration { @SuppressWarnings("unchecked") private HttpMessageConverter findConverter( Class> selectedConverterType, - MediaType mediaType) { + MediaType mediaType) { if (this.converterCache.containsKey(mediaType)) { return (HttpMessageConverter) this.converterCache .get(mediaType); diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java index 1d1e84197b..9fc42fee97 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java @@ -50,7 +50,7 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl * @author Dave Syer */ public class EndpointHandlerMapping extends RequestMappingHandlerMapping implements - ApplicationContextAware { +ApplicationContextAware { private final Set endpoints; diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java index f10e9b2b9a..4863c923f0 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointMvcIntegrationTests.java @@ -16,12 +16,16 @@ package org.springframework.boot.actuate.autoconfigure; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -38,11 +42,12 @@ import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMappingCustomizer; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; import org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration; -import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.web.HttpMessageConverters; import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.test.IntegrationTest; @@ -51,6 +56,7 @@ import org.springframework.boot.test.TestRestTemplate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.http.converter.HttpMessageConverter; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; @@ -60,9 +66,6 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - /** * Integration tests for MVC {@link Endpoint}s. * @@ -82,7 +85,7 @@ public class EndpointMvcIntegrationTests { private TestInterceptor interceptor; @Test - public void envEndpointNotHidden() throws InterruptedException { + public void envEndpointHidden() throws InterruptedException { String body = new TestRestTemplate().getForObject("http://localhost:" + this.port + "/env/user.dir", String.class); assertNotNull(body); @@ -90,14 +93,23 @@ public class EndpointMvcIntegrationTests { assertTrue(this.interceptor.invoked()); } + @Test + public void healthEndpointNotHidden() throws InterruptedException { + String body = new TestRestTemplate().getForObject("http://localhost:" + this.port + + "/health", String.class); + assertNotNull(body); + assertTrue("Wrong body: \n" + body, body.contains("status")); + assertTrue(this.interceptor.invoked()); + } + @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({ EmbeddedServletContainerAutoConfiguration.class, - ServerPropertiesAutoConfiguration.class, - DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, - JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) + ServerPropertiesAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, + JacksonAutoConfiguration.class, ErrorMvcAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class }) protected static @interface MinimalWebConfiguration { } @@ -105,8 +117,8 @@ public class EndpointMvcIntegrationTests { @Configuration @MinimalWebConfiguration @Import({ ManagementServerPropertiesAutoConfiguration.class, - JacksonAutoConfiguration.class, EndpointAutoConfiguration.class, - EndpointWebMvcAutoConfiguration.class }) + JacksonAutoConfiguration.class, EndpointAutoConfiguration.class, + EndpointWebMvcAutoConfiguration.class }) @RestController protected static class Application { @@ -122,6 +134,15 @@ public class EndpointMvcIntegrationTests { return Collections.singletonMap("foo", (Object) "bar"); } + @Autowired(required = false) + private final List> converters = Collections.emptyList(); + + @Bean + @ConditionalOnMissingBean + public HttpMessageConverters messageConverters() { + return new HttpMessageConverters(this.converters); + } + @Bean public EndpointHandlerMappingCustomizer mappingCustomizer() { return new EndpointHandlerMappingCustomizer() { diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/sample/hypermedia/jpa/SampleHypermediaJpaApplicationCustomLinksPathIntegrationTests.java b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/sample/hypermedia/jpa/SampleHypermediaJpaApplicationCustomLinksPathIntegrationTests.java new file mode 100644 index 0000000000..a2383b44fb --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/sample/hypermedia/jpa/SampleHypermediaJpaApplicationCustomLinksPathIntegrationTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2015 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 sample.hypermedia.jpa; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +@SpringApplicationConfiguration(classes = SampleHypermediaJpaApplication.class) +@DirtiesContext +@TestPropertySource(properties = { "debug=true", "management.contextPath=", +"endpoints.docs.curies.enabled=false" }) +public class SampleHypermediaJpaApplicationCustomLinksPathIntegrationTests { + + @Autowired + private WebApplicationContext context; + + @Autowired + private MvcEndpoints mvcEndpoints; + + private MockMvc mockMvc; + + @Before + public void setUp() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + } + + @Test + public void links() throws Exception { + this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists()) + .andExpect(jsonPath("$._links.health").exists()) + .andExpect(jsonPath("$._links.books").exists()); + } + + @Test + public void health() throws Exception { + this.mockMvc.perform(get("/health").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists()); + } + + @Test + public void docs() throws Exception { + MvcResult response = this.mockMvc + .perform(get("/docs/").accept(MediaType.TEXT_HTML)) + .andExpect(status().isOk()).andReturn(); + System.err.println(response.getResponse().getContentAsString()); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/sample/hypermedia/jpa/SampleHypermediaJpaApplicationSharedRootIntegrationTests.java b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/sample/hypermedia/jpa/SampleHypermediaJpaApplicationSharedRootIntegrationTests.java new file mode 100644 index 0000000000..21184f4ef4 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/sample/hypermedia/jpa/SampleHypermediaJpaApplicationSharedRootIntegrationTests.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012-2015 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 sample.hypermedia.jpa; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +@SpringApplicationConfiguration(classes = SampleHypermediaJpaApplication.class) +@DirtiesContext +@TestPropertySource(properties = { "endpoints.links.path=/admin", "management.contextPath=", +"endpoints.docs.curies.enabled=false" }) +public class SampleHypermediaJpaApplicationSharedRootIntegrationTests { + + @Autowired + private WebApplicationContext context; + + @Autowired + private MvcEndpoints mvcEndpoints; + + private MockMvc mockMvc; + + @Before + public void setUp() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + } + + @Test + public void home() throws Exception { + this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists()) + .andExpect(jsonPath("$._links.health").exists()) + .andExpect(jsonPath("$._links.admin").exists()) + .andExpect(jsonPath("$._links.books").exists()); + } + + @Test + public void health() throws Exception { + this.mockMvc.perform(get("/health").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists()); + } + + @Test + public void links() throws Exception { + this.mockMvc.perform(get("/admin").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$._links.health").exists()) + .andExpect(jsonPath("$._links").exists()); + } + + @Test + public void docs() throws Exception { + MvcResult response = this.mockMvc + .perform(get("/docs/").accept(MediaType.TEXT_HTML)) + .andExpect(status().isOk()).andReturn(); + System.err.println(response.getResponse().getContentAsString()); + } + +}