From 7f1264bb65bf2b5285fe81b20006b36b226b0426 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 18 Dec 2013 12:44:54 +0000 Subject: [PATCH] Replace @FrameworkEndpoint with MvcEndpoint interface --- .../EndpointWebMvcAutoConfiguration.java | 71 +++++--------- ...dpointWebMvcChildContextConfiguration.java | 20 ++-- .../ManagementSecurityAutoConfiguration.java | 3 +- .../AutoConfigurationReportEndpoint.java | 6 -- .../endpoint/mvc/EndpointHandlerMapping.java | 42 ++++----- .../endpoint/mvc/EnvironmentMvcEndpoint.java | 4 +- .../endpoint/mvc/FrameworkEndpoint.java | 34 ------- .../endpoint/mvc/GenericMvcEndpoint.java | 14 ++- .../{ => mvc}/ManagementErrorEndpoint.java | 15 ++- .../endpoint/mvc/MetricsMvcEndpoint.java | 4 +- .../actuate/endpoint/mvc/MvcEndpoint.java | 12 ++- .../actuate/endpoint/mvc/MvcEndpoints.java | 88 ++++++++++++++++++ .../endpoint/mvc/ShutdownMvcEndpoint.java | 1 - .../EndpointWebMvcAutoConfigurationTests.java | 27 ++++-- .../endpoint/EnvironmentEndpointTests.java | 1 - .../mvc/EndpointHandlerMappingTests.java | 91 +++++++++--------- .../mvc/EnvironmentMvcEndpointTests.java | 93 +++++++++++++++++++ 17 files changed, 336 insertions(+), 190 deletions(-) delete mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/FrameworkEndpoint.java rename spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/{ => mvc}/ManagementErrorEndpoint.java (90%) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoints.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java index 4eeddec69f0..fb381ea0064 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java @@ -17,8 +17,6 @@ package org.springframework.boot.actuate.autoconfigure; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -30,23 +28,20 @@ import javax.servlet.http.HttpServletResponse; import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; import org.springframework.boot.actuate.endpoint.MetricsEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; import org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.GenericMvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; import org.springframework.boot.actuate.endpoint.mvc.ShutdownMvcEndpoint; import org.springframework.boot.actuate.properties.ManagementServerProperties; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +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.ConditionalOnWebApplication; @@ -63,7 +58,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.stereotype.Component; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.servlet.DispatcherServlet; @@ -97,7 +91,8 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, @Bean @ConditionalOnMissingBean public EndpointHandlerMapping endpointHandlerMapping() { - EndpointHandlerMapping mapping = new EndpointHandlerMapping(); + EndpointHandlerMapping mapping = new EndpointHandlerMapping(mvcEndpoints() + .getEndpoints()); mapping.setDisabled(ManagementServerPort.get(this.applicationContext) != ManagementServerPort.SAME); mapping.setPrefix(this.managementServerProperties.getContextPath()); return mapping; @@ -133,48 +128,28 @@ public class EndpointWebMvcAutoConfiguration implements ApplicationContextAware, }; } - @Component - protected static class GenericEndpointPostProcessor implements - BeanDefinitionRegistryPostProcessor { - - private BeanDefinitionRegistry registry; - - private Map>, Class> endpointTypes = new HashMap>, Class>(); - - public GenericEndpointPostProcessor() { - this.endpointTypes.put(EnvironmentEndpoint.class, - EnvironmentMvcEndpoint.class); - this.endpointTypes.put(MetricsEndpoint.class, MetricsMvcEndpoint.class); - this.endpointTypes.put(ShutdownEndpoint.class, ShutdownMvcEndpoint.class); - } - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) - throws BeansException { - for (String name : beanFactory.getBeanNamesForType(Endpoint.class)) { - Class type = getTypeForEndpoint(beanFactory.getType(name)); - BeanDefinitionBuilder bean = BeanDefinitionBuilder - .genericBeanDefinition(type); - bean.addConstructorArgReference(name); - this.registry.registerBeanDefinition("mvc." + name, - bean.getBeanDefinition()); - } - } + @Bean + @ConditionalOnMissingBean + public MvcEndpoints mvcEndpoints() { + return new MvcEndpoints(); + } - protected Class getTypeForEndpoint(Class endpoint) { - Class type = GenericMvcEndpoint.class; - if (this.endpointTypes.containsKey(endpoint)) { - type = this.endpointTypes.get(endpoint); - } - return type; - } + @Bean + @ConditionalOnBean(EnvironmentEndpoint.class) + public EnvironmentMvcEndpoint environmentMvcEndpoint(EnvironmentEndpoint delegate) { + return new EnvironmentMvcEndpoint(delegate); + } - @Override - public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) - throws BeansException { - this.registry = registry; - } + @Bean + @ConditionalOnBean(MetricsEndpoint.class) + public MetricsMvcEndpoint metricsMvcEndpoint(MetricsEndpoint delegate) { + return new MetricsMvcEndpoint(delegate); + } + @Bean + @ConditionalOnBean(ShutdownEndpoint.class) + public ShutdownMvcEndpoint shutdownMvcEndpoint(ShutdownEndpoint delegate) { + return new ShutdownMvcEndpoint(delegate); } private void createChildManagementContext() { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java index 5d255ba854b..4fa3dee7f73 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java @@ -16,6 +16,9 @@ package org.springframework.boot.actuate.autoconfigure; +import java.util.HashSet; +import java.util.Set; + import javax.servlet.Filter; import org.springframework.beans.factory.BeanFactory; @@ -24,13 +27,16 @@ 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.ManagementErrorEndpoint; import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; +import org.springframework.boot.actuate.endpoint.mvc.ManagementErrorEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; import org.springframework.boot.actuate.properties.ManagementServerProperties; import org.springframework.boot.actuate.web.ErrorController; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.SearchStrategy; +import org.springframework.boot.autoconfigure.web.HttpMessageConverters; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.EmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; @@ -38,7 +44,6 @@ 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.client.RestTemplate; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.HandlerAdapter; import org.springframework.web.servlet.HandlerMapping; @@ -99,16 +104,19 @@ public class EndpointWebMvcChildContextConfiguration { } @Bean - public HandlerAdapter handlerAdapter() { + public HandlerAdapter handlerAdapter(HttpMessageConverters converters) { // TODO: maybe this needs more configuration for non-basic response use cases RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter(); - adapter.setMessageConverters(new RestTemplate().getMessageConverters()); + adapter.setMessageConverters(converters.getConverters()); return adapter; } @Bean - public HandlerMapping handlerMapping() { - EndpointHandlerMapping mapping = new EndpointHandlerMapping(); + public HandlerMapping handlerMapping(MvcEndpoints endpoints, + ListableBeanFactory beanFactory) { + Set set = new HashSet(endpoints.getEndpoints()); + set.addAll(beanFactory.getBeansOfType(MvcEndpoint.class).values()); + EndpointHandlerMapping mapping = new EndpointHandlerMapping(set); // In a child context we definitely want to see the parent endpoints mapping.setDetectHandlerMethodsInAncestorContexts(true); return mapping; diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfiguration.java index 7de8a357d9a..b476d6e1647 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementSecurityAutoConfiguration.java @@ -19,6 +19,7 @@ package org.springframework.boot.actuate.autoconfigure; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Set; import javax.annotation.PostConstruct; import javax.servlet.Filter; @@ -210,7 +211,7 @@ public class ManagementSecurityAutoConfiguration { return NO_PATHS; } - List> endpoints = endpointHandlerMapping.getEndpoints(); + Set> endpoints = endpointHandlerMapping.getEndpoints(); List paths = new ArrayList(endpoints.size()); for (Endpoint endpoint : endpoints) { if (endpoint.isSensitive() == secure) { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java index a10237be0b8..967373fc2b2 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AutoConfigurationReportEndpoint.java @@ -21,7 +21,6 @@ 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; @@ -32,8 +31,6 @@ 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; @@ -45,7 +42,6 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; * @author Dave Syer */ @ConfigurationProperties(name = "endpoints.autoconfig", ignoreUnknownFields = false) -@FrameworkEndpoint public class AutoConfigurationReportEndpoint extends AbstractEndpoint { @Autowired @@ -56,8 +52,6 @@ public class AutoConfigurationReportEndpoint extends AbstractEndpoint { } @Override - @RequestMapping - @ResponseBody public Report invoke() { return new Report(this.autoConfigurationReport); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java index 74b810dafcb..8884a7e34d2 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMapping.java @@ -17,17 +17,13 @@ package org.springframework.boot.actuate.endpoint.mvc; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.Collection; +import java.util.HashSet; 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.core.annotation.AnnotationUtils; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.servlet.HandlerMapping; @@ -56,9 +52,9 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl * */ public class EndpointHandlerMapping extends RequestMappingHandlerMapping implements - InitializingBean, ApplicationContextAware { + ApplicationContextAware { - private List> endpoints; + private Set endpoints; private String prefix = ""; @@ -67,8 +63,10 @@ public class EndpointHandlerMapping extends RequestMappingHandlerMapping impleme /** * Create a new {@link EndpointHandlerMapping} instance. All {@link Endpoint}s will be * detected from the {@link ApplicationContext}. + * @param endpoints */ - public EndpointHandlerMapping() { + public EndpointHandlerMapping(Collection endpoints) { + this.endpoints = new HashSet(endpoints); // By default the static resource handler mapping is LOWEST_PRECEDENCE - 1 setOrder(LOWEST_PRECEDENCE - 2); } @@ -76,28 +74,20 @@ public class EndpointHandlerMapping extends RequestMappingHandlerMapping impleme @Override public void afterPropertiesSet() { super.afterPropertiesSet(); - if (this.endpoints == null) { - this.endpoints = findEndpointBeans(); + if (!this.disabled) { + for (MvcEndpoint endpoint : this.endpoints) { + detectHandlerMethods(endpoint); + } } } - @SuppressWarnings({ "rawtypes", "unchecked" }) - private List> findEndpointBeans() { - return new ArrayList(BeanFactoryUtils.beansOfTypeIncludingAncestors( - getApplicationContext(), Endpoint.class).values()); - } - /** - * Detects @FrameworkEndpoint annotations in handler beans. - * - * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#isHandler(java.lang.Class) + * Since all handler beans are passed into the constructor there is no need to detect + * anything here */ @Override protected boolean isHandler(Class beanType) { - if (this.disabled) { - return false; - } - return AnnotationUtils.findAnnotation(beanType, FrameworkEndpoint.class) != null; + return false; } @Override @@ -169,7 +159,7 @@ public class EndpointHandlerMapping extends RequestMappingHandlerMapping impleme /** * Return the endpoints */ - public List> getEndpoints() { - return Collections.unmodifiableList(this.endpoints); + public Set> getEndpoints() { + return this.endpoints; } } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java index 09207d45764..1eacc19b752 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpoint.java @@ -22,13 +22,13 @@ import org.springframework.core.env.Environment; 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.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; /** * @author Dave Syer */ -@FrameworkEndpoint public class EnvironmentMvcEndpoint extends GenericMvcEndpoint implements EnvironmentAware { @@ -38,7 +38,7 @@ public class EnvironmentMvcEndpoint extends GenericMvcEndpoint implements super(delegate); } - @RequestMapping("/{name:.*}") + @RequestMapping(value = "/{name:.*}", method = RequestMethod.GET) @ResponseBody public Object value(@PathVariable String name) { String result = this.environment.getProperty(name); diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/FrameworkEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/FrameworkEndpoint.java deleted file mode 100644 index f1a37e72770..00000000000 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/FrameworkEndpoint.java +++ /dev/null @@ -1,34 +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.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 { - -} \ No newline at end of file diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/GenericMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/GenericMvcEndpoint.java index c8fa2e436bc..6b56625f9fa 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/GenericMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/GenericMvcEndpoint.java @@ -24,7 +24,6 @@ import org.springframework.web.bind.annotation.ResponseBody; /** * @author Dave Syer */ -@FrameworkEndpoint public class GenericMvcEndpoint implements MvcEndpoint { private Endpoint delegate; @@ -44,4 +43,17 @@ public class GenericMvcEndpoint implements MvcEndpoint { return this.delegate.getPath(); } + @Override + public boolean isSensitive() { + return this.delegate.isSensitive(); + } + + @Override + public Class getEndpointType() { + @SuppressWarnings("unchecked") + Class> type = (Class>) this.delegate + .getClass(); + return type; + } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ManagementErrorEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ManagementErrorEndpoint.java similarity index 90% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ManagementErrorEndpoint.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ManagementErrorEndpoint.java index 441c59d147f..5b9e120f9cb 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ManagementErrorEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ManagementErrorEndpoint.java @@ -14,12 +14,10 @@ * limitations under the License. */ -package org.springframework.boot.actuate.endpoint; +package org.springframework.boot.actuate.endpoint.mvc; import java.util.Map; -import org.springframework.boot.actuate.endpoint.mvc.FrameworkEndpoint; -import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; import org.springframework.boot.actuate.web.ErrorController; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.web.bind.annotation.RequestMapping; @@ -35,7 +33,6 @@ import org.springframework.web.context.request.RequestContextHolder; * @author Dave Syer */ @ConfigurationProperties(name = "error") -@FrameworkEndpoint public class ManagementErrorEndpoint implements MvcEndpoint { private final ErrorController controller; @@ -57,4 +54,14 @@ public class ManagementErrorEndpoint implements MvcEndpoint { public String getPath() { return this.path; } + + @Override + public boolean isSensitive() { + return false; + } + + @Override + public Class getEndpointType() { + return null; + } } \ No newline at end of file diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpoint.java index 14f72486f9a..26800b5b892 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MetricsMvcEndpoint.java @@ -20,13 +20,13 @@ import org.springframework.boot.actuate.endpoint.MetricsEndpoint; 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.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; /** * @author Dave Syer */ -@FrameworkEndpoint public class MetricsMvcEndpoint extends GenericMvcEndpoint { private MetricsEndpoint delegate; @@ -36,7 +36,7 @@ public class MetricsMvcEndpoint extends GenericMvcEndpoint { this.delegate = delegate; } - @RequestMapping("/{name:.*}") + @RequestMapping(value = "/{name:.*}", method = RequestMethod.GET) @ResponseBody public Object value(@PathVariable String name) { Object value = this.delegate.invoke().get(name); diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoint.java index 8ea12bbea11..19225dcc51a 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoint.java @@ -16,11 +16,19 @@ package org.springframework.boot.actuate.endpoint.mvc; +import org.springframework.boot.actuate.endpoint.Endpoint; + /** + * A strategy for the MVC layer on top of an {@link Endpoint}. Implementations are allowed + * to use @RequestMapping and the full Spring MVC machinery, but should not + * use @Controller or @RequestMapping at the type level (since + * that would lead to a double mapping of paths, once by the regular MVC handler mappings + * and once by the {@link EndpointHandlerMapping}). + * * @author Dave Syer */ -public interface MvcEndpoint { +public interface MvcEndpoint extends Endpoint { - String getPath(); + Class getEndpointType(); } \ No newline at end of file diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoints.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoints.java new file mode 100644 index 00000000000..3e1801b45c8 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/MvcEndpoints.java @@ -0,0 +1,88 @@ +/* + * 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.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.beans.BeansException; +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.stereotype.Component; + +/** + * A registry for all {@link MvcEndpoint} beans, and a factory for a set of generic ones + * wrapping existing {@link Endpoint} instances that are not already exposed as MVC + * endpoints. + * + * @author Dave Syer + */ +@Component +public class MvcEndpoints implements ApplicationContextAware, InitializingBean { + + private ApplicationContext applicationContext; + + private Set endpoints = new HashSet(); + + private Set> customTypes; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + public void afterPropertiesSet() throws Exception { + Collection existing = this.applicationContext.getBeansOfType( + MvcEndpoint.class).values(); + this.endpoints.addAll(existing); + this.customTypes = findEndpointClasses(existing); + @SuppressWarnings("rawtypes") + Collection delegates = this.applicationContext.getBeansOfType( + Endpoint.class).values(); + for (Endpoint endpoint : delegates) { + if (isGenericEndpoint(endpoint.getClass())) { + this.endpoints.add(new GenericMvcEndpoint(endpoint)); + } + } + } + + private Set> findEndpointClasses(Collection existing) { + Set> types = new HashSet>(); + for (MvcEndpoint endpoint : existing) { + Class type = endpoint.getEndpointType(); + if (type != null) { + types.add(type); + } + } + return types; + } + + public Set getEndpoints() { + return this.endpoints; + } + + private boolean isGenericEndpoint(Class type) { + return !this.customTypes.contains(type) + && !MvcEndpoint.class.isAssignableFrom(type); + } + +} \ No newline at end of file diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ShutdownMvcEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ShutdownMvcEndpoint.java index d1dc2f31cb7..0877ed6885e 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ShutdownMvcEndpoint.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/mvc/ShutdownMvcEndpoint.java @@ -24,7 +24,6 @@ import org.springframework.web.bind.annotation.ResponseBody; /** * @author Dave Syer */ -@FrameworkEndpoint public class ShutdownMvcEndpoint extends GenericMvcEndpoint { public ShutdownMvcEndpoint(ShutdownEndpoint delegate) { diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java index 521a4f587d3..5f7ece55b54 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java @@ -24,8 +24,8 @@ import java.nio.charset.Charset; 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.mvc.FrameworkEndpoint; +import org.springframework.boot.actuate.endpoint.Endpoint; +import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; import org.springframework.boot.actuate.properties.ManagementServerProperties; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; @@ -240,20 +240,29 @@ public class EndpointWebMvcAutoConfigurationTests { } - @FrameworkEndpoint - public static class TestEndpoint extends AbstractEndpoint { + public static class TestEndpoint implements MvcEndpoint { - public TestEndpoint() { - super("/endpoint", false, true); - } - - @Override @RequestMapping @ResponseBody public String invoke() { return "endpointoutput"; } + @Override + public String getPath() { + return "/endpoint"; + } + + @Override + public boolean isSensitive() { + return true; + } + + @Override + public Class getEndpointType() { + return Endpoint.class; + } + } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpointTests.java index 133e897f68a..081cf162be8 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpointTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EnvironmentEndpointTests.java @@ -17,7 +17,6 @@ package org.springframework.boot.actuate.endpoint; import org.junit.Test; -import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java index 741fea047c7..d40f34b9c87 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EndpointHandlerMappingTests.java @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.endpoint.mvc; import java.lang.reflect.Method; +import java.util.Arrays; import org.junit.Before; import org.junit.Test; @@ -44,14 +45,10 @@ import static org.junit.Assert.assertThat; 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(TestMvcEndpoint.class, "invoke"); } @@ -59,18 +56,17 @@ public class EndpointHandlerMappingTests { public void withoutPrefix() throws Exception { TestMvcEndpoint endpointA = new TestMvcEndpoint(new TestEndpoint("/a")); TestMvcEndpoint endpointB = new TestMvcEndpoint(new TestEndpoint("/b")); - 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")) + EndpointHandlerMapping mapping = new EndpointHandlerMapping(Arrays.asList( + endpointA, endpointB)); + mapping.setApplicationContext(this.context); + mapping.afterPropertiesSet(); + assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a")) .getHandler(), equalTo((Object) new HandlerMethod(endpointA, this.method))); - assertThat(this.mapping.getHandler(new MockHttpServletRequest("GET", "/b")) + assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/b")) .getHandler(), equalTo((Object) new HandlerMethod(endpointB, this.method))); - assertThat(this.mapping.getHandler(new MockHttpServletRequest("GET", "/c")), + assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/c")), nullValue()); } @@ -78,59 +74,62 @@ public class EndpointHandlerMappingTests { public void withPrefix() throws Exception { TestMvcEndpoint endpointA = new TestMvcEndpoint(new TestEndpoint("/a")); TestMvcEndpoint endpointB = new TestMvcEndpoint(new TestEndpoint("/b")); - 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")) + EndpointHandlerMapping mapping = new EndpointHandlerMapping(Arrays.asList( + endpointA, endpointB)); + mapping.setApplicationContext(this.context); + mapping.setPrefix("/a"); + mapping.afterPropertiesSet(); + assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a/a")) .getHandler(), equalTo((Object) new HandlerMethod(endpointA, this.method))); - assertThat(this.mapping.getHandler(new MockHttpServletRequest("GET", "/a/b")) + assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a/b")) .getHandler(), equalTo((Object) new HandlerMethod(endpointB, this.method))); - assertThat(this.mapping.getHandler(new MockHttpServletRequest("GET", "/a")), + assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a")), nullValue()); } @Test(expected = HttpRequestMethodNotSupportedException.class) public void onlyGetHttpMethodForNonActionEndpoints() throws Exception { - TestMvcEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("/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"))); + TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("/a")); + EndpointHandlerMapping mapping = new EndpointHandlerMapping( + Arrays.asList(endpoint)); + mapping.setApplicationContext(this.context); + mapping.afterPropertiesSet(); + assertNotNull(mapping.getHandler(new MockHttpServletRequest("GET", "/a"))); + assertNull(mapping.getHandler(new MockHttpServletRequest("POST", "/a"))); } @Test public void postHttpMethodForActionEndpoints() throws Exception { - TestMvcEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("/a")); - this.context.getDefaultListableBeanFactory().registerSingleton( - endpoint.getPath(), endpoint); - this.mapping.afterPropertiesSet(); - assertNotNull(this.mapping.getHandler(new MockHttpServletRequest("POST", "/a"))); + TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("/a")); + EndpointHandlerMapping mapping = new EndpointHandlerMapping( + Arrays.asList(endpoint)); + mapping.setApplicationContext(this.context); + mapping.afterPropertiesSet(); + assertNotNull(mapping.getHandler(new MockHttpServletRequest("POST", "/a"))); } @Test(expected = HttpRequestMethodNotSupportedException.class) public void onlyPostHttpMethodForActionEndpoints() throws Exception { - TestMvcEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("/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"))); + TestActionEndpoint endpoint = new TestActionEndpoint(new TestEndpoint("/a")); + EndpointHandlerMapping mapping = new EndpointHandlerMapping( + Arrays.asList(endpoint)); + mapping.setApplicationContext(this.context); + mapping.afterPropertiesSet(); + assertNotNull(mapping.getHandler(new MockHttpServletRequest("POST", "/a"))); + assertNull(mapping.getHandler(new MockHttpServletRequest("GET", "/a"))); } @Test public void disabled() throws Exception { TestMvcEndpoint endpoint = new TestMvcEndpoint(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")), + EndpointHandlerMapping mapping = new EndpointHandlerMapping( + Arrays.asList(endpoint)); + mapping.setDisabled(true); + mapping.setApplicationContext(this.context); + mapping.afterPropertiesSet(); + assertThat(mapping.getHandler(new MockHttpServletRequest("GET", "/a")), nullValue()); } @@ -141,14 +140,12 @@ public class EndpointHandlerMappingTests { } @Override - @RequestMapping(method = RequestMethod.GET) public Object invoke() { return null; } } - @FrameworkEndpoint private static class TestMvcEndpoint extends GenericMvcEndpoint { public TestMvcEndpoint(TestEndpoint delegate) { @@ -157,8 +154,7 @@ public class EndpointHandlerMappingTests { } - @FrameworkEndpoint - private static class TestActionEndpoint extends TestMvcEndpoint { + private static class TestActionEndpoint extends GenericMvcEndpoint { public TestActionEndpoint(TestEndpoint delegate) { super(delegate); @@ -169,6 +165,7 @@ public class EndpointHandlerMappingTests { public Object invoke() { return null; } + } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java new file mode 100644 index 00000000000..79a9f4cd6e2 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/mvc/EnvironmentMvcEndpointTests.java @@ -0,0 +1,93 @@ +/* + * 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 org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.TestUtils; +import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration; +import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; +import org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpointTests.TestConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +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.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; + +/** + * @author Dave Syer + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = { TestConfiguration.class }) +@WebAppConfiguration +public class EnvironmentMvcEndpointTests { + + @Autowired + private WebApplicationContext context; + + private MockMvc mvc; + + @Before + public void setUp() { + this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); + TestUtils.addEnviroment((ConfigurableApplicationContext) this.context, "foo:bar"); + } + + @Test + public void home() throws Exception { + this.mvc.perform(get("/env")).andExpect(status().isOk()) + .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"))); + } + + @Import(EndpointWebMvcAutoConfiguration.class) + @EnableWebMvc + @Configuration + public static class TestConfiguration { + + @Bean + public EnvironmentEndpoint endpoint() { + return new EnvironmentEndpoint(); + } + + @Bean + public EnvironmentMvcEndpoint mvcEndpoint() { + return new EnvironmentMvcEndpoint(endpoint()); + } + + } + +}