diff --git a/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/HypermediaEndpointDocumentation.java b/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/HypermediaEndpointDocumentation.java index 0b113d4cf6..6188868134 100644 --- a/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/HypermediaEndpointDocumentation.java +++ b/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/HypermediaEndpointDocumentation.java @@ -82,7 +82,7 @@ public class HypermediaEndpointDocumentation { @Test public void home() throws Exception { - this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) + this.mockMvc.perform(get("/links").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()).andDo(document("admin")); } 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 2195ff2627..c95db6d57b 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 @@ -23,12 +23,10 @@ import java.util.concurrent.ConcurrentHashMap; import javax.annotation.PostConstruct; -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.HalBrowserMvcEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.HalBrowserMvcEndpoint.HalBrowserLocation; import org.springframework.boot.actuate.endpoint.mvc.HypermediaDisabled; import org.springframework.boot.actuate.endpoint.mvc.LinksMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; @@ -44,17 +42,15 @@ 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; 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.io.DefaultResourceLoader; +import org.springframework.core.io.ResourceLoader; 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; @@ -67,8 +63,6 @@ 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; import org.springframework.web.servlet.HandlerMapping; @@ -81,12 +75,14 @@ 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.BasicLinkBuilder.linkToCurrentMapping; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; /** * Configuration for hypermedia in HTTP endpoints. * * @author Dave Syer + * @author Phillip Webb * @since 1.3.0 */ @ManagementContextConfiguration @@ -97,14 +93,12 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; @EnableConfigurationProperties(ResourceProperties.class) public class EndpointWebMvcHypermediaManagementContextConfiguration { - @Bean @ConditionalOnProperty(value = "endpoints.hal.enabled", matchIfMissing = true) - @ConditionalOnResource(resources = "classpath:/META-INF/resources/webjars/hal-browser/b7669f1-1") - @Conditional(MissingSpringDataRestResourceCondition.class) - public HalBrowserEndpoint halBrowserMvcEndpoint( + @Conditional(HalBrowserCondition.class) + @Bean + public HalBrowserMvcEndpoint halBrowserMvcEndpoint( ManagementServerProperties management, ResourceProperties resources) { - return new HalBrowserEndpoint(management, - resources.getWelcomePage() != null ? "/hal" : ""); + return new HalBrowserMvcEndpoint(management); } @Bean @@ -130,22 +124,19 @@ public class EndpointWebMvcHypermediaManagementContextConfiguration { } /** - * {@link SpringBootCondition} to detect the Spring Data REST HAL browser. + * {@link SpringBootCondition} to detect the HAL browser. */ - @Configuration("EndpointHypermediaAutoConfiguration.MissingResourceCondition") - @ConditionalOnResource(resources = "classpath:/META-INF/spring-data-rest/hal-browser/index.html") - protected static class MissingSpringDataRestResourceCondition extends - SpringBootCondition { + protected static class HalBrowserCondition extends SpringBootCondition { @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { - if (context.getRegistry().containsBeanDefinition( - "EndpointHypermediaAutoConfiguration.MissingResourceCondition")) { - return ConditionOutcome.noMatch("Spring Data REST HAL browser found"); - } - return ConditionOutcome.match("Spring Data REST HAL browser not found"); - + ResourceLoader loader = context.getResourceLoader(); + loader = (loader == null ? new DefaultResourceLoader() : loader); + HalBrowserLocation found = HalBrowserMvcEndpoint + .getHalBrowserLocation(loader); + return new ConditionOutcome(found != null, "HAL Browser " + + (found == null ? "not found" : "at " + found)); } } @@ -157,98 +148,8 @@ public class EndpointWebMvcHypermediaManagementContextConfiguration { public static class LinksConfiguration { @Bean - @Conditional(NotSpringDataRestHomePageCondition.class) public LinksMvcEndpoint linksMvcEndpoint(ResourceProperties resources) { - String defaultPath = getDefaultPath(resources); - return new LinksMvcEndpoint(defaultPath); - } - - private String getDefaultPath(ResourceProperties resources) { - return resources.getWelcomePage() != null ? "/links" : ""; - } - - private static class NotSpringDataRestHomePageCondition extends - SpringBootCondition { - - private static final String REST_CONFIGURATION_CLASS = "org.springframework." - + "data.rest.core.config.RepositoryRestConfiguration"; - - @Override - public ConditionOutcome getMatchOutcome(ConditionContext context, - AnnotatedTypeMetadata metadata) { - if (!ClassUtils.isPresent(REST_CONFIGURATION_CLASS, null)) { - return ConditionOutcome.match("Spring Data REST is not present"); - } - return getMatchOutcome(context, - ClassUtils.resolveClassName(REST_CONFIGURATION_CLASS, null)); - } - - private ConditionOutcome getMatchOutcome(ConditionContext context, - Class configurationClass) { - ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); - if (beanFactory.getBeanNamesForType(configurationClass, true, false).length == 0) { - return ConditionOutcome.match("Spring Data REST is not configured"); - } - Environment environment = context.getEnvironment(); - String path = getManagementContextPath(beanFactory, environment); - 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"); - } - return ConditionOutcome.match("Management context path " - + "is home but links path is not: '" + path + "'"); - } - // 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 String getManagementContextPath( - ConfigurableListableBeanFactory beanFactory, Environment environment) { - String path = getProperty(environment, "management.", "contextPath"); - if (path == null - && hasCustomBeanDefinition(beanFactory, - ManagementServerProperties.class, - ManagementServerPropertiesAutoConfiguration.class)) { - path = beanFactory.getBean(ManagementServerProperties.class) - .getContextPath(); - } - return path; - } - - private boolean isHome(String path) { - return path == null || "".equals(path) || "/".equals(path); - } - - private String getProperty(Environment environment, String prefix, String name) { - RelaxedPropertyResolver resolver = new RelaxedPropertyResolver( - environment, prefix); - return resolver.getProperty(name, String.class); - } - - private 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; - } - + return new LinksMvcEndpoint(); } /** @@ -306,29 +207,21 @@ public class EndpointWebMvcHypermediaManagementContextConfiguration { private void beforeBodyWrite(Object body, ServletServerHttpRequest request) { Object pattern = request.getServletRequest().getAttribute( HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); - if (pattern != null) { - String path = pattern.toString(); - if (isHomePage(path) || isManagementPath(path) || isLinksPath(path)) { - ResourceSupport resource = (ResourceSupport) body; - if (isHomePage(path) && hasManagementPath()) { - String rel = this.management.getContextPath().substring(1); - resource.add(linkTo( - EndpointWebMvcHypermediaManagementContextConfiguration.class) - .slash(this.management.getContextPath()).withRel(rel)); - } - else { - this.linksEnhancer.addEndpointLinks(resource, ""); - } - } + if (pattern != null && body instanceof ResourceSupport) { + beforeBodyWrite(pattern.toString(), (ResourceSupport) body); } } - private boolean hasManagementPath() { - return StringUtils.hasText(this.management.getContextPath()); - } - - private boolean isManagementPath(String path) { - return this.management.getContextPath().equals(path); + private void beforeBodyWrite(String path, ResourceSupport body) { + if (isLinksPath(path)) { + this.linksEnhancer.addEndpointLinks(body, + this.linksEndpoint.getPath()); + } + else if (isHomePage(path)) { + body.add(linkToCurrentMapping() + .slash(this.management.getContextPath()) + .slash(this.linksEndpoint.getPath()).withRel("actuator")); + } } private boolean isLinksPath(String path) { @@ -370,7 +263,7 @@ public class EndpointWebMvcHypermediaManagementContextConfiguration { Class> converterType) { Class controllerType = returnType.getDeclaringClass(); return !LinksMvcEndpoint.class.isAssignableFrom(controllerType) - && !HalBrowserEndpoint.class.isAssignableFrom(controllerType); + && !HalBrowserMvcEndpoint.class.isAssignableFrom(controllerType); } @Override diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HalBrowserEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HalBrowserEndpoint.java deleted file mode 100644 index 4f28ff61e8..0000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HalBrowserEndpoint.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 org.springframework.boot.actuate.endpoint.mvc; - -import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties; -import org.springframework.boot.actuate.endpoint.Endpoint; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; - -/** - * {@link MvcEndpoint} to support the Spring Data HAL browser. - * - * @author Dave Syer - * @since 1.3.0 - */ -@ConfigurationProperties("endpoints.hal") -public class HalBrowserEndpoint extends WebMvcConfigurerAdapter implements MvcEndpoint { - - private static final String HAL_BROWSER_VERSION = "b7669f1-1"; - - private static final String HAL_BROWSER_LOCATION = "classpath:/META-INF/resources/webjars/hal-browser/" - + HAL_BROWSER_VERSION + "/"; - - private String path; - - private ManagementServerProperties management; - - private boolean sensitive = false; - - public HalBrowserEndpoint(ManagementServerProperties management, String defaultPath) { - this.management = management; - this.path = defaultPath; - } - - @RequestMapping(value = "/", produces = MediaType.TEXT_HTML_VALUE) - public String browse() { - return "forward:" + this.management.getContextPath() + this.path - + "/browser.html"; - } - - @RequestMapping(value = "", produces = MediaType.TEXT_HTML_VALUE) - public String redirect() { - return "redirect:" + this.management.getContextPath() + this.path + "/#" - + this.management.getContextPath(); - } - - @Override - public void addResourceHandlers(ResourceHandlerRegistry registry) { - // Make sure the root path is not cached so the browser comes back for the JSON - registry.addResourceHandler(this.management.getContextPath() + this.path + "/") - .addResourceLocations(HAL_BROWSER_LOCATION).setCachePeriod(0); - registry.addResourceHandler(this.management.getContextPath() + this.path + "/**") - .addResourceLocations(HAL_BROWSER_LOCATION); - } - - public void setPath(String path) { - this.path = path; - } - - @Override - public String getPath() { - return this.path; - } - - public void setSensitive(boolean sensitive) { - this.sensitive = sensitive; - } - - @Override - public boolean isSensitive() { - return this.sensitive; - } - - @Override - public Class> getEndpointType() { - return null; - } - -} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HalBrowserMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HalBrowserMvcEndpoint.java new file mode 100644 index 0000000000..97fd03ee8a --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/HalBrowserMvcEndpoint.java @@ -0,0 +1,218 @@ +/* + * 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 org.springframework.boot.actuate.endpoint.mvc; + +import java.io.IOException; +import java.nio.charset.Charset; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties; +import org.springframework.boot.actuate.endpoint.Endpoint; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.http.MediaType; +import org.springframework.util.FileCopyUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.resource.ResourceTransformer; +import org.springframework.web.servlet.resource.ResourceTransformerChain; +import org.springframework.web.servlet.resource.TransformedResource; + +/** + * {@link MvcEndpoint} to support the HAL browser. + * + * @author Dave Syer + * @author Phillip Webb + * @since 1.3.0 + */ +@ConfigurationProperties("endpoints.hal") +public class HalBrowserMvcEndpoint extends WebMvcConfigurerAdapter implements + MvcEndpoint, ResourceLoaderAware { + + private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + private static HalBrowserLocation[] HAL_BROWSER_RESOURCE_LOCATIONS = { + new HalBrowserLocation("classpath:/META-INF/spring-data-rest/hal-browser/", + "index.html"), + new HalBrowserLocation( + "classpath:/META-INF/resources/webjars/hal-browser/b7669f1-1/", + "browser.html") }; + + /** + * Endpoint URL path. + */ + @NotNull + @Pattern(regexp = "/[^/]*", message = "Path must start with /") + private String path = "/hal"; + + /** + * Enable security on the endpoint. + */ + private boolean sensitive = false; + + /** + * Enable the endpoint. + */ + private boolean enabled = true; + + private final ManagementServerProperties management; + + @Autowired(required = false) + private LinksMvcEndpoint linksMvcEndpoint; + + private HalBrowserLocation location; + + public HalBrowserMvcEndpoint(ManagementServerProperties management) { + this.management = management; + } + + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + this.location = getHalBrowserLocation(resourceLoader); + } + + @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) + public String browse(HttpServletRequest request) { + String contextPath = this.management.getContextPath() + this.path + "/"; + if (request.getRequestURI().endsWith("/")) { + return "forward:" + contextPath + this.location.getHtmlFile(); + } + return "redirect:" + contextPath; + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + // Make sure the root path is not cached so the browser comes back for the JSON + // and add a transformer to set the initial link + String start = this.management.getContextPath() + this.path; + registry.addResourceHandler(start + "/", start + "/**") + .addResourceLocations(this.location.getResourceLocation()) + .setCachePeriod(0).resourceChain(true) + .addTransformer(new InitialUrlTransformer()); + } + + public void setPath(String path) { + this.path = path; + } + + @Override + public String getPath() { + return this.path; + } + + public void setSensitive(boolean sensitive) { + this.sensitive = sensitive; + } + + @Override + public boolean isSensitive() { + return this.sensitive; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isEnabled() { + return this.enabled; + } + + @Override + public Class> getEndpointType() { + return null; + } + + public static HalBrowserLocation getHalBrowserLocation(ResourceLoader resourceLoader) { + for (HalBrowserLocation candidate : HAL_BROWSER_RESOURCE_LOCATIONS) { + try { + Resource resource = resourceLoader.getResource(candidate.toString()); + if (resource != null && resource.exists()) { + return candidate; + } + } + catch (Exception ex) { + } + } + return null; + } + + /** + * {@link ResourceTransformer} to change the initial link location. + */ + private class InitialUrlTransformer implements ResourceTransformer { + + @Override + public Resource transform(HttpServletRequest request, Resource resource, + ResourceTransformerChain transformerChain) throws IOException { + resource = transformerChain.transform(request, resource); + if (resource.getFilename().equalsIgnoreCase( + HalBrowserMvcEndpoint.this.location.getHtmlFile())) { + return replaceInitialLink(resource); + } + return resource; + } + + private Resource replaceInitialLink(Resource resource) throws IOException { + LinksMvcEndpoint linksEndpoint = HalBrowserMvcEndpoint.this.linksMvcEndpoint; + if (linksEndpoint == null) { + return resource; + } + byte[] bytes = FileCopyUtils.copyToByteArray(resource.getInputStream()); + String content = new String(bytes, DEFAULT_CHARSET); + String initialLink = HalBrowserMvcEndpoint.this.management.getContextPath() + + linksEndpoint.getPath(); + content = content.replace("entryPoint: '/'", "entryPoint: '" + initialLink + + "'"); + return new TransformedResource(resource, content.getBytes(DEFAULT_CHARSET)); + } + + } + + public static class HalBrowserLocation { + + private final String resourceLocation; + + private final String htmlFile; + + public HalBrowserLocation(String resourceLocation, String html) { + this.resourceLocation = resourceLocation; + this.htmlFile = html; + } + + public String getResourceLocation() { + return this.resourceLocation; + } + + public String getHtmlFile() { + return this.htmlFile; + } + + @Override + public String toString() { + return this.resourceLocation + this.htmlFile; + } + + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LinksMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LinksMvcEndpoint.java index cf20e85a4a..8e2f4aa20d 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LinksMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/LinksMvcEndpoint.java @@ -16,6 +16,9 @@ package org.springframework.boot.actuate.endpoint.mvc; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; + import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.hateoas.ResourceSupport; @@ -32,14 +35,27 @@ import org.springframework.web.bind.annotation.ResponseBody; @ConfigurationProperties("endpoints.links") public class LinksMvcEndpoint implements MvcEndpoint { - private String path; + /** + * Endpoint URL path. + */ + @NotNull + @Pattern(regexp = "/[^/]*", message = "Path must start with /") + private String path = "/links"; + + /** + * Enable security on the endpoint. + */ private boolean sensitive = false; - public LinksMvcEndpoint(String defaultPath) { - this.path = defaultPath; + /** + * Enable the endpoint. + */ + private boolean enabled = true; + + public LinksMvcEndpoint() { } - @RequestMapping(value = { "/", "" }, produces = MediaType.APPLICATION_JSON_VALUE) + @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public ResourceSupport links() { return new ResourceSupport(); @@ -63,6 +79,14 @@ public class LinksMvcEndpoint implements MvcEndpoint { this.sensitive = sensitive; } + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + @Override public Class> getEndpointType() { return null; diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/BrowserPathHypermediaIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/BrowserPathHypermediaIntegrationTests.java index af92911d15..818ab12071 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/BrowserPathHypermediaIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/BrowserPathHypermediaIntegrationTests.java @@ -72,7 +72,7 @@ public class BrowserPathHypermediaIntegrationTests { public void redirect() throws Exception { this.mockMvc.perform(get("/hal").accept(MediaType.TEXT_HTML)) .andExpect(status().isFound()) - .andExpect(header().string("location", "/hal/#")); + .andExpect(header().string("location", "/hal/")); } @MinimalActuatorHypermediaApplication diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ContextPathHypermediaIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ContextPathHypermediaIntegrationTests.java index 341e4d7990..e6cfaa0311 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ContextPathHypermediaIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ContextPathHypermediaIntegrationTests.java @@ -70,7 +70,7 @@ public class ContextPathHypermediaIntegrationTests { @Test public void links() throws Exception { - this.mockMvc.perform(get("/admin").accept(MediaType.APPLICATION_JSON)) + this.mockMvc.perform(get("/admin/links").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists()); } @@ -89,10 +89,13 @@ public class ContextPathHypermediaIntegrationTests { public void endpointsAllListed() throws Exception { for (MvcEndpoint endpoint : this.mvcEndpoints.getEndpoints()) { String path = endpoint.getPath(); + if ("/links".equals(path)) { + continue; + } path = path.startsWith("/") ? path.substring(1) : path; path = path.length() > 0 ? path : "self"; this.mockMvc - .perform(get("/admin").accept(MediaType.APPLICATION_JSON)) + .perform(get("/admin/links").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect( jsonPath("$._links.%s.href", path).value( diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CustomHomepageHypermediaIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CustomHomepageHypermediaIntegrationTests.java index a91b147816..fbe0da4615 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CustomHomepageHypermediaIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CustomHomepageHypermediaIntegrationTests.java @@ -70,8 +70,11 @@ public class CustomHomepageHypermediaIntegrationTests { public void endpointsAllListed() throws Exception { for (MvcEndpoint endpoint : this.mvcEndpoints.getEndpoints()) { String path = endpoint.getPath(); + if ("/links".equals(path)) { + continue; + } path = path.startsWith("/") ? path.substring(1) : path; - this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) + this.mockMvc.perform(get("/links").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$._links.%s.href", path).exists()); } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ServerContextPathHypermediaIntegrationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ServerContextPathHypermediaIntegrationTests.java index a52675aa7d..7a8383d6b7 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ServerContextPathHypermediaIntegrationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/ServerContextPathHypermediaIntegrationTests.java @@ -70,7 +70,7 @@ public class ServerContextPathHypermediaIntegrationTests { HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); ResponseEntity entity = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/spring/", HttpMethod.GET, + "http://localhost:" + this.port + "/spring/hal/", HttpMethod.GET, new HttpEntity(null, headers), String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertTrue("Wrong body: " + entity.getBody(), entity.getBody().contains(" entity = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/", HttpMethod.GET, + "http://localhost:" + this.port + "/links", HttpMethod.GET, new HttpEntity(null, headers), String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertTrue("Wrong body: " + entity.getBody(), @@ -70,7 +70,7 @@ public class ServerPortHypermediaIntegrationTests { HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); ResponseEntity entity = new TestRestTemplate().exchange( - "http://localhost:" + this.port + "/", HttpMethod.GET, + "http://localhost:" + this.port + "/hal/", HttpMethod.GET, new HttpEntity(null, headers), String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertTrue("Wrong body: " + entity.getBody(), entity.getBody().contains(" 0 ? path : "/"; 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 index eb0391a594..54898a8fb9 100644 --- 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 @@ -59,10 +59,10 @@ public class SampleHypermediaJpaApplicationCustomLinksPathIntegrationTests { @Test public void links() throws Exception { - this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) + this.mockMvc.perform(get("/links").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists()) .andExpect(jsonPath("$._links.health").exists()) - .andExpect(jsonPath("$._links.books").exists()); + .andExpect(jsonPath("$._links.books").doesNotExist()); } @Test 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 index 1f5d333a8a..929a69c264 100644 --- 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 @@ -61,8 +61,7 @@ public class SampleHypermediaJpaApplicationSharedRootIntegrationTests { 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.actuator").exists()) .andExpect(jsonPath("$._links.books").exists()); } diff --git a/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/sample/hypermedia/jpa/SampleHypermediaJpaApplicationVanillaIntegrationTests.java b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/sample/hypermedia/jpa/SampleHypermediaJpaApplicationVanillaIntegrationTests.java index cf55d78797..96914ea394 100644 --- a/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/sample/hypermedia/jpa/SampleHypermediaJpaApplicationVanillaIntegrationTests.java +++ b/spring-boot-samples/spring-boot-sample-hypermedia-jpa/src/test/java/sample/hypermedia/jpa/SampleHypermediaJpaApplicationVanillaIntegrationTests.java @@ -69,7 +69,7 @@ public class SampleHypermediaJpaApplicationVanillaIntegrationTests { @Test public void adminLinks() throws Exception { - this.mockMvc.perform(get("/admin").accept(MediaType.APPLICATION_JSON)) + this.mockMvc.perform(get("/admin/links").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()).andExpect(jsonPath("$._links").exists()); } diff --git a/spring-boot-samples/spring-boot-sample-hypermedia/src/test/java/sample/hypermedia/SampleHypermediaApplicationHomePageTests.java b/spring-boot-samples/spring-boot-sample-hypermedia/src/test/java/sample/hypermedia/SampleHypermediaApplicationHomePageTests.java index aa43e36498..34187f1db6 100644 --- a/spring-boot-samples/spring-boot-sample-hypermedia/src/test/java/sample/hypermedia/SampleHypermediaApplicationHomePageTests.java +++ b/spring-boot-samples/spring-boot-sample-hypermedia/src/test/java/sample/hypermedia/SampleHypermediaApplicationHomePageTests.java @@ -48,27 +48,26 @@ public class SampleHypermediaApplicationHomePageTests { public void home() { String response = new TestRestTemplate().getForObject("http://localhost:" + this.port, String.class); - assertTrue("Wrong body: " + response, response.contains("\"_links\":")); - assertTrue("Wrong body: " + response, response.contains("\"curies\":")); + assertTrue("Wrong body: " + response, response.contains("404")); } @Test - public void homeWithJson() throws Exception { + public void linksWithJson() throws Exception { HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); ResponseEntity response = new TestRestTemplate().exchange( new RequestEntity(headers, HttpMethod.GET, new URI( - "http://localhost:" + this.port + "/")), String.class); + "http://localhost:" + this.port + "/links")), String.class); assertTrue("Wrong body: " + response, response.getBody().contains("\"_links\":")); } @Test - public void homeWithHtml() throws Exception { + public void halWithHtml() throws Exception { HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.TEXT_HTML)); ResponseEntity response = new TestRestTemplate().exchange( new RequestEntity(headers, HttpMethod.GET, new URI( - "http://localhost:" + this.port)), String.class); + "http://localhost:" + this.port + "/hal/")), String.class); assertTrue("Wrong body: " + response, response.getBody().contains("HAL Browser")); }