From 08d8cb8efd77b7e14735074a2cd213146475275a Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Mon, 24 Feb 2014 13:34:07 +0000 Subject: [PATCH] Add /mappings endpoint Lists AbstractUrlMappings and AbstractHandlerMethodMappings in a loose Map structure. E.g. { "/**/favicon.ico": { "bean": "faviconHandlerMapping" }, "/**": { "bean": "resourceHandlerMapping" }, "/webjars/**": { "bean": "resourceHandlerMapping" }, "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}": { "bean": "requestMappingHandlerMapping", "method": "public java.util.Map org.springframework.boot.actuate.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)" ... } Fixes gh-378 --- .../EndpointAutoConfiguration.java | 16 ++ .../endpoint/RequestMappingEndpoint.java | 141 ++++++++++++++++++ .../EndpointAutoConfigurationTests.java | 2 + .../endpoint/RequestMappingEndpointTests.java | 107 +++++++++++++ 4 files changed, 266 insertions(+) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpoint.java create mode 100644 spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpointTests.java diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java index e0a38a842f..2f07cdc7aa 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfiguration.java @@ -34,6 +34,7 @@ import org.springframework.boot.actuate.endpoint.HealthEndpoint; import org.springframework.boot.actuate.endpoint.InfoEndpoint; import org.springframework.boot.actuate.endpoint.MetricsEndpoint; import org.springframework.boot.actuate.endpoint.PublicMetrics; +import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.boot.actuate.endpoint.TraceEndpoint; import org.springframework.boot.actuate.endpoint.VanillaPublicMetrics; @@ -47,6 +48,7 @@ import org.springframework.boot.actuate.trace.TraceRepository; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; 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.SearchStrategy; import org.springframework.boot.bind.PropertiesConfigurationFactory; @@ -56,6 +58,7 @@ import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PropertiesLoaderUtils; +import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; /** * {@link EnableAutoConfiguration Auto-configuration} for common management @@ -160,6 +163,19 @@ public class EndpointAutoConfiguration { return new ShutdownEndpoint(); } + @Configuration + @ConditionalOnClass(AbstractHandlerMethodMapping.class) + protected static class RequestMappingEndpointConfiguration { + + @Bean + @ConditionalOnMissingBean + public RequestMappingEndpoint requestMappingEndpoint() { + RequestMappingEndpoint endpoint = new RequestMappingEndpoint(); + return endpoint; + } + + } + @Bean @ConditionalOnMissingBean public ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint() { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpoint.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpoint.java new file mode 100644 index 0000000000..188a396355 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpoint.java @@ -0,0 +1,141 @@ +/* + * 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.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; +import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; + +/** + * @author Dave Syer + */ +public class RequestMappingEndpoint extends AbstractEndpoint> + implements ApplicationContextAware { + + private List handlerMappings = Collections.emptyList(); + + private List> methodMappings = Collections + .emptyList(); + + private ApplicationContext applicationContext; + + public RequestMappingEndpoint() { + super("mappings"); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + this.applicationContext = applicationContext; + } + + /** + * @param handlerMappings the mappings to set + */ + public void setHandlerMappings(List handlerMappings) { + this.handlerMappings = handlerMappings; + } + + /** + * @param methodMappings the method mappings to set + */ + public void setMethodMappings(List> methodMappings) { + this.methodMappings = methodMappings; + } + + @Override + public Map invoke() { + Map result = new LinkedHashMap(); + extractHandlerMappings(this.handlerMappings, result); + extractHandlerMappings(this.applicationContext, result); + extractMethodMappings(this.methodMappings, result); + extractMethodMappings(this.applicationContext, result); + return result; + } + + protected void extractMethodMappings(ApplicationContext applicationContext, + Map result) { + if (applicationContext != null) { + Map> mappings = new HashMap>(); + for (String name : applicationContext.getBeansOfType( + AbstractHandlerMethodMapping.class).keySet()) { + mappings.put(name, applicationContext.getBean(name, + AbstractHandlerMethodMapping.class)); + @SuppressWarnings("unchecked") + Map methods = applicationContext.getBean(name, + AbstractHandlerMethodMapping.class).getHandlerMethods(); + for (Object key : methods.keySet()) { + Map map = new LinkedHashMap(); + map.put("bean", name); + map.put("method", methods.get(key).toString()); + result.put(key.toString(), map); + } + } + } + } + + protected void extractHandlerMappings(ApplicationContext applicationContext, + Map result) { + if (applicationContext != null) { + Map mappings = applicationContext + .getBeansOfType(AbstractUrlHandlerMapping.class); + for (String name : mappings.keySet()) { + AbstractUrlHandlerMapping mapping = mappings.get(name); + Map handlers = mapping.getHandlerMap(); + for (String key : handlers.keySet()) { + result.put(key, Collections.singletonMap("bean", name)); + } + } + } + } + + protected void extractHandlerMappings( + Collection handlerMappings, + Map result) { + for (AbstractUrlHandlerMapping mapping : handlerMappings) { + Map handlers = mapping.getHandlerMap(); + for (String key : handlers.keySet()) { + Object handler = handlers.get(key); + result.put(key, + Collections.singletonMap("type", handler.getClass().getName())); + } + } + } + + protected void extractMethodMappings( + Collection> methodMappings, + Map result) { + for (AbstractHandlerMethodMapping mapping : methodMappings) { + Map methods = mapping.getHandlerMethods(); + for (Object key : methods.keySet()) { + result.put(key.toString(), + Collections.singletonMap("method", methods.get(key).toString())); + } + } + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java index 09b6af56f6..c8dd1ee5e6 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointAutoConfigurationTests.java @@ -28,6 +28,7 @@ import org.springframework.boot.actuate.endpoint.EnvironmentEndpoint; import org.springframework.boot.actuate.endpoint.HealthEndpoint; import org.springframework.boot.actuate.endpoint.InfoEndpoint; import org.springframework.boot.actuate.endpoint.MetricsEndpoint; +import org.springframework.boot.actuate.endpoint.RequestMappingEndpoint; import org.springframework.boot.actuate.endpoint.ShutdownEndpoint; import org.springframework.boot.actuate.endpoint.TraceEndpoint; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; @@ -75,6 +76,7 @@ public class EndpointAutoConfigurationTests { assertNotNull(this.context.getBean(MetricsEndpoint.class)); assertNotNull(this.context.getBean(ShutdownEndpoint.class)); assertNotNull(this.context.getBean(TraceEndpoint.class)); + assertNotNull(this.context.getBean(RequestMappingEndpoint.class)); } @Test diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpointTests.java new file mode 100644 index 0000000000..3e0de15719 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/RequestMappingEndpointTests.java @@ -0,0 +1,107 @@ +/* + * 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.Arrays; +import java.util.Collections; +import java.util.Map; + +import org.junit.Test; +import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; +import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter; +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; +import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; +import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Dave Syer + */ +public class RequestMappingEndpointTests { + + private RequestMappingEndpoint endpoint = new RequestMappingEndpoint(); + + @Test + public void concreteUrlMappings() { + SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); + mapping.setUrlMap(Collections.singletonMap("/foo", new Object())); + mapping.setApplicationContext(new StaticApplicationContext()); + mapping.initApplicationContext(); + this.endpoint.setHandlerMappings(Collections + . singletonList(mapping)); + Map result = this.endpoint.invoke(); + assertEquals(1, result.size()); + @SuppressWarnings("unchecked") + Map map = (Map) result.get("/foo"); + assertEquals("java.lang.Object", map.get("type")); + } + + @Test + public void beanUrlMappings() { + StaticApplicationContext context = new StaticApplicationContext(); + SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); + mapping.setUrlMap(Collections.singletonMap("/foo", new Object())); + mapping.setApplicationContext(context); + mapping.initApplicationContext(); + context.getDefaultListableBeanFactory().registerSingleton("mapping", mapping); + this.endpoint.setApplicationContext(context); + Map result = this.endpoint.invoke(); + assertEquals(1, result.size()); + @SuppressWarnings("unchecked") + Map map = (Map) result.get("/foo"); + assertEquals("mapping", map.get("bean")); + } + + @Test + public void beanMethodMappings() { + StaticApplicationContext context = new StaticApplicationContext(); + EndpointHandlerMapping mapping = new EndpointHandlerMapping( + Arrays.asList(new EndpointMvcAdapter(new DumpEndpoint()))); + mapping.setApplicationContext(new StaticApplicationContext()); + mapping.afterPropertiesSet(); + context.getDefaultListableBeanFactory().registerSingleton("mapping", mapping); + this.endpoint.setApplicationContext(context); + Map result = this.endpoint.invoke(); + assertEquals(1, result.size()); + assertTrue(result.keySet().iterator().next().contains("/dump")); + @SuppressWarnings("unchecked") + Map handler = (Map) result.values().iterator() + .next(); + assertTrue(handler.containsKey("method")); + } + + @Test + public void concreteMethodMappings() { + EndpointHandlerMapping mapping = new EndpointHandlerMapping( + Arrays.asList(new EndpointMvcAdapter(new DumpEndpoint()))); + mapping.setApplicationContext(new StaticApplicationContext()); + mapping.afterPropertiesSet(); + this.endpoint.setMethodMappings(Collections + .> singletonList(mapping)); + Map result = this.endpoint.invoke(); + assertEquals(1, result.size()); + assertTrue(result.keySet().iterator().next().contains("/dump")); + @SuppressWarnings("unchecked") + Map handler = (Map) result.values().iterator() + .next(); + assertTrue(handler.containsKey("method")); + } + +}