Add support for Spring HATEOAS hypermedia in Actuator endpoints

If spring-hateoas is on the classpath and an MvcEndpoint returns a
@ResponseBody it will be extended and wrapped into a Resource with links.
All the existing endpoints that return sensible JSON data can be extended
this way (i.e. not /logfile). The HAL browser will also be added as an
endpoint if available on the classpath. Finally, asciidocs for the
Actuator endpoints are available as a separate jar file, which if
included in an app will also generate a new (HTTP) endpoint.

Fixes gh-1390
This commit is contained in:
Dave Syer
2015-07-01 18:36:21 +01:00
parent 82fdb87a8c
commit 74e9e0749b
74 changed files with 3106 additions and 154 deletions

View File

@@ -44,8 +44,10 @@ 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.hateoas.HypermediaAutoConfiguration;
import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.bind.RelaxedPropertyResolver;
@@ -86,8 +88,9 @@ import org.springframework.web.servlet.DispatcherServlet;
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@ConditionalOnWebApplication
@AutoConfigureAfter({ PropertyPlaceholderAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class, WebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class })
EmbeddedServletContainerAutoConfiguration.class, WebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
HypermediaAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class })
public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
SmartInitializingSingleton {

View File

@@ -0,0 +1,380 @@
/*
* Copyright 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.autoconfigure;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.mvc.ActuatorDocsEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.HalBrowserEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.HypermediaDisabled;
import org.springframework.boot.actuate.endpoint.mvc.LinksEnhancer;
import org.springframework.boot.actuate.endpoint.mvc.LinksMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
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.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.type.AnnotatedTypeMetadata;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.UriTemplate;
import org.springframework.hateoas.hal.CurieProvider;
import org.springframework.hateoas.hal.DefaultCurieProvider;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
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.StringUtils;
import org.springframework.util.TypeUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
/**
* Autoconfiguration for hypermedia in HTTP endpoints.
*
* @author Dave Syer
*
*/
@Configuration
@ConditionalOnClass(Link.class)
@ConditionalOnWebApplication
@ConditionalOnBean(HttpMessageConverters.class)
@ConditionalOnProperty(value = "endpoints.enabled", matchIfMissing = true)
@EnableConfigurationProperties(ResourceProperties.class)
public class EndpointWebMvcHypermediaConfiguration {
@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(
ManagementServerProperties management, ResourceProperties resources) {
return new HalBrowserEndpoint(management,
resources.getWelcomePage() != null ? "/hal" : "");
}
@Bean
@ConditionalOnProperty(value = "endpoints.docs.enabled", matchIfMissing = true)
@ConditionalOnResource(resources = "classpath:/META-INF/resources/spring-boot-actuator/docs/index.html")
public ActuatorDocsEndpoint actuatorDocsEndpoint(ManagementServerProperties management) {
return new ActuatorDocsEndpoint(management);
}
@Bean
@ConditionalOnBean(ActuatorDocsEndpoint.class)
@ConditionalOnMissingBean(CurieProvider.class)
@ConditionalOnProperty(value = "endpoints.docs.curies.enabled", matchIfMissing = false)
public DefaultCurieProvider curieProvider(ServerProperties server,
ManagementServerProperties management, ActuatorDocsEndpoint endpoint) {
String path = management.getContextPath() + endpoint.getPath()
+ "/#spring_boot_actuator__{rel}";
if (server.getPort() == management.getPort() && management.getPort() != null
&& management.getPort() != 0) {
path = server.getPath(path);
}
return new DefaultCurieProvider("boot", new UriTemplate(path));
}
@Configuration("EndpointHypermediaAutoConfiguration.MissingResourceCondition")
@ConditionalOnResource(resources = "classpath:/META-INF/spring-data-rest/hal-browser/index.html")
protected static class MissingSpringDataRestResourceCondition 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");
}
}
@ConditionalOnProperty(value = "endpoints.links.enabled", matchIfMissing = true)
public static class LinksConfiguration {
@Bean
public LinksMvcEndpoint linksMvcEndpoint(ResourceProperties resources) {
return new LinksMvcEndpoint(resources.getWelcomePage() != null ? "/links"
: "");
}
/**
* 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
* {@link ResourceSupport} (e.g. when using Spring Data REST).
*
* @author Dave Syer
*
*/
@ControllerAdvice
public static class HomePageLinksAdvice implements ResponseBodyAdvice<Object> {
@Autowired
MvcEndpoints endpoints;
@Autowired
LinksMvcEndpoint linksEndpoint;
@Autowired
ManagementServerProperties management;
private LinksEnhancer linksEnhancer;
@PostConstruct
public void init() {
this.linksEnhancer = new LinksEnhancer(this.endpoints,
this.management.getContextPath());
}
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
Class<?> controllerType = returnType.getDeclaringClass();
if (!LinksMvcEndpoint.class.isAssignableFrom(controllerType)
&& MvcEndpoint.class.isAssignableFrom(controllerType)) {
return false;
}
returnType.increaseNestingLevel();
Type nestedType = returnType.getNestedGenericParameterType();
returnType.decreaseNestingLevel();
return ResourceSupport.class.isAssignableFrom(returnType
.getParameterType())
|| TypeUtils.isAssignable(ResourceSupport.class, nestedType);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
HttpServletRequest servletRequest = null;
if (request instanceof ServletServerHttpRequest) {
servletRequest = ((ServletServerHttpRequest) request)
.getServletRequest();
Object pattern = servletRequest
.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(
EndpointWebMvcHypermediaConfiguration.class)
.slash(this.management.getContextPath()).withRel(
rel));
}
else {
this.linksEnhancer.addEndpointLinks(resource, "");
}
}
}
}
return body;
}
private boolean hasManagementPath() {
return StringUtils.hasText(this.management.getContextPath());
}
private boolean isManagementPath(String path) {
return this.management.getContextPath().equals(path);
}
private boolean isLinksPath(String path) {
return (this.management.getContextPath() + this.linksEndpoint.getPath())
.equals(path);
}
private boolean isHomePage(String path) {
return "".equals(path) || "/".equals(path);
}
}
/**
* Controller advice that adds links to the existing Actuator endpoints. By default
* all the top-level resources are enhanced with a "self" link. Those resources that
* could not be enhanced (e.g. "/env/{name}") because their values are "primitive" are
* ignored. Those that have values of type Collection (e.g. /trace) are transformed in
* to maps, and the original collection value is added with a key equal to the
* endpoint name.
*
* @author Dave Syer
*
*/
@ControllerAdvice(assignableTypes = MvcEndpoint.class)
public static class MvcEndpointAdvice implements ResponseBodyAdvice<Object> {
@Autowired
ManagementServerProperties management;
@Autowired
HttpMessageConverters converters;
private Map<MediaType, HttpMessageConverter<?>> converterCache = new ConcurrentHashMap<MediaType, HttpMessageConverter<?>>();
@Autowired
ObjectMapper mapper;
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
Class<?> controllerType = returnType.getDeclaringClass();
return !LinksMvcEndpoint.class.isAssignableFrom(controllerType)
&& !HalBrowserEndpoint.class.isAssignableFrom(controllerType);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (body == null) {
// Assume it already was handled
return body;
}
if (body instanceof Resource) {
// Assume it already has its links
return body;
}
@SuppressWarnings("unchecked")
HttpMessageConverter<Object> converter = (HttpMessageConverter<Object>) findConverter(
selectedConverterType, selectedContentType);
if (converter == null) {
// Not a resource that can be enhanced with a link
return body;
}
if (AnnotationUtils.findAnnotation(returnType.getMethod(),
HypermediaDisabled.class) != null
|| AnnotationUtils.findAnnotation(returnType.getMethod()
.getDeclaringClass(), HypermediaDisabled.class) != null) {
return body;
}
HttpServletRequest servletRequest = null;
if (request instanceof ServletServerHttpRequest) {
servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
String path = (String) servletRequest
.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
if (path == null) {
path = "";
}
try {
converter.write(new EndpointResource(body, path),
selectedContentType, response);
}
catch (IOException e) {
throw new HttpMessageNotWritableException("Cannot write response", e);
}
return null;
}
else {
return body;
}
}
private HttpMessageConverter<?> findConverter(
Class<? extends HttpMessageConverter<?>> selectedConverterType,
MediaType mediaType) {
if (this.converterCache.containsKey(mediaType)) {
return this.converterCache.get(mediaType);
}
for (HttpMessageConverter<?> converter : this.converters) {
if (selectedConverterType.isAssignableFrom(converter.getClass())
&& converter.canWrite(EndpointResource.class, mediaType)) {
this.converterCache.put(mediaType, converter);
return converter;
}
}
return null;
}
}
}
@JsonInclude(content = Include.NON_NULL)
@JacksonXmlRootElement(localName = "resource")
private static class EndpointResource extends ResourceSupport {
private Object content;
private Map<String, Object> embedded;
@SuppressWarnings("unchecked")
public EndpointResource(Object content, String path) {
this.content = content instanceof Map ? null : content;
this.embedded = (Map<String, Object>) (this.content == null ? content : null);
add(linkTo(Object.class).slash(path).withSelfRel());
}
@JsonUnwrapped
public Object getContent() {
return this.content;
}
@JsonAnyGetter
public Map<String, Object> getEmbedded() {
return this.embedded;
}
}
}

View File

@@ -19,6 +19,7 @@ package org.springframework.boot.actuate.autoconfigure;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -102,7 +103,7 @@ public class ManagementSecurityAutoConfiguration {
@Configuration
protected static class ManagementSecurityPropertiesConfiguration implements
SecurityPrerequisite {
SecurityPrerequisite {
@Autowired(required = false)
private SecurityProperties security;
@@ -114,7 +115,7 @@ public class ManagementSecurityAutoConfiguration {
public void init() {
if (this.management != null && this.security != null) {
this.security.getUser().getRole()
.add(this.management.getSecurity().getRole());
.add(this.management.getSecurity().getRole());
}
}
@@ -123,7 +124,7 @@ public class ManagementSecurityAutoConfiguration {
// Get the ignored paths in early
@Order(SecurityProperties.IGNORED_ORDER + 1)
private static class IgnoredPathsWebSecurityConfigurerAdapter implements
WebSecurityConfigurer<WebSecurity> {
WebSecurityConfigurer<WebSecurity> {
@Autowired(required = false)
private ErrorController errorController;
@@ -208,7 +209,7 @@ public class ManagementSecurityAutoConfiguration {
@ConditionalOnProperty(prefix = "management.security", name = "enabled", matchIfMissing = true)
@Order(ManagementServerProperties.BASIC_AUTH_ORDER)
protected static class ManagementWebSecurityConfigurerAdapter extends
WebSecurityConfigurerAdapter {
WebSecurityConfigurerAdapter {
@Autowired
private SecurityProperties security;
@@ -317,13 +318,13 @@ public class ManagementSecurityAutoConfiguration {
endpointMapping,
false)
: getEndpointPaths(endpointMapping);
for (String path : paths) {
pathMatchers.add(new AntPathRequestMatcher(
ManagementWebSecurityConfigurerAdapter.this.server
for (String path : paths) {
pathMatchers.add(new AntPathRequestMatcher(
ManagementWebSecurityConfigurerAdapter.this.server
.getPath(path)));
}
this.delegate = pathMatchers.isEmpty() ? AnyRequestMatcher.INSTANCE
: new OrRequestMatcher(pathMatchers);
}
this.delegate = pathMatchers.isEmpty() ? AnyRequestMatcher.INSTANCE
: new OrRequestMatcher(pathMatchers);
}
return this.delegate.matches(request);
}
@@ -343,15 +344,19 @@ public class ManagementSecurityAutoConfiguration {
return NO_PATHS;
}
Set<? extends MvcEndpoint> endpoints = endpointHandlerMapping.getEndpoints();
List<String> paths = new ArrayList<String>(endpoints.size());
Set<String> paths = new LinkedHashSet<String>(endpoints.size());
for (MvcEndpoint endpoint : endpoints) {
if (endpoint.isSensitive() == secure) {
String path = endpointHandlerMapping.getPath(endpoint.getPath());
paths.add(path);
// Ensure that nested paths are secured
paths.add(path + "/**");
// Add Spring MVC-generated additional paths
paths.add(path + ".*");
if (!path.equals("")) {
// Ensure that nested paths are secured
paths.add(path + "/**");
// Add Spring MVC-generated additional paths
paths.add(path + ".*");
} else {
paths.add("/");
}
}
}
return paths.toArray(new String[paths.size()]);

View File

@@ -0,0 +1,112 @@
/*
* Copyright 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.actuate.endpoint.mvc.MvcEndpoint;
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;
/**
* @author Dave Syer
*
*/
@ConfigurationProperties("endpoints.docs")
public class ActuatorDocsEndpoint extends WebMvcConfigurerAdapter implements MvcEndpoint {
private String path = "/docs";
private boolean sensitive;
private ManagementServerProperties management;
private Curies curies = new Curies();
public Curies getCuries() {
return this.curies;
}
/**
* Properties of the default CurieProvider (used for adding docs links). If enabled, all
* unqualified rels will pick up a prefix and a curie template pointing to the docs endpoint.
*
*/
public static class Curies {
/**
* Enable the curie generation (off by default).
*/
private boolean enabled = false;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
public ActuatorDocsEndpoint(ManagementServerProperties management) {
this.management = management;
}
@RequestMapping(value = "/", produces = MediaType.TEXT_HTML_VALUE)
public String browse() {
return "forward:" + this.management.getContextPath() + this.path + "/index.html";
}
@RequestMapping(value = "", produces = MediaType.TEXT_HTML_VALUE)
public String redirect() {
return "redirect:" + this.management.getContextPath() + this.path + "/";
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler(this.management.getContextPath() + this.path + "/**")
.addResourceLocations(
"classpath:/META-INF/resources/spring-boot-actuator/docs/");
}
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<? extends Endpoint<?>> getEndpointType() {
return null;
}
}

View File

@@ -38,7 +38,7 @@ import org.springframework.web.bind.annotation.ResponseStatus;
* @author Andy Wilkinson
*/
public class EnvironmentMvcEndpoint extends EndpointMvcAdapter implements
EnvironmentAware {
EnvironmentAware {
private Environment environment;
@@ -48,6 +48,7 @@ public class EnvironmentMvcEndpoint extends EndpointMvcAdapter implements
@RequestMapping(value = "/{name:.*}", method = RequestMethod.GET)
@ResponseBody
@HypermediaDisabled
public Object value(@PathVariable String name) {
if (!getDelegate().isEnabled()) {
// Shouldn't happen - MVC endpoint shouldn't be registered when delegate's

View File

@@ -0,0 +1,96 @@
/*
* Copyright 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;
/**
* @author Dave Syer
*
*/
@ConfigurationProperties("endpoints.hal")
public class HalBrowserEndpoint extends WebMvcConfigurerAdapter implements MvcEndpoint {
private static final String HAL_BROWSER_VERSION = "b7669f1-1";
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 otherwise the browser won't come back for
// the JSON
registry.addResourceHandler(this.management.getContextPath() + this.path + "/")
.addResourceLocations(
"classpath:/META-INF/resources/webjars/hal-browser/"
+ HAL_BROWSER_VERSION + "/").setCachePeriod(0);
registry.addResourceHandler(this.management.getContextPath() + this.path + "/**")
.addResourceLocations(
"classpath:/META-INF/resources/webjars/hal-browser/"
+ HAL_BROWSER_VERSION + "/");
}
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<? extends Endpoint<?>> getEndpointType() {
return null;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 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.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Dave Syer
*
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HypermediaDisabled {
}

View File

@@ -45,8 +45,9 @@ import org.springframework.web.util.UrlPathHelper;
* @author Andy Wilkinson
*/
@ConfigurationProperties(prefix = "endpoints.jolokia", ignoreUnknownFields = false)
@HypermediaDisabled
public class JolokiaMvcEndpoint implements MvcEndpoint, InitializingBean,
ApplicationContextAware, ServletContextAware {
ApplicationContextAware, ServletContextAware {
/**
* Endpoint URL path.

View File

@@ -0,0 +1,61 @@
/*
* Copyright 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 static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.util.StringUtils;
/**
* @author Dave Syer
*
*/
public class LinksEnhancer {
private MvcEndpoints endpoints;
private String rootPath;
public LinksEnhancer(MvcEndpoints endpoints, String rootPath) {
this.endpoints = endpoints;
this.rootPath = rootPath;
}
public void addEndpointLinks(ResourceSupport resource, String self) {
if (!resource.hasLink("self")) {
resource.add(linkTo(LinksEnhancer.class).slash(
this.rootPath + self).withSelfRel());
}
for (MvcEndpoint endpoint : this.endpoints.getEndpoints()) {
if (endpoint.getPath().equals(self)) {
continue;
}
Class<?> type = endpoint.getEndpointType();
if (type == null) {
type = Object.class;
}
String path = endpoint.getPath();
String rel = path.startsWith("/") ? path.substring(1) : path;
if (StringUtils.hasText(rel)) {
resource.add(linkTo(type).slash(this.rootPath + endpoint.getPath())
.withRel(rel));
}
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 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.endpoint.Endpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author Dave Syer
*
*/
@ConfigurationProperties("endpoints.links")
public class LinksMvcEndpoint implements MvcEndpoint {
private String path = "";
private boolean sensitive = false;
public LinksMvcEndpoint(String defaultPath) {
this.path = defaultPath;
}
@RequestMapping(value = { "/", "" }, produces=MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResourceSupport links() {
ResourceSupport resource = new ResourceSupport();
return resource;
}
public void setPath(String path) {
this.path = path;
}
@Override
public String getPath() {
return this.path;
}
@Override
public boolean isSensitive() {
return this.sensitive;
}
public void setSensitive(boolean sensitive) {
this.sensitive = sensitive;
}
@Override
public Class<? extends Endpoint<?>> getEndpointType() {
return null;
}
}

View File

@@ -44,6 +44,7 @@ public class MetricsMvcEndpoint extends EndpointMvcAdapter {
@RequestMapping(value = "/{name:.*}", method = RequestMethod.GET)
@ResponseBody
@HypermediaDisabled
public Object value(@PathVariable String name) {
if (!this.delegate.isEnabled()) {
// Shouldn't happen - MVC endpoint shouldn't be registered when delegate's

View File

@@ -19,4 +19,5 @@ org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration,
org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration
org.springframework.boot.actuate.autoconfigure.EndpointWebMvcConfiguration=\
org.springframework.boot.actuate.autoconfigure.EndpointWebMvcConfiguration
org.springframework.boot.actuate.autoconfigure.EndpointWebMvcConfiguration,\
org.springframework.boot.actuate.autoconfigure.EndpointWebMvcHypermediaConfiguration

View File

@@ -0,0 +1,71 @@
package org.springframework.boot.actuate.autoconfigure;
import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
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.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.BrowserPathHypermediaIntegrationTests.SpringBootHypermediaApplication;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Configuration;
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)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration
@TestPropertySource(properties = "endpoints.hal.path=/hal")
@DirtiesContext
public class BrowserPathHypermediaIntegrationTests {
@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 browser() throws Exception {
MvcResult response = this.mockMvc
.perform(get("/hal/").accept(MediaType.TEXT_HTML))
.andExpect(status().isOk()).andReturn();
assertEquals("/hal/browser.html", response.getResponse().getForwardedUrl());
}
@Test
public void redirect() throws Exception {
this.mockMvc.perform(get("/hal").accept(MediaType.TEXT_HTML))
.andExpect(status().isFound())
.andExpect(header().string("location", "/hal/#"));
}
@MinimalActuatorHypermediaApplication
@Configuration
public static class SpringBootHypermediaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootHypermediaApplication.class, args);
}
}
}

View File

@@ -0,0 +1,105 @@
package org.springframework.boot.actuate.autoconfigure;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
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.autoconfigure.ContextPathHypermediaIntegrationTests.SpringBootHypermediaApplication;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.hateoas.ResourceSupport;
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.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration
@TestPropertySource(properties = "management.contextPath:/admin")
@DirtiesContext
public class ContextPathHypermediaIntegrationTests {
@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());
}
@Test
public void links() throws Exception {
this.mockMvc.perform(get("/admin").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()).andExpect(jsonPath("$._links").exists());
}
@Test
public void trace() throws Exception {
this.mockMvc
.perform(get("/admin/trace").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(
jsonPath("$._links.self.href").value(
"http://localhost/admin/trace"))
.andExpect(jsonPath("$.content").isArray());
}
@Test
public void endpointsAllListed() throws Exception {
for (MvcEndpoint endpoint : this.mvcEndpoints.getEndpoints()) {
String path = endpoint.getPath();
path = path.startsWith("/") ? path.substring(1) : path;
path = path.length() > 0 ? path : "self";
this.mockMvc
.perform(get("/admin").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(
jsonPath("$._links.%s.href", path).value(
"http://localhost/admin" + endpoint.getPath()));
}
}
@MinimalActuatorHypermediaApplication
@RestController
public static class SpringBootHypermediaApplication {
@RequestMapping("")
public ResourceSupport home() {
ResourceSupport resource = new ResourceSupport();
resource.add(linkTo(SpringBootHypermediaApplication.class).slash("/")
.withSelfRel());
return resource;
}
public static void main(String[] args) {
new SpringApplicationBuilder(SpringBootHypermediaApplication.class)
.properties("management.contextPath:/admin").run(args);
}
}
}

View File

@@ -0,0 +1,80 @@
package org.springframework.boot.actuate.autoconfigure;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
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.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.CustomHomepageHypermediaIntegrationTests.SpringBootHypermediaApplication;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.http.MediaType;
import org.springframework.test.annotation.DirtiesContext;
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.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration
@DirtiesContext
public class CustomHomepageHypermediaIntegrationTests {
@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());
}
@Test
public void endpointsAllListed() throws Exception {
for (MvcEndpoint endpoint : this.mvcEndpoints.getEndpoints()) {
String path = endpoint.getPath();
path = path.startsWith("/") ? path.substring(1) : path;
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.%s.href", path).exists());
}
}
@MinimalActuatorHypermediaApplication
@RestController
public static class SpringBootHypermediaApplication {
@RequestMapping("")
public ResourceSupport home() {
ResourceSupport resource = new ResourceSupport();
resource.add(linkTo(SpringBootHypermediaApplication.class).slash("/").withSelfRel());
return resource;
}
public static void main(String[] args) {
SpringApplication.run(SpringBootHypermediaApplication.class, args);
}
}
}

View File

@@ -16,6 +16,9 @@
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;
@@ -38,6 +41,7 @@ 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.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration;
@@ -59,9 +63,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.
*
@@ -93,18 +94,19 @@ public class EndpointMvcIntegrationTests {
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ EmbeddedServletContainerAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
ServerPropertiesAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
protected static @interface MinimalWebConfiguration {
}
@Configuration
@MinimalWebConfiguration
@Import({ ManagementServerPropertiesAutoConfiguration.class,
EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class })
@Import({ ManagementServerPropertiesAutoConfiguration.class, JacksonAutoConfiguration.class,
EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class })
@RestController
protected static class Application {

View File

@@ -16,6 +16,18 @@
package org.springframework.boot.actuate.autoconfigure;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import java.io.FileNotFoundException;
import java.net.SocketException;
import java.net.URI;
@@ -39,6 +51,7 @@ import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.ShutdownMvcEndpoint;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
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;
@@ -71,18 +84,6 @@ import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link EndpointWebMvcAutoConfiguration}.
*
@@ -228,6 +229,7 @@ public class EndpointWebMvcAutoConfigurationTests {
ServerPortConfig.class, PropertyPlaceholderAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class,
JacksonAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
@@ -244,7 +246,7 @@ public class EndpointWebMvcAutoConfigurationTests {
this.applicationContext.register(RootConfig.class, BaseConfiguration.class,
ServerPortConfig.class, EndpointWebMvcAutoConfiguration.class);
new ServerPortInfoApplicationContextInitializer()
.initialize(this.applicationContext);
.initialize(this.applicationContext);
this.applicationContext.refresh();
Integer localServerPort = this.applicationContext.getEnvironment().getProperty(
"local.server.port", Integer.class);
@@ -260,7 +262,7 @@ public class EndpointWebMvcAutoConfigurationTests {
@Test
public void portPropertiesOnDifferentPort() throws Exception {
new ServerPortInfoApplicationContextInitializer()
.initialize(this.applicationContext);
.initialize(this.applicationContext);
this.applicationContext.register(RootConfig.class, DifferentPortConfig.class,
BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class,
ErrorMvcAutoConfiguration.class);
@@ -295,7 +297,7 @@ public class EndpointWebMvcAutoConfigurationTests {
this.applicationContext.refresh();
// /health, /metrics, /env (/shutdown is disabled by default)
assertThat(this.applicationContext.getBeansOfType(MvcEndpoint.class).size(),
is(equalTo(4)));
is(equalTo(6)));
}
@Test
@@ -421,12 +423,13 @@ public class EndpointWebMvcAutoConfigurationTests {
@Configuration
@Import({ PropertyPlaceholderAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class,
EndpointAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class, WebMvcAutoConfiguration.class })
EmbeddedServletContainerAutoConfiguration.class,
JacksonAutoConfiguration.class,
EndpointAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
DispatcherServletAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class, WebMvcAutoConfiguration.class })
protected static class BaseConfiguration {
}
@@ -584,7 +587,7 @@ public class EndpointWebMvcAutoConfigurationTests {
}
private static class GrabManagementPort implements
ApplicationListener<EmbeddedServletContainerInitializedEvent> {
ApplicationListener<EmbeddedServletContainerInitializedEvent> {
private ApplicationContext rootContext;

View File

@@ -16,6 +16,8 @@
package org.springframework.boot.actuate.autoconfigure;
import static org.junit.Assert.assertEquals;
import org.junit.After;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint;
@@ -23,14 +25,14 @@ import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Health.Builder;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.mock.web.MockServletContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link EndpointWebMvcAutoConfiguration} of the {@link HealthMvcEndpoint}.
*
@@ -53,6 +55,8 @@ public class HealthMvcEndpointAutoConfigurationTests {
this.context.setServletContext(new MockServletContext());
this.context.register(SecurityAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class,
TestHealthIndicator.class);
this.context.refresh();
@@ -67,6 +71,8 @@ public class HealthMvcEndpointAutoConfigurationTests {
this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext());
this.context.register(SecurityAutoConfiguration.class,
JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class,
TestHealthIndicator.class);

View File

@@ -16,6 +16,13 @@
package org.springframework.boot.actuate.autoconfigure;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import javax.servlet.Filter;
import org.hamcrest.Matchers;
@@ -23,9 +30,11 @@ import org.junit.After;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -51,13 +60,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link ManagementSecurityAutoConfiguration}.
*
@@ -81,6 +83,7 @@ public class ManagementSecurityAutoConfigurationTests {
this.context.setServletContext(new MockServletContext());
this.context.register(SecurityAutoConfiguration.class,
ManagementSecurityAutoConfiguration.class,
JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
@@ -109,6 +112,7 @@ public class ManagementSecurityAutoConfigurationTests {
this.context.setServletContext(new MockServletContext());
this.context.register(EndpointAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class,
JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
SecurityAutoConfiguration.class,
@@ -118,7 +122,7 @@ public class ManagementSecurityAutoConfigurationTests {
UserDetails user = getUser();
assertTrue(user.getAuthorities().containsAll(
AuthorityUtils
.commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_ADMIN")));
.commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_ADMIN")));
}
private UserDetails getUser() {
@@ -153,6 +157,7 @@ public class ManagementSecurityAutoConfigurationTests {
this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext());
this.context.register(HttpMessageConvertersAutoConfiguration.class,
JacksonAutoConfiguration.class,
EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
SecurityAutoConfiguration.class,
@@ -203,9 +208,11 @@ public class ManagementSecurityAutoConfigurationTests {
this.context.register(AuthenticationConfig.class,
SecurityAutoConfiguration.class,
ManagementSecurityAutoConfiguration.class,
JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
WebMvcAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
@@ -214,20 +221,20 @@ public class ManagementSecurityAutoConfigurationTests {
.addFilters(filter).build();
// no user (Main)
mockMvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
.andExpect(springAuthenticateRealmHeader());
mockMvc.perform(MockMvcRequestBuilders.get("/home"))
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
.andExpect(springAuthenticateRealmHeader());
// invalid user (Main)
mockMvc.perform(
MockMvcRequestBuilders.get("/").header("authorization", "Basic xxx"))
MockMvcRequestBuilders.get("/home").header("authorization", "Basic xxx"))
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
.andExpect(springAuthenticateRealmHeader());
// no user (Management)
mockMvc.perform(MockMvcRequestBuilders.get("/beans"))
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
.andExpect(springAuthenticateRealmHeader());
.andExpect(MockMvcResultMatchers.status().isUnauthorized())
.andExpect(springAuthenticateRealmHeader());
// invalid user (Management)
mockMvc.perform(
@@ -247,7 +254,7 @@ public class ManagementSecurityAutoConfigurationTests {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password")
.roles("USER");
.roles("USER");
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 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.autoconfigure;
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 org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration;
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.ServerPropertiesAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author Dave Syer
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Import({ ServerPropertiesAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class,
DispatcherServletAutoConfiguration.class, JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, WebMvcAutoConfiguration.class,
HypermediaAutoConfiguration.class, EndpointAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
public @interface MinimalActuatorHypermediaApplication {
}

View File

@@ -0,0 +1,82 @@
package org.springframework.boot.actuate.autoconfigure;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.ServerContextPathHypermediaIntegrationTests.SpringBootHypermediaApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration
@IntegrationTest({ "server.port=0", "server.contextPath=/spring" })
@DirtiesContext
public class ServerContextPathHypermediaIntegrationTests {
@Value("${local.server.port}")
private int port;
@Test
public void links() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
ResponseEntity<String> entity = new TestRestTemplate().exchange(
"http://localhost:" + this.port + "/spring/", HttpMethod.GET,
new HttpEntity<Void>(null, headers), String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body: " + entity.getBody(),
entity.getBody().contains("\"_links\":"));
}
@Test
public void browser() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
ResponseEntity<String> entity = new TestRestTemplate().exchange(
"http://localhost:" + this.port + "/spring/", HttpMethod.GET,
new HttpEntity<Void>(null, headers), String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body: " + entity.getBody(),
entity.getBody().contains("<title"));
}
@MinimalActuatorHypermediaApplication
@RestController
public static class SpringBootHypermediaApplication {
@RequestMapping("")
public ResourceSupport home() {
ResourceSupport resource = new ResourceSupport();
resource.add(linkTo(SpringBootHypermediaApplication.class).slash("/")
.withSelfRel());
return resource;
}
public static void main(String[] args) {
new SpringApplicationBuilder(SpringBootHypermediaApplication.class)
.properties("server.contextPath=/spring").run(args);
}
}
}

View File

@@ -0,0 +1,81 @@
package org.springframework.boot.actuate.autoconfigure;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.ServerPortHypermediaIntegrationTests.SpringBootHypermediaApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration
@IntegrationTest({ "server.port=0", "management.port=0" })
@DirtiesContext
public class ServerPortHypermediaIntegrationTests {
@Value("${local.management.port}")
private int port;
@Test
public void links() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
ResponseEntity<String> entity = new TestRestTemplate().exchange(
"http://localhost:" + this.port + "/", HttpMethod.GET,
new HttpEntity<Void>(null, headers), String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body: " + entity.getBody(),
entity.getBody().contains("\"_links\":"));
}
@Test
public void browser() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
ResponseEntity<String> entity = new TestRestTemplate().exchange(
"http://localhost:" + this.port + "/", HttpMethod.GET,
new HttpEntity<Void>(null, headers), String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body: " + entity.getBody(), entity.getBody().contains("<title"));
}
@MinimalActuatorHypermediaApplication
@RestController
public static class SpringBootHypermediaApplication {
@RequestMapping("")
public ResourceSupport home() {
ResourceSupport resource = new ResourceSupport();
resource.add(linkTo(SpringBootHypermediaApplication.class).slash("/")
.withSelfRel());
return resource;
}
public static void main(String[] args) {
new SpringApplicationBuilder(SpringBootHypermediaApplication.class)
.properties("management.port:9000").run(args);
}
}
}

View File

@@ -0,0 +1,115 @@
package org.springframework.boot.actuate.autoconfigure;
import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
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.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.VanillaHypermediaIntegrationTests.SpringBootHypermediaApplication;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.test.annotation.DirtiesContext;
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)
@SpringApplicationConfiguration(classes = SpringBootHypermediaApplication.class)
@WebAppConfiguration
@DirtiesContext
public class VanillaHypermediaIntegrationTests {
@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(header().doesNotExist("cache-control"));
}
@Test
public void browser() throws Exception {
MvcResult response = this.mockMvc.perform(get("/").accept(MediaType.TEXT_HTML))
.andExpect(status().isOk()).andReturn();
assertEquals("/browser.html", response.getResponse().getForwardedUrl());
}
@Test
public void trace() throws Exception {
this.mockMvc
.perform(get("/trace").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.self.href").value("http://localhost/trace"))
.andExpect(jsonPath("$.content").isArray());
}
@Test
public void envValue() throws Exception {
this.mockMvc.perform(get("/env/user.home").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links").doesNotExist());
}
@Test
public void endpointsAllListed() throws Exception {
for (MvcEndpoint endpoint : this.mvcEndpoints.getEndpoints()) {
String path = endpoint.getPath();
path = path.startsWith("/") ? path.substring(1) : path;
this.mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.%s.href", path).exists());
}
}
@Test
public void endpointsEachHaveSelf() throws Exception {
for (MvcEndpoint endpoint : this.mvcEndpoints.getEndpoints()) {
String path = endpoint.getPath();
if ("/hal".equals(path) || "/logfile".equals(path)) {
// TODO: /logfile shouldn't be active anyway
continue;
}
path = path.length() > 0 ? path : "/";
this.mockMvc
.perform(get(path).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(
jsonPath("$._links.self.href").value(
"http://localhost" + endpoint.getPath()));
}
}
@MinimalActuatorHypermediaApplication
@Configuration
public static class SpringBootHypermediaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootHypermediaApplication.class, args);
}
}
}

View File

@@ -16,6 +16,12 @@
package org.springframework.boot.actuate.endpoint.mvc;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalToIgnoringCase;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -24,6 +30,9 @@ import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfigur
import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration;
import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpointTests.TestConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
@@ -35,13 +44,6 @@ import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalToIgnoringCase;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link EnvironmentMvcEndpoint}
@@ -70,13 +72,13 @@ public class EnvironmentMvcEndpointTests {
@Test
public void home() throws Exception {
this.mvc.perform(get("/env")).andExpect(status().isOk())
.andExpect(content().string(containsString("systemProperties")));
.andExpect(content().string(containsString("systemProperties")));
}
@Test
public void sub() throws Exception {
this.mvc.perform(get("/env/foo")).andExpect(status().isOk())
.andExpect(content().string(equalToIgnoringCase("bar")));
.andExpect(content().string(equalToIgnoringCase("bar")));
}
@Test
@@ -88,13 +90,15 @@ public class EnvironmentMvcEndpointTests {
@Test
public void regex() throws Exception {
this.mvc.perform(get("/env/foo.*")).andExpect(status().isOk())
.andExpect(content().string(containsString("\"foo\":\"bar\"")))
.andExpect(content().string(containsString("\"fool\":\"baz\"")));
.andExpect(content().string(containsString("\"foo\":\"bar\"")))
.andExpect(content().string(containsString("\"fool\":\"baz\"")));
}
@Import({ EndpointWebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class })
@EnableWebMvc
@Import({ JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
WebMvcAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class })
@Configuration
public static class TestConfiguration {

View File

@@ -16,6 +16,11 @@
package org.springframework.boot.actuate.endpoint.mvc;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -25,6 +30,8 @@ import org.springframework.boot.actuate.autoconfigure.JolokiaAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration;
import org.springframework.boot.actuate.endpoint.mvc.JolokiaMvcEndpointContextPathTests.Config;
import org.springframework.boot.actuate.endpoint.mvc.JolokiaMvcEndpointContextPathTests.ContextPathListener;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.boot.test.SpringApplicationConfiguration;
@@ -39,11 +46,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author Christian Dupuis
* @author Dave Syer
@@ -71,20 +73,22 @@ public class JolokiaMvcEndpointContextPathTests {
@Test
public void read() throws Exception {
this.mvc.perform(get("/admin/jolokia/read/java.lang:type=Memory"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("NonHeapMemoryUsage")));
.andExpect(status().isOk())
.andExpect(content().string(containsString("NonHeapMemoryUsage")));
}
@Configuration
@EnableConfigurationProperties
@EnableWebMvc
@Import({ EndpointWebMvcAutoConfiguration.class, JolokiaAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class })
@Import({ JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class, JolokiaAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class })
public static class Config {
}
public static class ContextPathListener implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext context) {
EnvironmentTestUtils.addEnvironment(context, "management.contextPath:/admin");

View File

@@ -16,6 +16,14 @@
package org.springframework.boot.actuate.endpoint.mvc;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.Set;
import org.hamcrest.Matcher;
@@ -27,6 +35,8 @@ import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfigur
import org.springframework.boot.actuate.autoconfigure.JolokiaAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration;
import org.springframework.boot.actuate.endpoint.mvc.JolokiaMvcEndpointTests.Config;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.boot.test.SpringApplicationConfiguration;
@@ -40,14 +50,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link JolokiaMvcEndpoint}
*
@@ -84,28 +86,30 @@ public class JolokiaMvcEndpointTests {
@Test
public void search() throws Exception {
this.mvc.perform(get("/jolokia/search/java.lang:*")).andExpect(status().isOk())
.andExpect(content().string(containsString("GarbageCollector")));
.andExpect(content().string(containsString("GarbageCollector")));
}
@Test
public void read() throws Exception {
this.mvc.perform(get("/jolokia/read/java.lang:type=Memory"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("NonHeapMemoryUsage")));
.andExpect(status().isOk())
.andExpect(content().string(containsString("NonHeapMemoryUsage")));
}
@Test
public void list() throws Exception {
this.mvc.perform(get("/jolokia/list/java.lang/type=Memory/attr"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("NonHeapMemoryUsage")));
.andExpect(status().isOk())
.andExpect(content().string(containsString("NonHeapMemoryUsage")));
}
@Configuration
@EnableConfigurationProperties
@EnableWebMvc
@Import({ EndpointWebMvcAutoConfiguration.class, JolokiaAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class })
@Import({ JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class, JolokiaAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class })
public static class Config {
}

View File

@@ -16,6 +16,12 @@
package org.springframework.boot.actuate.endpoint.mvc;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -30,6 +36,9 @@ import org.springframework.boot.actuate.endpoint.MetricsEndpoint;
import org.springframework.boot.actuate.endpoint.PublicMetrics;
import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpointTests.TestConfiguration;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -39,13 +48,6 @@ import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link MetricsMvcEndpoint}
@@ -72,7 +74,7 @@ public class MetricsMvcEndpointTests {
@Test
public void home() throws Exception {
this.mvc.perform(get("/metrics")).andExpect(status().isOk())
.andExpect(content().string(containsString("\"foo\":1")));
.andExpect(content().string(containsString("\"foo\":1")));
}
@Test
@@ -84,7 +86,7 @@ public class MetricsMvcEndpointTests {
@Test
public void specificMetric() throws Exception {
this.mvc.perform(get("/metrics/foo")).andExpect(status().isOk())
.andExpect(content().string(equalTo("1")));
.andExpect(content().string(equalTo("1")));
}
@Test
@@ -100,35 +102,37 @@ public class MetricsMvcEndpointTests {
@Test
public void regexAll() throws Exception {
String expected = "{\"foo\":1,\"group1.a\":1,\"group1.b\":1,\"group2.a\":1,\"group2_a\":1}";
String expected = "\"foo\":1,\"group1.a\":1,\"group1.b\":1,\"group2.a\":1,\"group2_a\":1";
this.mvc.perform(get("/metrics/.*")).andExpect(status().isOk())
.andExpect(content().string(expected));
.andExpect(content().string(containsString(expected)));
}
@Test
public void regexGroupDot() throws Exception {
String expected = "{\"group1.a\":1,\"group1.b\":1,\"group2.a\":1}";
String expected = "\"group1.a\":1,\"group1.b\":1,\"group2.a\":1";
this.mvc.perform(get("/metrics/group[0-9]+\\..*")).andExpect(status().isOk())
.andExpect(content().string(expected));
.andExpect(content().string(containsString(expected)));
}
@Test
public void regexGroup1() throws Exception {
String expected = "{\"group1.a\":1,\"group1.b\":1}";
String expected = "\"group1.a\":1,\"group1.b\":1";
this.mvc.perform(get("/metrics/group1\\..*")).andExpect(status().isOk())
.andExpect(content().string(expected));
.andExpect(content().string(containsString(expected)));
}
@Test
public void specificMetricWithDot() throws Exception {
this.mvc.perform(get("/metrics/group2.a")).andExpect(status().isOk())
.andExpect(content().string("1"));
.andExpect(content().string(containsString("1")));
}
@Import({ EndpointWebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class })
@EnableWebMvc
@Import({ JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
EndpointWebMvcAutoConfiguration.class,
WebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class })
@Configuration
public static class TestConfiguration {

View File

@@ -16,6 +16,10 @@
package org.springframework.boot.actuate.endpoint.mvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
@@ -23,6 +27,7 @@ import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfigur
import org.springframework.boot.actuate.autoconfigure.JolokiaAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.test.EnvironmentTestUtils;
@@ -33,10 +38,6 @@ import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Integration tests for the actuator endpoints' CORS support
*
@@ -50,7 +51,7 @@ public class MvcEndpointCorsIntegrationTests {
public void createContext() {
this.context = new AnnotationConfigWebApplicationContext();
this.context.setServletContext(new MockServletContext());
this.context.register(HttpMessageConvertersAutoConfiguration.class,
this.context.register(JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
EndpointAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
@@ -62,7 +63,7 @@ public class MvcEndpointCorsIntegrationTests {
createMockMvc().perform(
options("/beans").header("Origin", "foo.example.com").header(
HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).andExpect(
header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
}
@Test
@@ -72,7 +73,7 @@ public class MvcEndpointCorsIntegrationTests {
createMockMvc().perform(
options("/beans").header("Origin", "bar.example.com").header(
HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).andExpect(
status().isForbidden());
status().isForbidden());
performAcceptedCorsRequest();
}
@@ -99,8 +100,8 @@ public class MvcEndpointCorsIntegrationTests {
"endpoints.cors.allowed-origins:foo.example.com");
createMockMvc().perform(
options("/beans").header("Origin", "foo.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Alpha"))
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Alpha"))
.andExpect(status().isForbidden());
}
@@ -110,15 +111,15 @@ public class MvcEndpointCorsIntegrationTests {
"endpoints.cors.allowed-origins:foo.example.com",
"endpoints.cors.allowed-headers:Alpha,Bravo");
createMockMvc()
.perform(
options("/beans")
.header("Origin", "foo.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS,
"Alpha"))
.andExpect(status().isOk())
.andExpect(
header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Alpha"));
.perform(
options("/beans")
.header("Origin", "foo.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS,
"Alpha"))
.andExpect(status().isOk())
.andExpect(
header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Alpha"));
}
@Test
@@ -128,7 +129,7 @@ public class MvcEndpointCorsIntegrationTests {
createMockMvc().perform(
options("/health").header(HttpHeaders.ORIGIN, "foo.example.com").header(
HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD")).andExpect(
status().isForbidden());
status().isForbidden());
}
@Test
@@ -137,10 +138,10 @@ public class MvcEndpointCorsIntegrationTests {
"endpoints.cors.allowed-origins:foo.example.com",
"endpoints.cors.allowed-methods:GET,HEAD");
createMockMvc()
.perform(
options("/health")
.header(HttpHeaders.ORIGIN, "foo.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD"))
.perform(
options("/health")
.header(HttpHeaders.ORIGIN, "foo.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD"))
.andExpect(status().isOk())
.andExpect(
header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS,
@@ -172,7 +173,7 @@ public class MvcEndpointCorsIntegrationTests {
createMockMvc().perform(
options("/jolokia").header("Origin", "bar.example.com").header(
HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).andExpect(
status().isForbidden());
status().isForbidden());
performAcceptedCorsRequest("/jolokia");
}
@@ -189,10 +190,10 @@ public class MvcEndpointCorsIntegrationTests {
return createMockMvc()
.perform(
options(url).header(HttpHeaders.ORIGIN, "foo.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"))
.andExpect(
header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN,
"foo.example.com")).andExpect(status().isOk());
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"))
.andExpect(
header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN,
"foo.example.com")).andExpect(status().isOk());
}
}