@FrameworkEndpoint replaces EndpointHandlerAdapter

This commit is contained in:
Dave Syer
2013-12-08 15:46:12 +00:00
committed by Phillip Webb
parent 5a978e2f31
commit bbac4ea9fb
27 changed files with 425 additions and 529 deletions

View File

@@ -29,7 +29,6 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerAdapter;
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
import org.springframework.boot.actuate.properties.ManagementServerProperties;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@@ -40,7 +39,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
@@ -91,15 +89,6 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware,
return mapping;
}
@Bean
@ConditionalOnMissingBean
public EndpointHandlerAdapter endpointHandlerAdapter(
final HttpMessageConverters messageConverters) {
EndpointHandlerAdapter adapter = new EndpointHandlerAdapter();
adapter.setMessageConverters(messageConverters.getConverters());
return adapter;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {

View File

@@ -16,8 +16,6 @@
package org.springframework.boot.actuate.autoconfigure;
import java.util.Map;
import javax.servlet.Filter;
import org.springframework.beans.factory.BeanFactory;
@@ -26,9 +24,7 @@ import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerAdapter;
import org.springframework.boot.actuate.endpoint.ManagementErrorEndpoint;
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
import org.springframework.boot.actuate.properties.ManagementServerProperties;
import org.springframework.boot.actuate.web.ErrorController;
@@ -42,11 +38,11 @@ import org.springframework.boot.context.embedded.ErrorPage;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
/**
* Configuration triggered from {@link EndpointWebMvcAutoConfiguration} when a new
@@ -57,6 +53,9 @@ import org.springframework.web.servlet.HandlerMapping;
@Configuration
public class EndpointWebMvcChildContextConfiguration {
@Value("${error.path:/error}")
private String errorPath = "/error";
@Configuration
protected static class ServerCustomization implements
EmbeddedServletContainerCustomizer {
@@ -100,13 +99,19 @@ public class EndpointWebMvcChildContextConfiguration {
}
@Bean
public HandlerMapping handlerMapping() {
return new EndpointHandlerMapping();
public HandlerAdapter handlerAdapter() {
// TODO: maybe this needs more configuration for non-basic response use cases
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
adapter.setMessageConverters(new RestTemplate().getMessageConverters());
return adapter;
}
@Bean
public HandlerAdapter handlerAdapter() {
return new EndpointHandlerAdapter();
public HandlerMapping handlerMapping() {
EndpointHandlerMapping mapping = new EndpointHandlerMapping();
// In a child context we definitely want to see the parent endpoints
mapping.setDetectHandlerMethodsInAncestorContexts(true);
return mapping;
}
/*
@@ -116,15 +121,8 @@ public class EndpointWebMvcChildContextConfiguration {
* endpoints.
*/
@Bean
public Endpoint<Map<String, Object>> errorEndpoint(final ErrorController controller) {
return new AbstractEndpoint<Map<String, Object>>("/error", false, true) {
@Override
protected Map<String, Object> doInvoke() {
RequestAttributes attributes = RequestContextHolder
.currentRequestAttributes();
return controller.extract(attributes, false);
}
};
public ManagementErrorEndpoint errorEndpoint(final ErrorController controller) {
return new ManagementErrorEndpoint(this.errorPath, controller);
}
@Configuration

View File

@@ -19,28 +19,15 @@ package org.springframework.boot.actuate.endpoint;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
/**
* Abstract base for {@link Endpoint} implementations.
* <p>
* {@link Endpoint}s that support other {@link HttpMethod}s than {@link HttpMethod#GET}
* should override {@link #methods()} and provide a list of supported methods.
*
* @author Phillip Webb
* @author Christian Dupuis
*/
public abstract class AbstractEndpoint<T> implements Endpoint<T> {
private static final MediaType[] NO_MEDIA_TYPES = new MediaType[0];
protected static final HttpMethod[] NO_HTTP_METHOD = new HttpMethod[0];
protected static final HttpMethod[] GET_HTTP_METHOD = new HttpMethod[] { HttpMethod.GET };
protected static final HttpMethod[] POST_HTTP_METHOD = new HttpMethod[] { HttpMethod.POST };
@NotNull
@Pattern(regexp = "/[^/]*", message = "Path must start with /")
private String path;
@@ -85,23 +72,4 @@ public abstract class AbstractEndpoint<T> implements Endpoint<T> {
this.sensitive = sensitive;
}
@Override
public MediaType[] produces() {
return NO_MEDIA_TYPES;
}
@Override
public HttpMethod[] methods() {
return GET_HTTP_METHOD;
}
@Override
public final T invoke() {
if (this.enabled) {
return doInvoke();
}
throw new EndpointDisabledException();
}
protected abstract T doInvoke();
}

View File

@@ -21,6 +21,7 @@ import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.AutoConfigurationReportEndpoint.Report;
import org.springframework.boot.actuate.endpoint.mvc.FrameworkEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigurationReport;
import org.springframework.boot.autoconfigure.AutoConfigurationReport.ConditionAndOutcome;
import org.springframework.boot.autoconfigure.AutoConfigurationReport.ConditionAndOutcomes;
@@ -31,6 +32,8 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
@@ -42,6 +45,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
* @author Dave Syer
*/
@ConfigurationProperties(name = "endpoints.autoconfig", ignoreUnknownFields = false)
@FrameworkEndpoint
public class AutoConfigurationReportEndpoint extends AbstractEndpoint<Report> {
@Autowired
@@ -52,7 +56,9 @@ public class AutoConfigurationReportEndpoint extends AbstractEndpoint<Report> {
}
@Override
protected Report doInvoke() {
@RequestMapping
@ResponseBody
public Report invoke() {
return new Report(this.autoConfigurationReport);
}

View File

@@ -17,12 +17,15 @@
package org.springframework.boot.actuate.endpoint;
import org.springframework.beans.BeansException;
import org.springframework.boot.actuate.endpoint.mvc.FrameworkEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.LiveBeansView;
import org.springframework.core.env.Environment;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Exposes JSON view of Spring beans. If the {@link Environment} contains a key setting
@@ -33,6 +36,7 @@ import org.springframework.http.MediaType;
* @author Dave Syer
*/
@ConfigurationProperties(name = "endpoints.beans", ignoreUnknownFields = false)
@FrameworkEndpoint
public class BeansEndpoint extends AbstractEndpoint<String> implements
ApplicationContextAware {
@@ -51,12 +55,9 @@ public class BeansEndpoint extends AbstractEndpoint<String> implements
}
@Override
public MediaType[] produces() {
return new MediaType[] { MediaType.APPLICATION_JSON };
}
@Override
protected String doInvoke() {
@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String invoke() {
return this.liveBeansView.getSnapshotAsJson();
}
}

View File

@@ -19,10 +19,13 @@ package org.springframework.boot.actuate.endpoint;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.boot.actuate.endpoint.mvc.FrameworkEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -37,6 +40,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
* @author Christian Dupuis
*/
@ConfigurationProperties(name = "endpoints.configprops", ignoreUnknownFields = false)
@FrameworkEndpoint
public class ConfigurationPropertiesReportEndpoint extends
AbstractEndpoint<Map<String, Object>> implements ApplicationContextAware {
@@ -64,7 +68,9 @@ public class ConfigurationPropertiesReportEndpoint extends
@Override
@SuppressWarnings("unchecked")
protected Map<String, Object> doInvoke() {
@RequestMapping
@ResponseBody
public Map<String, Object> invoke() {
Map<String, Object> beans = this.context
.getBeansWithAnnotation(ConfigurationProperties.class);

View File

@@ -21,7 +21,10 @@ import java.lang.management.ThreadInfo;
import java.util.Arrays;
import java.util.List;
import org.springframework.boot.actuate.endpoint.mvc.FrameworkEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* {@link Endpoint} to expose thread info.
@@ -29,6 +32,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Dave Syer
*/
@ConfigurationProperties(name = "endpoints.dump", ignoreUnknownFields = false)
@FrameworkEndpoint
public class DumpEndpoint extends AbstractEndpoint<List<ThreadInfo>> {
/**
@@ -39,7 +43,9 @@ public class DumpEndpoint extends AbstractEndpoint<List<ThreadInfo>> {
}
@Override
protected List<ThreadInfo> doInvoke() {
@RequestMapping
@ResponseBody
public List<ThreadInfo> invoke() {
return Arrays.asList(ManagementFactory.getThreadMXBean().dumpAllThreads(true,
true));
}

View File

@@ -16,9 +16,6 @@
package org.springframework.boot.actuate.endpoint;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
/**
* An endpoint that can be used to expose useful information to operations. Usually
* exposed via Spring MVC but could also be exposed using some other technique.
@@ -41,16 +38,6 @@ public interface Endpoint<T> {
*/
boolean isSensitive();
/**
* Returns the {@link MediaType}s that this endpoint produces or {@code null}.
*/
MediaType[] produces();
/**
* Returns the {@link HttpMethod}s that this endpoint supports.
*/
HttpMethod[] methods();
/**
* Called to invoke the endpoint.
* @return the results of the invocation

View File

@@ -19,6 +19,7 @@ package org.springframework.boot.actuate.endpoint;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.mvc.FrameworkEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.ConfigurableEnvironment;
@@ -26,6 +27,11 @@ import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* {@link Endpoint} to expose {@link ConfigurableEnvironment environment} information.
@@ -34,6 +40,7 @@ import org.springframework.core.env.StandardEnvironment;
* @author Phillip Webb
*/
@ConfigurationProperties(name = "endpoints.env", ignoreUnknownFields = false)
@FrameworkEndpoint
public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> implements
EnvironmentAware {
@@ -47,7 +54,9 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> i
}
@Override
protected Map<String, Object> doInvoke() {
@RequestMapping
@ResponseBody
public Map<String, Object> invoke() {
Map<String, Object> result = new LinkedHashMap<String, Object>();
result.put("profiles", this.environment.getActiveProfiles());
for (PropertySource<?> source : getPropertySources()) {
@@ -63,6 +72,16 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> i
return result;
}
@RequestMapping("/{name:.*}")
@ResponseBody
public Object value(@PathVariable String name) {
String result = this.environment.getProperty(name);
if (result == null) {
throw new NoSuchPropertyException("No such property: " + name);
}
return sanitize(name, result);
}
private Iterable<PropertySource<?>> getPropertySources() {
if (this.environment != null
&& this.environment instanceof ConfigurableEnvironment) {
@@ -84,4 +103,13 @@ public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> i
this.environment = environment;
}
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No such property")
public static class NoSuchPropertyException extends RuntimeException {
public NoSuchPropertyException(String string) {
super(string);
}
}
}

View File

@@ -16,9 +16,12 @@
package org.springframework.boot.actuate.endpoint;
import org.springframework.boot.actuate.endpoint.mvc.FrameworkEndpoint;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* {@link Endpoint} to expose application health.
@@ -26,6 +29,7 @@ import org.springframework.util.Assert;
* @author Dave Syer
*/
@ConfigurationProperties(name = "endpoints.health", ignoreUnknownFields = false)
@FrameworkEndpoint
public class HealthEndpoint<T> extends AbstractEndpoint<T> {
private HealthIndicator<? extends T> indicator;
@@ -41,8 +45,14 @@ public class HealthEndpoint<T> extends AbstractEndpoint<T> {
this.indicator = indicator;
}
HealthEndpoint() {
super("/health", false, true);
}
@Override
protected T doInvoke() {
@RequestMapping
@ResponseBody
public T invoke() {
return this.indicator.health();
}

View File

@@ -20,8 +20,11 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.mvc.FrameworkEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* {@link Endpoint} to expose arbitrary application information.
@@ -29,6 +32,7 @@ import org.springframework.util.Assert;
* @author Dave Syer
*/
@ConfigurationProperties(name = "endpoints.info", ignoreUnknownFields = false)
@FrameworkEndpoint
public class InfoEndpoint extends AbstractEndpoint<Map<String, Object>> {
private Map<String, ? extends Object> info;
@@ -45,7 +49,9 @@ public class InfoEndpoint extends AbstractEndpoint<Map<String, Object>> {
}
@Override
protected Map<String, Object> doInvoke() {
@RequestMapping
@ResponseBody
public Map<String, Object> invoke() {
Map<String, Object> info = new LinkedHashMap<String, Object>(this.info);
info.putAll(getAdditionalInfo());
return info;

View File

@@ -17,7 +17,6 @@
package org.springframework.boot.actuate.endpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.HttpMethod;
/**
* {@link Endpoint} implementation to register the Jolokia infrastructure with the Boot
@@ -33,13 +32,8 @@ public class JolokiaEndpoint extends AbstractEndpoint<String> {
}
@Override
protected String doInvoke() {
public String invoke() {
return null;
}
@Override
public HttpMethod[] methods() {
return NO_HTTP_METHOD;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2012-2013 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;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.mvc.FrameworkEndpoint;
import org.springframework.boot.actuate.web.ErrorController;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
/**
* Special endpoint for handling "/error" path when the management servlet is in a child
* context. The regular {@link ErrorController} should be available there but because of
* the way the handler mappings are set up it will not be detected.
*
* @author Dave Syer
*/
@FrameworkEndpoint
@ConfigurationProperties(name = "error")
public class ManagementErrorEndpoint extends AbstractEndpoint<Map<String, Object>> {
private final ErrorController controller;
public ManagementErrorEndpoint(String path, ErrorController controller) {
super(path, false, true);
this.controller = controller;
}
@Override
@RequestMapping
@ResponseBody
public Map<String, Object> invoke() {
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
return this.controller.extract(attributes, false);
}
}

View File

@@ -19,9 +19,15 @@ package org.springframework.boot.actuate.endpoint;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.mvc.FrameworkEndpoint;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* {@link Endpoint} to expose {@link PublicMetrics}.
@@ -29,6 +35,7 @@ import org.springframework.util.Assert;
* @author Dave Syer
*/
@ConfigurationProperties(name = "endpoints.metrics", ignoreUnknownFields = false)
@FrameworkEndpoint
public class MetricsEndpoint extends AbstractEndpoint<Map<String, Object>> {
private PublicMetrics metrics;
@@ -45,7 +52,9 @@ public class MetricsEndpoint extends AbstractEndpoint<Map<String, Object>> {
}
@Override
protected Map<String, Object> doInvoke() {
@RequestMapping
@ResponseBody
public Map<String, Object> invoke() {
Map<String, Object> result = new LinkedHashMap<String, Object>();
for (Metric metric : this.metrics.metrics()) {
result.put(metric.getName(), metric.getValue());
@@ -53,4 +62,22 @@ public class MetricsEndpoint extends AbstractEndpoint<Map<String, Object>> {
return result;
}
@RequestMapping("/{name:.*}")
@ResponseBody
public Object value(@PathVariable String name) {
Object value = invoke().get(name);
if (value == null) {
throw new NoSuchMetricException("No such metric: " + name);
}
return value;
}
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No such metric")
public static class NoSuchMetricException extends RuntimeException {
public NoSuchMetricException(String string) {
super(string);
}
}
}

View File

@@ -20,11 +20,14 @@ import java.util.Collections;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.boot.actuate.endpoint.mvc.FrameworkEndpoint;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* {@link Endpoint} to shutdown the {@link ApplicationContext}.
@@ -33,6 +36,7 @@ import org.springframework.http.HttpMethod;
* @author Christian Dupuis
*/
@ConfigurationProperties(name = "endpoints.shutdown", ignoreUnknownFields = false)
@FrameworkEndpoint
public class ShutdownEndpoint extends AbstractEndpoint<Map<String, Object>> implements
ApplicationContextAware {
@@ -46,7 +50,9 @@ public class ShutdownEndpoint extends AbstractEndpoint<Map<String, Object>> impl
}
@Override
protected Map<String, Object> doInvoke() {
@RequestMapping(method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> invoke() {
if (this.context == null) {
return Collections.<String, Object> singletonMap("message",
@@ -77,9 +83,4 @@ public class ShutdownEndpoint extends AbstractEndpoint<Map<String, Object>> impl
}
}
@Override
public HttpMethod[] methods() {
return POST_HTTP_METHOD;
}
}

View File

@@ -18,10 +18,13 @@ package org.springframework.boot.actuate.endpoint;
import java.util.List;
import org.springframework.boot.actuate.endpoint.mvc.FrameworkEndpoint;
import org.springframework.boot.actuate.trace.Trace;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* {@link Endpoint} to expose {@link Trace} information.
@@ -29,6 +32,7 @@ import org.springframework.util.Assert;
* @author Dave Syer
*/
@ConfigurationProperties(name = "endpoints.trace", ignoreUnknownFields = false)
@FrameworkEndpoint
public class TraceEndpoint extends AbstractEndpoint<List<Trace>> {
private TraceRepository repository;
@@ -45,7 +49,9 @@ public class TraceEndpoint extends AbstractEndpoint<List<Trace>> {
}
@Override
protected List<Trace> doInvoke() {
@RequestMapping
@ResponseBody
public List<Trace> invoke() {
return this.repository.findAll();
}
}

View File

@@ -1,238 +0,0 @@
/*
* Copyright 2012-2013 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.EndpointDisabledException;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
import com.fasterxml.jackson.databind.SerializationFeature;
/**
* MVC {@link HandlerAdapter} for {@link Endpoint}s. Similar in may respects to
* {@link AbstractMessageConverterMethodProcessor} but not tied to annotated methods.
*
* @author Phillip Webb
*
* @see EndpointHandlerMapping
*/
public final class EndpointHandlerAdapter implements HandlerAdapter {
private final Log logger = LogFactory.getLog(getClass());
private static final MediaType MEDIA_TYPE_APPLICATION = new MediaType("application");
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
private List<HttpMessageConverter<?>> messageConverters;
private List<MediaType> allSupportedMediaTypes;
public EndpointHandlerAdapter() {
WebMvcConfigurationSupportConventions conventions = new WebMvcConfigurationSupportConventions();
setMessageConverters(conventions.getDefaultHttpMessageConverters());
}
@Override
public boolean supports(Object handler) {
return handler instanceof Endpoint;
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
handle(request, response, (Endpoint<?>) handler);
return null;
}
@SuppressWarnings("unchecked")
private void handle(HttpServletRequest request, HttpServletResponse response,
Endpoint<?> endpoint) throws Exception {
Object result = null;
try {
result = endpoint.invoke();
}
catch (EndpointDisabledException e) {
// Disabled endpoints should get mapped to a HTTP 404
throw new NoSuchRequestHandlingMethodException(request);
}
Class<?> resultClass = result.getClass();
List<MediaType> mediaTypes = getMediaTypes(request, endpoint, resultClass);
MediaType selectedMediaType = selectMediaType(mediaTypes);
ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
try {
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter.canWrite(resultClass, selectedMediaType)) {
((HttpMessageConverter<Object>) messageConverter).write(result,
selectedMediaType, outputMessage);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Written [" + result + "] as \""
+ selectedMediaType + "\" using [" + messageConverter
+ "]");
}
return;
}
}
}
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
finally {
outputMessage.close();
}
}
private List<MediaType> getMediaTypes(HttpServletRequest request,
Endpoint<?> endpoint, Class<?> resultClass)
throws HttpMediaTypeNotAcceptableException {
List<MediaType> requested = getAcceptableMediaTypes(request);
List<MediaType> producible = getProducibleMediaTypes(endpoint, resultClass);
Set<MediaType> compatible = new LinkedHashSet<MediaType>();
for (MediaType r : requested) {
for (MediaType p : producible) {
if (r.isCompatibleWith(p)) {
compatible.add(getMostSpecificMediaType(r, p));
}
}
}
if (compatible.isEmpty()) {
throw new HttpMediaTypeNotAcceptableException(producible);
}
List<MediaType> mediaTypes = new ArrayList<MediaType>(compatible);
MediaType.sortBySpecificityAndQuality(mediaTypes);
return mediaTypes;
}
private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request)
throws HttpMediaTypeNotAcceptableException {
List<MediaType> mediaTypes = this.contentNegotiationManager
.resolveMediaTypes(new ServletWebRequest(request));
return mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL)
: mediaTypes;
}
private List<MediaType> getProducibleMediaTypes(Endpoint<?> endpoint,
Class<?> returnValueClass) {
MediaType[] mediaTypes = endpoint.produces();
if (mediaTypes != null && mediaTypes.length != 0) {
return Arrays.asList(mediaTypes);
}
if (this.allSupportedMediaTypes.isEmpty()) {
return Collections.singletonList(MediaType.ALL);
}
List<MediaType> result = new ArrayList<MediaType>();
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter.canWrite(returnValueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
return result;
}
private MediaType getMostSpecificMediaType(MediaType acceptType, MediaType produceType) {
produceType = produceType.copyQualityValue(acceptType);
return MediaType.SPECIFICITY_COMPARATOR.compare(acceptType, produceType) <= 0 ? acceptType
: produceType;
}
private MediaType selectMediaType(List<MediaType> mediaTypes) {
MediaType selectedMediaType = null;
for (MediaType mediaType : mediaTypes) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.equals(MediaType.ALL)
|| mediaType.equals(MEDIA_TYPE_APPLICATION)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
return selectedMediaType;
}
public void setContentNegotiationManager(
ContentNegotiationManager contentNegotiationManager) {
this.contentNegotiationManager = contentNegotiationManager;
}
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.messageConverters = messageConverters;
Set<MediaType> allSupportedMediaTypes = new LinkedHashSet<MediaType>();
for (HttpMessageConverter<?> messageConverter : messageConverters) {
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
}
this.allSupportedMediaTypes = new ArrayList<MediaType>(allSupportedMediaTypes);
MediaType.sortBySpecificity(this.allSupportedMediaTypes);
}
/**
* Default conventions, taken from {@link WebMvcConfigurationSupport} with a few minor
* tweaks.
*/
private static class WebMvcConfigurationSupportConventions extends
WebMvcConfigurationSupport {
public List<HttpMessageConverter<?>> getDefaultHttpMessageConverters() {
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
addDefaultHttpMessageConverters(converters);
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter jacksonConverter = (MappingJackson2HttpMessageConverter) converter;
jacksonConverter.getObjectMapper().disable(
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
}
return converters;
}
}
}

View File

@@ -16,33 +16,46 @@
package org.springframework.boot.actuate.endpoint.mvc;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import java.util.Set;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.http.HttpMethod;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
/**
* {@link HandlerMapping} to map {@link Endpoint}s to URLs via {@link Endpoint#getPath()}.
* Only endpoints that are annotated as <code>@FrameworkEndpoint</code> will be mapped,
* and within that class only those methods with <code>@RequestMapping</code> will be
* exposed. The semantics of <code>@RequestMapping</code> should be identical to a normal
* <code>@Controller</code>, but the endpoints should not be annotated as
* <code>@Controller</code> (otherwise they will be mapped by the normal MVC mechanisms).
*
* <p>
* One of the aims of the mapping is to support endpoints that work as HTTP endpoints but
* can still provide useful service interfaces when there is no HTTP server (and no Spring
* MVC on the classpath). Note that any endpoints having method signaturess will break in
* a non-servlet environment.
* </p>
*
* @author Phillip Webb
* @author Christian Dupuis
* @see EndpointHandlerAdapter
* @author Dave Syer
*
*/
public class EndpointHandlerMapping extends AbstractUrlHandlerMapping implements
public class EndpointHandlerMapping extends RequestMappingHandlerMapping implements
InitializingBean, ApplicationContextAware {
private List<Endpoint<?>> endpoints;
@@ -56,28 +69,16 @@ public class EndpointHandlerMapping extends AbstractUrlHandlerMapping implements
* detected from the {@link ApplicationContext}.
*/
public EndpointHandlerMapping() {
setOrder(HIGHEST_PRECEDENCE);
}
/**
* Create a new {@link EndpointHandlerMapping} with the specified endpoints.
* @param endpoints the endpoints
*/
public EndpointHandlerMapping(Collection<? extends Endpoint<?>> endpoints) {
Assert.notNull(endpoints, "Endpoints must not be null");
this.endpoints = new ArrayList<Endpoint<?>>(endpoints);
// By default the static resource handler mapping is LOWEST_PRECEDENCE - 1
setOrder(LOWEST_PRECEDENCE - 2);
}
@Override
public void afterPropertiesSet() throws Exception {
public void afterPropertiesSet() {
super.afterPropertiesSet();
if (this.endpoints == null) {
this.endpoints = findEndpointBeans();
}
if (!this.disabled) {
for (Endpoint<?> endpoint : this.endpoints) {
registerHandler(this.prefix + endpoint.getPath(), endpoint);
}
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@@ -86,20 +87,60 @@ public class EndpointHandlerMapping extends AbstractUrlHandlerMapping implements
getApplicationContext(), Endpoint.class).values());
}
/**
* Detects &#64;FrameworkEndpoint annotations in handler beans.
*
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#isHandler(java.lang.Class)
*/
@Override
protected Object lookupHandler(String urlPath, HttpServletRequest request)
throws Exception {
Object handler = super.lookupHandler(urlPath, request);
if (handler != null) {
Object endpoint = (handler instanceof HandlerExecutionChain ? ((HandlerExecutionChain) handler)
.getHandler() : handler);
HttpMethod method = HttpMethod.valueOf(request.getMethod());
if (endpoint instanceof Endpoint
&& supportsMethod(((Endpoint<?>) endpoint).methods(), method)) {
return endpoint;
protected boolean isHandler(Class<?> beanType) {
if (this.disabled) {
return false;
}
return AnnotationUtils.findAnnotation(beanType, FrameworkEndpoint.class) != null;
}
@Override
protected void registerHandlerMethod(Object handler, Method method,
RequestMappingInfo mapping) {
if (mapping == null) {
return;
}
Set<String> defaultPatterns = mapping.getPatternsCondition().getPatterns();
String[] patterns = new String[defaultPatterns.isEmpty() ? 1 : defaultPatterns
.size()];
String path = "";
Object bean = handler;
if (bean instanceof String) {
bean = getApplicationContext().getBean((String) handler);
}
if (bean instanceof Endpoint) {
Endpoint<?> endpoint = (Endpoint<?>) bean;
path = endpoint.getPath();
}
int i = 0;
String prefix = StringUtils.hasText(this.prefix) ? this.prefix + path : path;
if (defaultPatterns.isEmpty()) {
patterns[0] = prefix;
}
else {
for (String pattern : defaultPatterns) {
patterns[i] = prefix + pattern;
i++;
}
}
return null;
PatternsRequestCondition patternsInfo = new PatternsRequestCondition(patterns);
RequestMappingInfo modified = new RequestMappingInfo(patternsInfo,
mapping.getMethodsCondition(), mapping.getParamsCondition(),
mapping.getHeadersCondition(), mapping.getConsumesCondition(),
mapping.getProducesCondition(), mapping.getCustomCondition());
super.registerHandlerMethod(handler, method, modified);
}
/**
@@ -131,16 +172,4 @@ public class EndpointHandlerMapping extends AbstractUrlHandlerMapping implements
public List<Endpoint<?>> getEndpoints() {
return Collections.unmodifiableList(this.endpoints);
}
private boolean supportsMethod(HttpMethod[] supportedMethods,
HttpMethod requestedMethod) {
Assert.notNull(supportedMethods, "SupportMethods must not be null");
Assert.notNull(supportedMethods, "RequestedMethod must not be null");
for (HttpMethod supportedMethod : supportedMethods) {
if (supportedMethod.equals(requestedMethod)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2012-2013 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.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.stereotype.Component;
/**
* @author Dave Syer
*/
@Component
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FrameworkEndpoint {
}

View File

@@ -63,7 +63,7 @@ public class BasicErrorController implements ErrorController {
@RequestMapping(value = "${error.path:/error}", produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request) {
Map<String, Object> map = extract(new ServletRequestAttributes(request), false);
Map<String, Object> map = error(request);
return new ModelAndView(ERROR_KEY, map);
}

View File

@@ -25,7 +25,7 @@ import org.junit.After;
import org.junit.Test;
import org.springframework.boot.TestUtils;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.mvc.FrameworkEndpoint;
import org.springframework.boot.actuate.properties.ManagementServerProperties;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration;
@@ -200,13 +200,8 @@ public class EndpointWebMvcAutoConfigurationTests {
}
@Bean
public Endpoint<String> testEndpoint() {
return new AbstractEndpoint<String>("/endpoint", false, true) {
@Override
public String doInvoke() {
return "endpointoutput";
}
};
public TestEndpoint testEndpoint() {
return new TestEndpoint();
}
}
@@ -245,4 +240,20 @@ public class EndpointWebMvcAutoConfigurationTests {
}
@FrameworkEndpoint
public static class TestEndpoint extends AbstractEndpoint<String> {
public TestEndpoint() {
super("/endpoint", false, true);
}
@Override
@RequestMapping
@ResponseBody
public String invoke() {
return "endpointoutput";
}
}
}

View File

@@ -25,7 +25,6 @@ import org.springframework.boot.TestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.http.MediaType;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
@@ -49,16 +48,13 @@ public abstract class AbstractEndpointTests<T extends Endpoint<?>> {
private final String property;
private MediaType[] produces;
public AbstractEndpointTests(Class<?> configClass, Class<?> type, String path,
boolean sensitive, String property, MediaType... produces) {
boolean sensitive, String property) {
this.configClass = configClass;
this.type = type;
this.path = path;
this.sensitive = sensitive;
this.property = property;
this.produces = produces;
}
@Before
@@ -75,11 +71,6 @@ public abstract class AbstractEndpointTests<T extends Endpoint<?>> {
}
}
@Test
public void producesMediaType() {
assertThat(getEndpointBean().produces(), equalTo(this.produces));
}
@Test
public void getPath() throws Exception {
assertThat(getEndpointBean().getPath(), equalTo(this.path));

View File

@@ -17,11 +17,9 @@
package org.springframework.boot.actuate.endpoint;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.BeansEndpoint;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThat;
@@ -34,8 +32,7 @@ import static org.junit.Assert.assertThat;
public class BeansEndpointTests extends AbstractEndpointTests<BeansEndpoint> {
public BeansEndpointTests() {
super(Config.class, BeansEndpoint.class, "/beans", true, "endpoints.beans",
MediaType.APPLICATION_JSON);
super(Config.class, BeansEndpoint.class, "/beans", true, "endpoints.beans");
}
@Test

View File

@@ -1,74 +0,0 @@
/*
* Copyright 2012-2013 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.util.Collections;
import java.util.Map;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link EndpointHandlerAdapter}.
*
* @author Phillip Webb
*/
public class EndpointHandlerAdapterTests {
private EndpointHandlerAdapter adapter = new EndpointHandlerAdapter();
private MockHttpServletRequest request = new MockHttpServletRequest();
private MockHttpServletResponse response = new MockHttpServletResponse();
@Test
public void onlySupportsEndpoints() throws Exception {
assertTrue(this.adapter.supports(mock(Endpoint.class)));
assertFalse(this.adapter.supports(mock(Object.class)));
}
@Test
public void rendersJson() throws Exception {
this.adapter.handle(this.request, this.response,
new AbstractEndpoint<Map<String, String>>("/foo") {
@Override
protected Map<String, String> doInvoke() {
return Collections.singletonMap("hello", "world");
}
});
assertEquals("{\"hello\":\"world\"}", this.response.getContentAsString());
}
@Test
public void rendersString() throws Exception {
this.request.addHeader("Accept", "text/plain");
this.adapter.handle(this.request, this.response, new AbstractEndpoint<String>(
"/foo") {
@Override
protected String doInvoke() {
return "hello world";
}
});
assertEquals("hello world", this.response.getContentAsString());
}
}

View File

@@ -16,12 +16,18 @@
package org.springframework.boot.actuate.endpoint.mvc;
import java.util.Arrays;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.http.HttpMethod;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
@@ -33,21 +39,38 @@ import static org.junit.Assert.assertThat;
* Tests for {@link EndpointHandlerMapping}.
*
* @author Phillip Webb
* @author Dave Syer
*/
public class EndpointHandlerMappingTests {
private StaticApplicationContext context = new StaticApplicationContext();
private EndpointHandlerMapping mapping = new EndpointHandlerMapping();
private Method method;
@Before
public void init() throws Exception {
this.context.getDefaultListableBeanFactory().registerSingleton("mapping",
this.mapping);
this.mapping.setApplicationContext(this.context);
this.method = ReflectionUtils.findMethod(TestEndpoint.class, "invoke");
}
@Test
public void withoutPrefix() throws Exception {
TestEndpoint endpointA = new TestEndpoint("/a");
TestEndpoint endpointB = new TestEndpoint("/b");
EndpointHandlerMapping mapping = new EndpointHandlerMapping(Arrays.asList(
endpointA, endpointB));
mapping.afterPropertiesSet();
assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a"))
.getHandler(), equalTo((Object) endpointA));
assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/b"))
.getHandler(), equalTo((Object) endpointB));
assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/c")),
this.context.getDefaultListableBeanFactory().registerSingleton(
endpointA.getPath(), endpointA);
this.context.getDefaultListableBeanFactory().registerSingleton(
endpointB.getPath(), endpointB);
this.mapping.afterPropertiesSet();
assertThat(this.mapping.getHandler(new MockHttpServletRequest("GET", "/a"))
.getHandler(),
equalTo((Object) new HandlerMethod(endpointA, this.method)));
assertThat(this.mapping.getHandler(new MockHttpServletRequest("GET", "/b"))
.getHandler(),
equalTo((Object) new HandlerMethod(endpointB, this.method)));
assertThat(this.mapping.getHandler(new MockHttpServletRequest("GET", "/c")),
nullValue());
}
@@ -55,49 +78,63 @@ public class EndpointHandlerMappingTests {
public void withPrefix() throws Exception {
TestEndpoint endpointA = new TestEndpoint("/a");
TestEndpoint endpointB = new TestEndpoint("/b");
EndpointHandlerMapping mapping = new EndpointHandlerMapping(Arrays.asList(
endpointA, endpointB));
mapping.setPrefix("/a");
mapping.afterPropertiesSet();
assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a/a"))
.getHandler(), equalTo((Object) endpointA));
assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a/b"))
.getHandler(), equalTo((Object) endpointB));
assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a")),
this.context.getDefaultListableBeanFactory().registerSingleton(
endpointA.getPath(), endpointA);
this.context.getDefaultListableBeanFactory().registerSingleton(
endpointB.getPath(), endpointB);
this.mapping.setPrefix("/a");
this.mapping.afterPropertiesSet();
assertThat(this.mapping.getHandler(new MockHttpServletRequest("GET", "/a/a"))
.getHandler(),
equalTo((Object) new HandlerMethod(endpointA, this.method)));
assertThat(this.mapping.getHandler(new MockHttpServletRequest("GET", "/a/b"))
.getHandler(),
equalTo((Object) new HandlerMethod(endpointB, this.method)));
assertThat(this.mapping.getHandler(new MockHttpServletRequest("GET", "/a")),
nullValue());
}
@Test
@Test(expected = HttpRequestMethodNotSupportedException.class)
public void onlyGetHttpMethodForNonActionEndpoints() throws Exception {
TestEndpoint endpoint = new TestEndpoint("/a");
EndpointHandlerMapping mapping = new EndpointHandlerMapping(
Arrays.asList(endpoint));
mapping.afterPropertiesSet();
assertNotNull(mapping.getHandler(new MockHttpServletRequest("GET", "/a")));
assertNull(mapping.getHandler(new MockHttpServletRequest("POST", "/a")));
this.context.getDefaultListableBeanFactory().registerSingleton(
endpoint.getPath(), endpoint);
this.mapping.afterPropertiesSet();
assertNotNull(this.mapping.getHandler(new MockHttpServletRequest("GET", "/a")));
assertNull(this.mapping.getHandler(new MockHttpServletRequest("POST", "/a")));
}
@Test
public void postHttpMethodForActionEndpoints() throws Exception {
TestEndpoint endpoint = new TestActionEndpoint("/a");
this.context.getDefaultListableBeanFactory().registerSingleton(
endpoint.getPath(), endpoint);
this.mapping.afterPropertiesSet();
assertNotNull(this.mapping.getHandler(new MockHttpServletRequest("POST", "/a")));
}
@Test(expected = HttpRequestMethodNotSupportedException.class)
public void onlyPostHttpMethodForActionEndpoints() throws Exception {
TestEndpoint endpoint = new TestActionEndpoint("/a");
EndpointHandlerMapping mapping = new EndpointHandlerMapping(
Arrays.asList(endpoint));
mapping.afterPropertiesSet();
assertNull(mapping.getHandler(new MockHttpServletRequest("GET", "/a")));
assertNotNull(mapping.getHandler(new MockHttpServletRequest("POST", "/a")));
this.context.getDefaultListableBeanFactory().registerSingleton(
endpoint.getPath(), endpoint);
this.mapping.afterPropertiesSet();
assertNotNull(this.mapping.getHandler(new MockHttpServletRequest("POST", "/a")));
assertNull(this.mapping.getHandler(new MockHttpServletRequest("GET", "/a")));
}
@Test
public void disabled() throws Exception {
TestEndpoint endpointA = new TestEndpoint("/a");
EndpointHandlerMapping mapping = new EndpointHandlerMapping(
Arrays.asList(endpointA));
mapping.setDisabled(true);
mapping.afterPropertiesSet();
assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a")),
TestEndpoint endpoint = new TestEndpoint("/a");
this.context.getDefaultListableBeanFactory().registerSingleton(
endpoint.getPath(), endpoint);
this.mapping.setDisabled(true);
this.mapping.afterPropertiesSet();
assertThat(this.mapping.getHandler(new MockHttpServletRequest("GET", "/a")),
nullValue());
}
@FrameworkEndpoint
private static class TestEndpoint extends AbstractEndpoint<Object> {
public TestEndpoint(String path) {
@@ -105,12 +142,14 @@ public class EndpointHandlerMappingTests {
}
@Override
public Object doInvoke() {
@RequestMapping(method = RequestMethod.GET)
public Object invoke() {
return null;
}
}
@FrameworkEndpoint
private static class TestActionEndpoint extends TestEndpoint {
public TestActionEndpoint(String path) {
@@ -118,8 +157,9 @@ public class EndpointHandlerMappingTests {
}
@Override
public HttpMethod[] methods() {
return POST_HTTP_METHOD;
@RequestMapping(method = RequestMethod.POST)
public Object invoke() {
return null;
}
}

View File

@@ -26,8 +26,10 @@ 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.ManagementSecurityAutoConfiguration;
import org.springframework.boot.actuate.web.BasicErrorControllerIntegrationTests.TestConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -81,7 +83,8 @@ public class BasicErrorControllerIntegrationTests {
}
@Configuration
@EnableAutoConfiguration
@EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class,
ManagementSecurityAutoConfiguration.class })
public static class TestConfiguration {
// For manual testing

View File

@@ -19,7 +19,9 @@ package org.springframework.boot.actuate.web;
import org.junit.After;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.ManagementSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
@@ -77,13 +79,15 @@ public class BasicErrorControllerSpecialIntegrationTests {
}
@Configuration
@EnableAutoConfiguration
@EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class,
ManagementSecurityAutoConfiguration.class })
protected static class ParentConfiguration {
}
@Configuration
@EnableAutoConfiguration
@EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class,
ManagementSecurityAutoConfiguration.class })
@EnableWebMvc
protected static class WebMvcIncludedConfiguration {
// For manual testing
@@ -94,7 +98,19 @@ public class BasicErrorControllerSpecialIntegrationTests {
}
@Configuration
@EnableAutoConfiguration
@EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class,
ManagementSecurityAutoConfiguration.class })
protected static class VanillaConfiguration {
// For manual testing
public static void main(String[] args) {
SpringApplication.run(VanillaConfiguration.class, args);
}
}
@Configuration
@EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class,
ManagementSecurityAutoConfiguration.class })
protected static class ChildConfiguration {
// For manual testing