diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml index 2d793ae26686..de859f77c1c7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml @@ -435,6 +435,11 @@ spring-restdocs-mockmvc test + + org.springframework.restdocs + spring-restdocs-webtestclient + test + org.springframework.security spring-security-test diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/endpoints/mappings.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/endpoints/mappings.adoc new file mode 100644 index 000000000000..2ef662f1a533 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/endpoints/mappings.adoc @@ -0,0 +1,80 @@ +[[mappings]] += Mappings (`mappings`) + +The `mappings` endpoint provides information about the application's request mappings. + + + +[[mappings-retrieving]] +== Retrieving the Mappings + +To retrieve the mappings, make a `GET` request to `/actuator/mappings`, as shown in the +following curl-based example: + +include::{snippets}mappings/curl-request.adoc[] + +The resulting response is similar to the following: + +include::{snippets}mappings/http-response.adoc[] + + + +[[mappings-retrieving-response-structure]] +=== Response Structure + +The response contains details of the application's mappings. The items found in the +response depend on the type of web application (reactive or Servlet-based). The +following table describes the structure of the common elements of the response: + +[cols="2,1,3"] +include::{snippets}mappings/response-fields.adoc[] + +The entries that may be found in `contexts.*.mappings` are described in the +following sections. + + +[[mappings-retrieving-response-structure-dispatcher-servlets]] +=== Dispatcher Servlets Response Structure + +When using Spring MVC, the response contains details of any `DispatcherServlet` +request mappings beneath `contexts.*.mappings.dispatcherServlets`. The following +table describes the structure of this section of the response: + +[cols="2,1,3"] +include::{snippets}mappings/response-fields-dispatcher-servlets.adoc[] + + + +[[mappings-retrieving-response-structure-servlets]] +=== Servlets Response Structure + +When using the Servlet stack, the response contains details of any `Servlet` mappings +beneath `contexts.*.mappings.servlets`. The following table describes the structure of +this section of the response: + +[cols="2,1,3"] +include::{snippets}mappings/response-fields-servlets.adoc[] + + + +[[mappings-retrieving-response-structure-servlet-filters]] +=== Servlet Filters Response Structure + +When using the Servlet stack, the response contains details of any `Filter` mappings +beneath `contexts.*.mappings.servletFilters`. The following table describes the +structure of this section of the response: + +[cols="2,1,3"] +include::{snippets}mappings/response-fields-servlet-filters.adoc[] + + + +[[mappings-retrieving-response-structure-dispatcher-servlets]] +=== Dispatcher Handlers Response Structure + +When using Spring WebFlux, the response contains details of any `DispatcherHandler` +request mappings beneath `contexts.*.mappings.dispatcherHandlers`. The following +table describes the structure of this section of the response: + +[cols="2,1,3"] +include::{snippets}mappings/response-fields-dispatcher-handlers.adoc[] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/index.adoc b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/index.adoc index 14af3216ef0e..c0d6a3ac0423 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/index.adoc +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/asciidoc/index.adoc @@ -61,6 +61,7 @@ include::endpoints/info.adoc[leveloffset=+1] include::endpoints/liquibase.adoc[leveloffset=+1] include::endpoints/logfile.adoc[leveloffset=+1] include::endpoints/loggers.adoc[leveloffset=+1] +include::endpoints/mappings.adoc[leveloffset=+1] include::endpoints/metrics.adoc[leveloffset=+1] include::endpoints/prometheus.adoc[leveloffset=+1] include::endpoints/scheduledtasks.adoc[leveloffset=+1] diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/MappingsEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/MappingsEndpointAutoConfiguration.java new file mode 100644 index 000000000000..52aa8e73fa33 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/MappingsEndpointAutoConfiguration.java @@ -0,0 +1,102 @@ +/* + * Copyright 2012-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.web; + +import java.util.Collection; +import java.util.Collections; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; +import org.springframework.boot.actuate.web.MappingDescriptionProvider; +import org.springframework.boot.actuate.web.MappingsEndpoint; +import org.springframework.boot.actuate.web.reactive.DispatcherHandlersMappingDescriptionProvider; +import org.springframework.boot.actuate.web.servlet.DispatcherServletsMappingDescriptionProvider; +import org.springframework.boot.actuate.web.servlet.FiltersMappingDescriptionProvider; +import org.springframework.boot.actuate.web.servlet.ServletsMappingDescriptionProvider; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.DispatcherHandler; +import org.springframework.web.servlet.DispatcherServlet; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for {@link MappingsEndpoint}. + * + * @author Andy Wilkinson + * @since 2.0.0 + */ +@ManagementContextConfiguration +public class MappingsEndpointAutoConfiguration { + + @Bean + @ConditionalOnEnabledEndpoint + public MappingsEndpoint mappingsEndpoint(ApplicationContext applicationContext, + ObjectProvider> descriptionProviders) { + return new MappingsEndpoint( + descriptionProviders.getIfAvailable(Collections::emptyList), + applicationContext); + } + + @Configuration + @ConditionalOnWebApplication(type = Type.SERVLET) + static class ServletWebConfiguration { + + @Bean + ServletsMappingDescriptionProvider servletMappingDescriptionProvider() { + return new ServletsMappingDescriptionProvider(); + } + + @Bean + FiltersMappingDescriptionProvider filterMappingDescriptionProvider() { + return new FiltersMappingDescriptionProvider(); + } + + @Configuration + @ConditionalOnClass(DispatcherServlet.class) + @ConditionalOnBean(DispatcherServlet.class) + static class SpringMvcConfiguration { + + @Bean + DispatcherServletsMappingDescriptionProvider dispatcherServletMappingDescriptionProvider( + ApplicationContext applicationContext) { + return new DispatcherServletsMappingDescriptionProvider(); + } + + } + + } + + @Configuration + @ConditionalOnWebApplication(type = Type.REACTIVE) + @ConditionalOnClass(DispatcherHandler.class) + @ConditionalOnBean(DispatcherHandler.class) + static class ReactiveWebConfiguration { + + @Bean + public DispatcherHandlersMappingDescriptionProvider dispatcherHandlerMappingDescriptionProvider( + ApplicationContext applicationContext) { + return new DispatcherHandlersMappingDescriptionProvider(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/RequestMappingEndpoint.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/RequestMappingEndpoint.java deleted file mode 100644 index 5ac586493201..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/RequestMappingEndpoint.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.web.servlet; - -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import org.springframework.aop.support.AopUtils; -import org.springframework.beans.BeansException; -import org.springframework.boot.actuate.endpoint.annotation.Endpoint; -import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; -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; - -/** - * {@link Endpoint} to expose Spring MVC mappings. - * - * @author Dave Syer - * @author Andy Wilkinson - * @since 2.0.0 - */ -@Endpoint(id = "mappings") -public class RequestMappingEndpoint implements ApplicationContextAware { - - private List handlerMappings = Collections.emptyList(); - - private List> methodMappings = Collections - .emptyList(); - - private ApplicationContext applicationContext; - - @Override - public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { - this.applicationContext = applicationContext; - } - - /** - * Set the handler mappings. - * @param handlerMappings the handler mappings - */ - public void setHandlerMappings(List handlerMappings) { - this.handlerMappings = handlerMappings; - } - - /** - * Set the method mappings. - * @param methodMappings the method mappings - */ - public void setMethodMappings(List> methodMappings) { - this.methodMappings = methodMappings; - } - - @ReadOperation - public Map mappings() { - Map result = new LinkedHashMap<>(); - extractHandlerMappings(this.handlerMappings, result); - extractHandlerMappings(this.applicationContext, result); - extractMethodMappings(this.methodMappings, result); - extractMethodMappings(this.applicationContext, result); - return result; - } - - @SuppressWarnings("rawtypes") - protected void extractMethodMappings(ApplicationContext applicationContext, - Map result) { - if (applicationContext != null) { - for (Entry bean : applicationContext - .getBeansOfType(AbstractHandlerMethodMapping.class).entrySet()) { - @SuppressWarnings("unchecked") - Map methods = bean.getValue().getHandlerMethods(); - for (Entry method : methods.entrySet()) { - Map map = new LinkedHashMap<>(); - map.put("bean", bean.getKey()); - map.put("method", method.getValue().toString()); - result.put(method.getKey().toString(), map); - } - } - } - } - - protected void extractHandlerMappings(ApplicationContext applicationContext, - Map result) { - if (applicationContext != null) { - Map mappings = applicationContext - .getBeansOfType(AbstractUrlHandlerMapping.class); - for (Entry mapping : mappings.entrySet()) { - Map handlers = getHandlerMap(mapping.getValue()); - for (Entry handler : handlers.entrySet()) { - result.put(handler.getKey(), - Collections.singletonMap("bean", mapping.getKey())); - } - } - } - } - - private Map getHandlerMap(AbstractUrlHandlerMapping mapping) { - if (AopUtils.isCglibProxy(mapping)) { - // If the AbstractUrlHandlerMapping is a cglib proxy we can't call - // the final getHandlerMap() method. - return Collections.emptyMap(); - } - return mapping.getHandlerMap(); - } - - protected void extractHandlerMappings( - Collection handlerMappings, - Map result) { - for (AbstractUrlHandlerMapping mapping : handlerMappings) { - Map handlers = mapping.getHandlerMap(); - for (Map.Entry entry : handlers.entrySet()) { - Class handlerClass = entry.getValue().getClass(); - result.put(entry.getKey(), - Collections.singletonMap("type", handlerClass.getName())); - } - } - } - - protected void extractMethodMappings( - Collection> methodMappings, - Map result) { - for (AbstractHandlerMethodMapping mapping : methodMappings) { - Map methods = mapping.getHandlerMethods(); - for (Map.Entry entry : methods.entrySet()) { - result.put(String.valueOf(entry.getKey()), Collections - .singletonMap("method", String.valueOf(entry.getValue()))); - } - } - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/RequestMappingEndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/RequestMappingEndpointAutoConfiguration.java deleted file mode 100644 index 04bac88a4f58..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/RequestMappingEndpointAutoConfiguration.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.web.servlet; - -import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; - -/** - * {@link EnableAutoConfiguration Auto-configuration} for {@link RequestMappingEndpoint}. - * - * @author Phillip Webb - * @since 2.0.0 - */ -@Configuration -@ConditionalOnClass(AbstractHandlerMethodMapping.class) -public class RequestMappingEndpointAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnEnabledEndpoint - public RequestMappingEndpoint requestMappingEndpoint() { - return new RequestMappingEndpoint(); - } - -} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories index d76062be2036..6bd280cc930f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories @@ -41,10 +41,10 @@ org.springframework.boot.actuate.autoconfigure.system.DiskSpaceHealthIndicatorAu org.springframework.boot.actuate.autoconfigure.trace.TraceEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.trace.TraceRepositoryAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.trace.TraceWebFilterAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.web.MappingsEndpointAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.web.reactive.ReactiveManagementContextAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration,\ -org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration,\ -org.springframework.boot.actuate.autoconfigure.web.servlet.RequestMappingEndpointAutoConfiguration +org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration=\ org.springframework.boot.actuate.autoconfigure.endpoint.web.reactive.WebFluxEndpointManagementContextConfiguration,\ diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AbstractEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AbstractEndpointDocumentationTests.java index 2ff5eb4234c1..46c5e7eca260 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AbstractEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AbstractEndpointDocumentationTests.java @@ -26,33 +26,26 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -import org.junit.Before; -import org.junit.Rule; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.endpoint.web.reactive.WebFluxEndpointManagementContextConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.WebMvcEndpointManagementContextConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.restdocs.JUnitRestDocumentation; -import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; import org.springframework.restdocs.operation.preprocess.ContentModifyingOperationPreprocessor; import org.springframework.restdocs.operation.preprocess.OperationPreprocessor; import org.springframework.restdocs.payload.FieldDescriptor; import org.springframework.restdocs.payload.JsonFieldType; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.context.TestPropertySource; import org.springframework.util.StringUtils; -import org.springframework.web.context.WebApplicationContext; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; @@ -62,30 +55,9 @@ * * @author Andy Wilkinson */ -@RunWith(SpringRunner.class) -@SpringBootTest(properties = { "spring.jackson.serialization.indent_output=true", +@TestPropertySource(properties = { "spring.jackson.serialization.indent_output=true", "management.endpoints.web.expose=*" }) -public abstract class AbstractEndpointDocumentationTests { - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); - - protected MockMvc mockMvc; - - @Autowired - private WebApplicationContext applicationContext; - - @Before - public void before() { - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.applicationContext) - .apply(MockMvcRestDocumentation - .documentationConfiguration(this.restDocumentation).uris()) - .build(); - } - - protected WebApplicationContext getApplicationContext() { - return this.applicationContext; - } +public class AbstractEndpointDocumentationTests { protected String describeEnumValues(Class> enumType) { return StringUtils @@ -160,7 +132,9 @@ private List select(List candidates, Predicate filter) { DispatcherServletAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, WebMvcEndpointManagementContextConfiguration.class, - PropertyPlaceholderAutoConfiguration.class }) + WebFluxEndpointManagementContextConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, WebFluxAutoConfiguration.class, + HttpHandlerAutoConfiguration.class }) static class BaseDocumentationConfiguration { } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AuditEventsEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AuditEventsEndpointDocumentationTests.java index 1edc26729680..bfe6c6f08088 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AuditEventsEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/AuditEventsEndpointDocumentationTests.java @@ -49,7 +49,7 @@ * @author Andy Wilkinson */ public class AuditEventsEndpointDocumentationTests - extends AbstractEndpointDocumentationTests { + extends MockMvcEndpointDocumentationTests { @MockBean private AuditEventRepository repository; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/BeansEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/BeansEndpointDocumentationTests.java index 96d9fc1fc311..e1a1e76b2f47 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/BeansEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/BeansEndpointDocumentationTests.java @@ -45,7 +45,7 @@ * * @author Andy Wilkinson */ -public class BeansEndpointDocumentationTests extends AbstractEndpointDocumentationTests { +public class BeansEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { @Test public void beans() throws Exception { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConditionsReportEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConditionsReportEndpointDocumentationTests.java index e7afb97f2c48..4bdf8b64ac57 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConditionsReportEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConditionsReportEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -51,7 +51,7 @@ * @author Andy Wilkinson */ public class ConditionsReportEndpointDocumentationTests - extends AbstractEndpointDocumentationTests { + extends MockMvcEndpointDocumentationTests { @Rule public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConfigurationPropertiesReportEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConfigurationPropertiesReportEndpointDocumentationTests.java index c80ee76dde5f..fb14a8a43176 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConfigurationPropertiesReportEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ConfigurationPropertiesReportEndpointDocumentationTests.java @@ -38,7 +38,7 @@ * @author Andy Wilkinson */ public class ConfigurationPropertiesReportEndpointDocumentationTests - extends AbstractEndpointDocumentationTests { + extends MockMvcEndpointDocumentationTests { @Test public void configProps() throws Exception { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/EnvironmentEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/EnvironmentEndpointDocumentationTests.java index 531895e7330c..1c44b54bc7a8 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/EnvironmentEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/EnvironmentEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -58,7 +58,7 @@ */ @TestPropertySource(properties = "spring.config.location=classpath:/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/") public class EnvironmentEndpointDocumentationTests - extends AbstractEndpointDocumentationTests { + extends MockMvcEndpointDocumentationTests { private static final FieldDescriptor activeProfiles = fieldWithPath("activeProfiles") .description("Names of the active profiles, if any."); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/FlywayEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/FlywayEndpointDocumentationTests.java index 55a198d4df94..334e4fe6ea01 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/FlywayEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/FlywayEndpointDocumentationTests.java @@ -45,7 +45,7 @@ * @author Andy Wilkinson */ @AutoConfigureTestDatabase -public class FlywayEndpointDocumentationTests extends AbstractEndpointDocumentationTests { +public class FlywayEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { @Test public void flyway() throws Exception { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java index aad70d26f5ec..9aebee4c93af 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HealthEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -47,7 +47,7 @@ * * @author Andy Wilkinson */ -public class HealthEndpointDocumentationTests extends AbstractEndpointDocumentationTests { +public class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { @Test public void health() throws Exception { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HeapDumpWebEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HeapDumpWebEndpointDocumentationTests.java index 2e0f7e22225f..20d5dc5889fe 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HeapDumpWebEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/HeapDumpWebEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -40,7 +40,7 @@ * @author Andy Wilkinson */ public class HeapDumpWebEndpointDocumentationTests - extends AbstractEndpointDocumentationTests { + extends MockMvcEndpointDocumentationTests { @Test public void heapDump() throws Exception { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/InfoEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/InfoEndpointDocumentationTests.java index 9342bc11639e..82725e71ee88 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/InfoEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/InfoEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -44,7 +44,7 @@ * * @author Andy Wilkinson */ -public class InfoEndpointDocumentationTests extends AbstractEndpointDocumentationTests { +public class InfoEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { @Test public void info() throws Exception { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LiquibaseEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LiquibaseEndpointDocumentationTests.java index 1056b603af38..abaddf1a6fb1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LiquibaseEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LiquibaseEndpointDocumentationTests.java @@ -43,7 +43,7 @@ * @author Andy Wilkinson */ public class LiquibaseEndpointDocumentationTests - extends AbstractEndpointDocumentationTests { + extends MockMvcEndpointDocumentationTests { @Test public void liquibase() throws Exception { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LogFileWebEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LogFileWebEndpointDocumentationTests.java index 8b8d263a914e..dd0c0ed71c1f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LogFileWebEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LogFileWebEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -36,7 +36,7 @@ */ @TestPropertySource(properties = "logging.file=src/test/resources/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/sample.log") public class LogFileWebEndpointDocumentationTests - extends AbstractEndpointDocumentationTests { + extends MockMvcEndpointDocumentationTests { @Test public void logFile() throws Exception { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LoggersEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LoggersEndpointDocumentationTests.java index 441ca4b978a9..08f6e48b00bb 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LoggersEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/LoggersEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -49,7 +49,7 @@ * @author Andy Wilkinson */ public class LoggersEndpointDocumentationTests - extends AbstractEndpointDocumentationTests { + extends MockMvcEndpointDocumentationTests { private static final List levelFields = Arrays.asList( fieldWithPath("configuredLevel") diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MappingsEndpointReactiveDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MappingsEndpointReactiveDocumentationTests.java new file mode 100644 index 000000000000..8e98a0f1e841 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MappingsEndpointReactiveDocumentationTests.java @@ -0,0 +1,113 @@ +/* + * Copyright 2012-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation; + +import java.util.Collection; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.actuate.web.MappingDescriptionProvider; +import org.springframework.boot.actuate.web.MappingsEndpoint; +import org.springframework.boot.actuate.web.reactive.DispatcherHandlersMappingDescriptionProvider; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; +import org.springframework.boot.web.server.LocalServerPort; +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.restdocs.JUnitRestDocumentation; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; + +import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document; +import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.documentationConfiguration; + +/** + * Tests for generating documentation describing {@link MappingsEndpoint}. + * + * @author Andy Wilkinson + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "spring.main.web-application-type=reactive") +public class MappingsEndpointReactiveDocumentationTests + extends AbstractEndpointDocumentationTests { + + @Rule + public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); + + @LocalServerPort + private int port; + + private WebTestClient client; + + @Before + public void webTestClient() { + this.client = WebTestClient + .bindToServer().filter(documentationConfiguration(this.restDocumentation) + .snippets().withDefaults()) + .baseUrl("http://localhost:" + this.port).build(); + } + + @Test + public void mappings() throws Exception { + this.client.get().uri("/actuator/mappings").exchange().expectStatus().isOk() + .expectBody() + .consumeWith(document("mappings", + responseFields( + beneathPath("contexts.*.mappings.dispatcherHandlers") + .withSubsectionId("dispatcher-handlers"), + fieldWithPath("*").description( + "Dispatcher handler mappings, if any, keyed by " + + "dispatcher handler bean name."), + fieldWithPath("*.[].handler") + .description("Handler for the mapping."), + fieldWithPath("*.[].predicate") + .description("Predicate for the mapping.")))); + } + + @Configuration + @Import(BaseDocumentationConfiguration.class) + static class TestConfiguration { + + @Bean + public NettyReactiveWebServerFactory netty() { + return new NettyReactiveWebServerFactory(0); + } + + @Bean + public DispatcherHandlersMappingDescriptionProvider dispatcherHandlersMappingDescriptionProvider() { + return new DispatcherHandlersMappingDescriptionProvider(); + } + + @Bean + public MappingsEndpoint mappingsEndpoint( + Collection descriptionProviders, + ConfigurableApplicationContext context) { + return new MappingsEndpoint(descriptionProviders, context); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MappingsEndpointServletDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MappingsEndpointServletDocumentationTests.java new file mode 100644 index 000000000000..8d64ec9557ff --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MappingsEndpointServletDocumentationTests.java @@ -0,0 +1,161 @@ +/* + * Copyright 2012-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation; + +import java.util.Collection; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.actuate.web.MappingDescriptionProvider; +import org.springframework.boot.actuate.web.MappingsEndpoint; +import org.springframework.boot.actuate.web.servlet.DispatcherServletsMappingDescriptionProvider; +import org.springframework.boot.actuate.web.servlet.FiltersMappingDescriptionProvider; +import org.springframework.boot.actuate.web.servlet.ServletsMappingDescriptionProvider; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.server.LocalServerPort; +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.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.restdocs.payload.ResponseFieldsSnippet; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; + +import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath; +import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document; +import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.documentationConfiguration; + +/** + * Tests for generating documentation describing {@link MappingsEndpoint}. + * + * @author Andy Wilkinson + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +public class MappingsEndpointServletDocumentationTests + extends AbstractEndpointDocumentationTests { + + @Rule + public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); + + @LocalServerPort + private int port; + + private WebTestClient client; + + @Before + public void webTestClient() { + this.client = WebTestClient.bindToServer() + .filter(documentationConfiguration(this.restDocumentation)) + .baseUrl("http://localhost:" + this.port).build(); + } + + @Test + public void mappings() throws Exception { + ResponseFieldsSnippet commonResponseFields = responseFields( + fieldWithPath("contexts") + .description("Application contexts keyed by id."), + fieldWithPath("contexts.*.mappings") + .description("Mappings in the context, keyed by mapping type."), + subsectionWithPath("contexts.*.mappings.dispatcherServlets") + .description("Dispatcher servlet mappings, if any."), + subsectionWithPath("contexts.*.mappings.servletFilters") + .description("Servlet filter mappings, if any."), + subsectionWithPath("contexts.*.mappings.servlets") + .description("Servlet mappings, if any."), + subsectionWithPath("contexts.*.mappings.dispatcherHandlers") + .description("Dispatcher handler mappings, if any.").optional() + .type(JsonFieldType.OBJECT), + parentIdField()); + this.client.get().uri("/actuator/mappings").exchange().expectBody() + .consumeWith(document("mappings", commonResponseFields, + responseFields( + beneathPath("contexts.*.mappings.dispatcherServlets") + .withSubsectionId("dispatcher-servlets"), + fieldWithPath("*").description( + "Dispatcher servlet mappings, if any, keyed by " + + "dispatcher servlet bean name."), + fieldWithPath("*.[].handler") + .description("Handler for the mapping."), + fieldWithPath("*.[].predicate") + .description("Predicate for the mapping.")), + responseFields( + beneathPath("contexts.*.mappings.servletFilters") + .withSubsectionId("servlet-filters"), + fieldWithPath("[].servletNameMappings").description( + "Names of the servlets to which the filter is mapped."), + fieldWithPath("[].urlPatternMappings").description( + "URL pattern to which the filter is mapped."), + fieldWithPath("[].name") + .description("Name of the filter."), + fieldWithPath("[].className") + .description("Class name of the filter")), + responseFields( + beneathPath("contexts.*.mappings.servlets") + .withSubsectionId("servlets"), + fieldWithPath("[].mappings") + .description("Mappings of the servlet."), + fieldWithPath("[].name") + .description("Name of the servlet."), + fieldWithPath("[].className") + .description("Class name of the servlet")))); + } + + @Configuration + @Import(BaseDocumentationConfiguration.class) + static class TestConfiguration { + + @Bean + public TomcatServletWebServerFactory tomcat() { + return new TomcatServletWebServerFactory(0); + } + + @Bean + public DispatcherServletsMappingDescriptionProvider dispatcherServletsMappingDescriptionProvider() { + return new DispatcherServletsMappingDescriptionProvider(); + } + + @Bean + public ServletsMappingDescriptionProvider servletsMappingDescriptionProvider() { + return new ServletsMappingDescriptionProvider(); + } + + @Bean + public FiltersMappingDescriptionProvider filtersMappingDescriptionProvider() { + return new FiltersMappingDescriptionProvider(); + } + + @Bean + public MappingsEndpoint mappingsEndpoint( + Collection descriptionProviders, + ConfigurableApplicationContext context) { + return new MappingsEndpoint(descriptionProviders, context); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MetricsEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MetricsEndpointDocumentationTests.java index 0a2c96fc4b70..d242b91d14f0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MetricsEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MetricsEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -40,7 +40,7 @@ * @author Andy Wilkinson */ public class MetricsEndpointDocumentationTests - extends AbstractEndpointDocumentationTests { + extends MockMvcEndpointDocumentationTests { @Test public void metricNames() throws Exception { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MockMvcEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MockMvcEndpointDocumentationTests.java new file mode 100644 index 000000000000..674bcf0f88aa --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/MockMvcEndpointDocumentationTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.autoconfigure.endpoint.web.documentation; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.restdocs.JUnitRestDocumentation; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +/** + * Abstract base class for tests that generate endpoint documentation using Spring REST + * Docs and {@link MockMvc}. + * + * @author Andy Wilkinson + */ +@RunWith(SpringRunner.class) +@SpringBootTest(properties = { "spring.jackson.serialization.indent_output=true", + "management.endpoints.web.expose=*" }) +public abstract class MockMvcEndpointDocumentationTests + extends AbstractEndpointDocumentationTests { + + @Rule + public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(); + + protected MockMvc mockMvc; + + @Autowired + private WebApplicationContext applicationContext; + + @Before + public void before() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.applicationContext) + .apply(MockMvcRestDocumentation + .documentationConfiguration(this.restDocumentation).uris()) + .build(); + } + + protected WebApplicationContext getApplicationContext() { + return this.applicationContext; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/PrometheusScrapeEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/PrometheusScrapeEndpointDocumentationTests.java index ef5d921aff20..cc5f2945dbe5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/PrometheusScrapeEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/PrometheusScrapeEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -38,7 +38,7 @@ * @author Andy Wilkinson */ public class PrometheusScrapeEndpointDocumentationTests - extends AbstractEndpointDocumentationTests { + extends MockMvcEndpointDocumentationTests { @Test public void prometheus() throws Exception { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ScheduledTasksEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ScheduledTasksEndpointDocumentationTests.java index 825abe54280e..65901ed54621 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ScheduledTasksEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ScheduledTasksEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -44,7 +44,7 @@ * @author Andy Wilkinson */ public class ScheduledTasksEndpointDocumentationTests - extends AbstractEndpointDocumentationTests { + extends MockMvcEndpointDocumentationTests { @Test public void scheduledTasks() throws Exception { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/SessionsEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/SessionsEndpointDocumentationTests.java index d9976084c430..242a9419c0b4 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/SessionsEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/SessionsEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -55,7 +55,7 @@ */ @TestPropertySource(properties = "spring.jackson.serialization.write-dates-as-timestamps=false") public class SessionsEndpointDocumentationTests - extends AbstractEndpointDocumentationTests { + extends MockMvcEndpointDocumentationTests { private static final Session sessionOne = createSession( Instant.now().minusSeconds(60 * 60 * 12), Instant.now().minusSeconds(45)); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ShutdownEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ShutdownEndpointDocumentationTests.java index 23af8081699b..02684dbb0506 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ShutdownEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ShutdownEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -37,7 +37,7 @@ * @author Andy Wilkinson */ public class ShutdownEndpointDocumentationTests - extends AbstractEndpointDocumentationTests { + extends MockMvcEndpointDocumentationTests { @Test public void shutdown() throws Exception { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ThreadDumpEndpointDocumentationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ThreadDumpEndpointDocumentationTests.java index 1e2be9d71c91..84741b4b8fe8 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ThreadDumpEndpointDocumentationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/documentation/ThreadDumpEndpointDocumentationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -38,7 +38,7 @@ * @author Andy Wilkinson */ public class ThreadDumpEndpointDocumentationTests - extends AbstractEndpointDocumentationTests { + extends MockMvcEndpointDocumentationTests { @Test public void threadDump() throws Exception { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/EndpointAutoConfigurationClasses.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/EndpointAutoConfigurationClasses.java index 65d2a5ea0c27..e9b346bb2c53 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/EndpointAutoConfigurationClasses.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/EndpointAutoConfigurationClasses.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -29,7 +29,7 @@ import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.management.ThreadDumpEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.trace.TraceEndpointAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.web.servlet.RequestMappingEndpointAutoConfiguration; +import org.springframework.boot.actuate.autoconfigure.web.MappingsEndpointAutoConfiguration; /** * A list of all endpoint auto-configuration classes for use in tests. @@ -50,7 +50,7 @@ final class EndpointAutoConfigurationClasses { all.add(InfoEndpointAutoConfiguration.class); all.add(ThreadDumpEndpointAutoConfiguration.class); all.add(TraceEndpointAutoConfiguration.class); - all.add(RequestMappingEndpointAutoConfiguration.class); + all.add(MappingsEndpointAutoConfiguration.class); ALL = all.toArray(new Class[] {}); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/RequestMappingEndpointTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/RequestMappingEndpointTests.java deleted file mode 100644 index 8d6ecb4a7601..000000000000 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/RequestMappingEndpointTests.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.actuate.autoconfigure.web.servlet; - -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; - -import org.junit.Test; - -import org.springframework.boot.actuate.endpoint.EndpointInfo; -import org.springframework.boot.actuate.endpoint.OperationType; -import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; -import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate; -import org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod; -import org.springframework.boot.actuate.endpoint.web.WebOperation; -import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping; -import org.springframework.boot.endpoint.web.EndpointMapping; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Lazy; -import org.springframework.context.annotation.Scope; -import org.springframework.context.annotation.ScopedProxyMode; -import org.springframework.context.support.StaticApplicationContext; -import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; -import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link RequestMappingEndpoint}. - * - * @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.mappings(); - assertThat(result).hasSize(1); - @SuppressWarnings("unchecked") - Map map = (Map) result.get("/foo"); - assertThat(map.get("type")).isEqualTo("java.lang.Object"); - } - - @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.mappings(); - assertThat(result).hasSize(1); - @SuppressWarnings("unchecked") - Map map = (Map) result.get("/foo"); - assertThat(map.get("bean")).isEqualTo("mapping"); - } - - @Test - public void beanUrlMappingsProxy() { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( - MappingConfiguration.class); - this.endpoint.setApplicationContext(context); - Map result = this.endpoint.mappings(); - assertThat(result).hasSize(1); - @SuppressWarnings("unchecked") - Map map = (Map) result.get("/foo"); - assertThat(map.get("bean")).isEqualTo("scopedTarget.mapping"); - } - - @SuppressWarnings("unchecked") - @Test - public void beanMethodMappings() { - StaticApplicationContext context = new StaticApplicationContext(); - context.getDefaultListableBeanFactory().registerSingleton("mapping", - createHandlerMapping()); - this.endpoint.setApplicationContext(context); - Map result = this.endpoint.mappings(); - assertThat(result).hasSize(2); - assertThat(result.keySet()).filteredOn((key) -> key.contains("[/actuator/test]")) - .hasOnlyOneElementSatisfying( - (key) -> assertThat((Map) result.get(key)) - .containsOnlyKeys("bean", "method")); - assertThat(result.keySet()).filteredOn((key) -> key.contains("[/actuator]")) - .hasOnlyOneElementSatisfying( - (key) -> assertThat((Map) result.get(key)) - .containsOnlyKeys("bean", "method")); - } - - @SuppressWarnings("unchecked") - @Test - public void concreteMethodMappings() { - WebMvcEndpointHandlerMapping mapping = createHandlerMapping(); - this.endpoint.setMethodMappings(Collections.singletonList(mapping)); - Map result = this.endpoint.mappings(); - assertThat(result).hasSize(2); - assertThat(result.keySet()).filteredOn((key) -> key.contains("[/actuator/test]")) - .hasOnlyOneElementSatisfying( - (key) -> assertThat((Map) result.get(key)) - .containsOnlyKeys("method")); - assertThat(result.keySet()).filteredOn((key) -> key.contains("[/actuator]")) - .hasOnlyOneElementSatisfying( - (key) -> assertThat((Map) result.get(key)) - .containsOnlyKeys("method")); - } - - private WebMvcEndpointHandlerMapping createHandlerMapping() { - OperationRequestPredicate requestPredicate = new OperationRequestPredicate("test", - WebEndpointHttpMethod.GET, Collections.singletonList("application/json"), - Collections.singletonList("application/json")); - WebOperation operation = new WebOperation(OperationType.READ, - (arguments) -> "Invoked", true, requestPredicate, "test"); - WebMvcEndpointHandlerMapping mapping = new WebMvcEndpointHandlerMapping( - new EndpointMapping("actuator"), - Collections.singleton(new EndpointInfo<>("test", true, - Collections.singleton(operation))), - new EndpointMediaTypes(Arrays.asList("application/vnd.test+json"), - Arrays.asList("application/vnd.test+json"))); - mapping.setApplicationContext(new StaticApplicationContext()); - mapping.afterPropertiesSet(); - return mapping; - } - - @Configuration - protected static class MappingConfiguration { - - @Bean - @Lazy - @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) - public AbstractUrlHandlerMapping mapping() { - SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); - mapping.setUrlMap(Collections.singletonMap("/foo", new Object())); - return mapping; - } - - } - -} diff --git a/spring-boot-project/spring-boot-actuator/pom.xml b/spring-boot-project/spring-boot-actuator/pom.xml index 9d5e6a65f4ea..3eacb314c687 100644 --- a/spring-boot-project/spring-boot-actuator/pom.xml +++ b/spring-boot-project/spring-boot-actuator/pom.xml @@ -66,6 +66,17 @@ jest true + + io.undertow + undertow-servlet + true + + + org.jboss.spec.javax.servlet + jboss-servlet-api_3.1_spec + + + javax.cache cache-api @@ -191,6 +202,11 @@ spring-data-redis true + + org.springframework.data + spring-data-rest-webmvc + true + org.springframework.data spring-data-solr @@ -281,22 +297,6 @@ reactor-netty test - - io.undertow - undertow-core - test - - - io.undertow - undertow-servlet - test - - - org.jboss.spec.javax.servlet - jboss-servlet-api_3.1_spec - - - org.hsqldb hsqldb diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/MappingDescriptionProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/MappingDescriptionProvider.java new file mode 100644 index 000000000000..09e2eb074d7a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/MappingDescriptionProvider.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2018 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.web; + +import java.util.List; + +import org.springframework.context.ApplicationContext; + +/** + * A {@link MappingDescriptionProvider} provides a {@link List} of mapping descriptions + * via implementation-specific introspection of an application context. + * + * @author Andy Wilkinson + * @since 2.0.0 + */ +public interface MappingDescriptionProvider { + + /** + * Returns the name of the mappings described by this provider. + * + * @return the name of the mappings + */ + String getMappingName(); + + /** + * Produce the descriptions of the mappings identified by this provider in the given + * {@code context}. + * @param context the application context to introspect + * @return the mapping descriptions + */ + Object describeMappings(ApplicationContext context); + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/MappingsEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/MappingsEndpoint.java new file mode 100644 index 000000000000..65e4a4001bbd --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/MappingsEndpoint.java @@ -0,0 +1,109 @@ +/* + * Copyright 2012-2018 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.web; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.context.ApplicationContext; + +/** + * {@link Endpoint} to expose HTTP request mappings. + * + * @author Andy Wilkinson + * @since 2.0.0 + */ +@Endpoint(id = "mappings") +public class MappingsEndpoint { + + private final Collection descriptionProviders; + + private final ApplicationContext context; + + public MappingsEndpoint(Collection descriptionProviders, + ApplicationContext context) { + this.descriptionProviders = descriptionProviders; + this.context = context; + } + + @ReadOperation + public ApplicationMappings mappings() { + ApplicationContext target = this.context; + Map contextMappings = new HashMap<>(); + while (target != null) { + contextMappings.put(target.getId(), mappingsForContext(target)); + target = target.getParent(); + } + return new ApplicationMappings(contextMappings); + } + + private ContextMappings mappingsForContext(ApplicationContext applicationContext) { + Map mappings = new HashMap<>(); + this.descriptionProviders + .forEach((provider) -> mappings.put(provider.getMappingName(), + provider.describeMappings(applicationContext))); + return new ContextMappings(mappings, applicationContext.getParent() == null ? null + : applicationContext.getId()); + } + + /** + * A description of an application's request mappings. Primarily intended for + * serialization to JSON. + */ + public static final class ApplicationMappings { + + private final Map contextMappings; + + private ApplicationMappings(Map contextMappings) { + this.contextMappings = contextMappings; + } + + public Map getContexts() { + return this.contextMappings; + } + + } + + /** + * A description of an application context's request mappings. Primarily intended for + * serialization to JSON. + */ + public static final class ContextMappings { + + private final Map mappings; + + private final String parentId; + + private ContextMappings(Map mappings, String parentId) { + this.mappings = mappings; + this.parentId = parentId; + } + + public String getParentId() { + return this.parentId; + } + + public Map getMappings() { + return this.mappings; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/reactive/DispatcherHandlerMappingDescription.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/reactive/DispatcherHandlerMappingDescription.java new file mode 100644 index 000000000000..44316a9b34ec --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/reactive/DispatcherHandlerMappingDescription.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2018 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.web.reactive; + +import org.springframework.web.servlet.DispatcherServlet; + +/** + * A description of a mapping known to a {@link DispatcherServlet}. + * + * @author Andy Wilkinson + * @since 2.0.0 + */ +public class DispatcherHandlerMappingDescription { + + private final String predicate; + + private final String handler; + + DispatcherHandlerMappingDescription(String predicate, String handler) { + this.predicate = predicate; + this.handler = handler; + } + + public String getHandler() { + return this.handler; + } + + public String getPredicate() { + return this.predicate; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/reactive/DispatcherHandlersMappingDescriptionProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/reactive/DispatcherHandlersMappingDescriptionProvider.java new file mode 100644 index 000000000000..3085048f8061 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/reactive/DispatcherHandlersMappingDescriptionProvider.java @@ -0,0 +1,203 @@ +/* + * Copyright 2012-2018 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.web.reactive; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import reactor.core.publisher.Mono; + +import org.springframework.boot.actuate.web.MappingDescriptionProvider; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.Resource; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.reactive.DispatcherHandler; +import org.springframework.web.reactive.HandlerMapping; +import org.springframework.web.reactive.function.server.HandlerFunction; +import org.springframework.web.reactive.function.server.RequestPredicate; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.RouterFunctions.Visitor; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.support.RouterFunctionMapping; +import org.springframework.web.reactive.handler.AbstractUrlHandlerMapping; +import org.springframework.web.reactive.result.method.RequestMappingInfo; +import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerMapping; +import org.springframework.web.util.pattern.PathPattern; + +/** + * A {@link MappingDescriptionProvider} that introspects the {@link HandlerMapping + * HandlerMappings} that are known to a {@link DispatcherHandler}. + * + * @author Andy Wilkinson + * @since 2.0.0 + */ +public class DispatcherHandlersMappingDescriptionProvider + implements MappingDescriptionProvider { + + private static final List> descriptionProviders = Arrays + .asList(new RequestMappingInfoHandlerMappingDescriptionProvider(), + new UrlHandlerMappingDescriptionProvider(), + new RouterFunctionMappingDescriptionProvider()); + + @Override + public String getMappingName() { + return "dispatcherHandlers"; + } + + @Override + public Map> describeMappings( + ApplicationContext context) { + Map> mappings = new HashMap<>(); + context.getBeansOfType(DispatcherHandler.class).forEach( + (name, handler) -> mappings.put(name, describeMappings(handler))); + return mappings; + } + + private List describeMappings( + DispatcherHandler dispatcherHandler) { + return dispatcherHandler.getHandlerMappings().stream().flatMap(this::describe) + .collect(Collectors.toList()); + } + + @SuppressWarnings("unchecked") + private Stream describe( + T handlerMapping) { + for (HandlerMappingDescriptionProvider descriptionProvider : descriptionProviders) { + if (descriptionProvider.getMappingClass().isInstance(handlerMapping)) { + return ((HandlerMappingDescriptionProvider) descriptionProvider) + .describe(handlerMapping).stream(); + + } + } + return Stream.empty(); + } + + private interface HandlerMappingDescriptionProvider { + + Class getMappingClass(); + + List describe(T handlerMapping); + + } + + private static final class RequestMappingInfoHandlerMappingDescriptionProvider + implements + HandlerMappingDescriptionProvider { + + @Override + public Class getMappingClass() { + return RequestMappingInfoHandlerMapping.class; + } + + @Override + public List describe( + RequestMappingInfoHandlerMapping handlerMapping) { + Map handlerMethods = handlerMapping + .getHandlerMethods(); + return handlerMethods.entrySet().stream().map(this::describe) + .collect(Collectors.toList()); + } + + private DispatcherHandlerMappingDescription describe( + Entry mapping) { + return new DispatcherHandlerMappingDescription(mapping.getKey().toString(), + mapping.getValue().toString()); + } + + } + + private static final class UrlHandlerMappingDescriptionProvider + implements HandlerMappingDescriptionProvider { + + @Override + public Class getMappingClass() { + return AbstractUrlHandlerMapping.class; + } + + @Override + public List describe( + AbstractUrlHandlerMapping handlerMapping) { + return handlerMapping.getHandlerMap().entrySet().stream().map(this::describe) + .collect(Collectors.toList()); + } + + private DispatcherHandlerMappingDescription describe( + Entry mapping) { + return new DispatcherHandlerMappingDescription( + mapping.getKey().getPatternString(), mapping.getValue().toString()); + } + + } + + private static final class RouterFunctionMappingDescriptionProvider + implements HandlerMappingDescriptionProvider { + + @Override + public Class getMappingClass() { + return RouterFunctionMapping.class; + } + + @Override + public List describe( + RouterFunctionMapping handlerMapping) { + MappingDescriptionVisitor visitor = new MappingDescriptionVisitor(); + RouterFunction routerFunction = handlerMapping.getRouterFunction(); + if (routerFunction != null) { + routerFunction.accept(visitor); + } + return visitor.descriptions; + } + + } + + private static final class MappingDescriptionVisitor implements Visitor { + + private final List descriptions = new ArrayList<>(); + + @Override + public void startNested(RequestPredicate predicate) { + } + + @Override + public void endNested(RequestPredicate predicate) { + } + + @Override + public void route(RequestPredicate predicate, + HandlerFunction handlerFunction) { + this.descriptions.add(new DispatcherHandlerMappingDescription( + predicate.toString(), handlerFunction.toString())); + } + + @Override + public void resources(Function> lookupFunction) { + } + + @Override + public void unknown(RouterFunction routerFunction) { + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/DispatcherServletHandlerMappings.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/DispatcherServletHandlerMappings.java new file mode 100644 index 000000000000..07855073df7e --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/DispatcherServletHandlerMappings.java @@ -0,0 +1,142 @@ +/* + * Copyright 2012-2018 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.web.servlet; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import javax.servlet.ServletException; + +import org.apache.catalina.Container; +import org.apache.catalina.Context; +import org.apache.catalina.core.StandardWrapper; + +import org.springframework.boot.web.embedded.tomcat.TomcatWebServer; +import org.springframework.boot.web.embedded.undertow.UndertowServletWebServer; +import org.springframework.boot.web.server.WebServer; +import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.HandlerMapping; + +/** + * {@code DispatcherServletHandlerMappings} provides access to a {@link DispatcherServlet + * DispatcherServlet's} handler mappings, triggering initialization of the dispatcher + * servlet if necessary. + * + * @author Andy Wilkinson + */ +final class DispatcherServletHandlerMappings { + + private final String name; + + private final DispatcherServlet dispatcherServlet; + + private final WebApplicationContext applicationContext; + + DispatcherServletHandlerMappings(String name, DispatcherServlet dispatcherServlet, + WebApplicationContext applicationContext) { + this.name = name; + this.dispatcherServlet = dispatcherServlet; + this.applicationContext = applicationContext; + } + + public List getHandlerMappings() { + List handlerMappings = this.dispatcherServlet + .getHandlerMappings(); + if (handlerMappings == null) { + initializeDispatcherServletIfPossible(); + handlerMappings = this.dispatcherServlet.getHandlerMappings(); + } + return handlerMappings == null ? Collections.emptyList() : handlerMappings; + } + + private void initializeDispatcherServletIfPossible() { + if (!(this.applicationContext instanceof ServletWebServerApplicationContext)) { + return; + } + WebServer webServer = ((ServletWebServerApplicationContext) this.applicationContext) + .getWebServer(); + if (webServer instanceof UndertowServletWebServer) { + new UndertowServletInitializer((UndertowServletWebServer) webServer) + .initializeServlet(this.name); + } + else if (webServer instanceof TomcatWebServer) { + new TomcatServletInitializer((TomcatWebServer) webServer) + .initializeServlet(this.name); + } + } + + public String getName() { + return this.name; + } + + private static final class TomcatServletInitializer { + + private final TomcatWebServer webServer; + + private TomcatServletInitializer(TomcatWebServer webServer) { + this.webServer = webServer; + } + + void initializeServlet(String name) { + findContext().ifPresent((context) -> initializeServlet(context, name)); + } + + private Optional findContext() { + return Stream.of(this.webServer.getTomcat().getHost().findChildren()) + .filter(Context.class::isInstance).map(Context.class::cast) + .findFirst(); + } + + private void initializeServlet(Context context, String name) { + Container child = context.findChild(name); + if (child instanceof StandardWrapper) { + try { + ((StandardWrapper) child).allocate(); + } + catch (ServletException ex) { + // Continue + } + } + } + + } + + private static final class UndertowServletInitializer { + + private final UndertowServletWebServer webServer; + + private UndertowServletInitializer(UndertowServletWebServer webServer) { + this.webServer = webServer; + } + + void initializeServlet(String name) { + try { + this.webServer.getDeploymentManager().getDeployment().getServlets() + .getManagedServlet(name).forceInit(); + } + catch (ServletException ex) { + // Continue + } + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/DispatcherServletMappingDescription.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/DispatcherServletMappingDescription.java new file mode 100644 index 000000000000..3084b10fd4df --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/DispatcherServletMappingDescription.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2018 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.web.servlet; + +import org.springframework.web.servlet.DispatcherServlet; + +/** + * A description of a mapping known to a {@link DispatcherServlet}. + * + * @author Andy Wilkinson + * @since 2.0.0 + */ +public class DispatcherServletMappingDescription { + + private final String handler; + + private final String predicate; + + /** + * Creates a new {@code DispatcherServletMappingDescription} for the given + * {@code handler} that will receives requests that match the given {@code predicate}. + * + * @param predicate the predicate + * @param handler the handler + */ + public DispatcherServletMappingDescription(String predicate, String handler) { + this.handler = handler; + this.predicate = predicate; + } + + /** + * Returns the handler for the described mapping. + * @return the handler + */ + public String getHandler() { + return this.handler; + } + + /** + * Returns the predicate for the described mapping. + * @return the predicate + */ + public String getPredicate() { + return this.predicate; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/DispatcherServletsMappingDescriptionProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/DispatcherServletsMappingDescriptionProvider.java new file mode 100644 index 000000000000..0d59de3c34cf --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/DispatcherServletsMappingDescriptionProvider.java @@ -0,0 +1,199 @@ +/* + * Copyright 2012-2018 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.web.servlet; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.boot.actuate.web.MappingDescriptionProvider; +import org.springframework.context.ApplicationContext; +import org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping; +import org.springframework.util.ClassUtils; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; + +/** + * A {@link MappingDescriptionProvider} that introspects the {@link HandlerMapping + * HandlerMappings} that are known to one or more {@link DispatcherServlet + * DispatcherServlets}. + * + * @author Andy Wilkinson + * @since 2.0.0 + */ +public class DispatcherServletsMappingDescriptionProvider + implements MappingDescriptionProvider { + + private static final List> descriptionProviders; + + static { + List> providers = new ArrayList<>(); + providers.add(new RequestMappingInfoHandlerMappingDescriptionProvider()); + providers.add(new UrlHandlerMappingDescriptionProvider()); + if (ClassUtils.isPresent( + "org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping", + null)) { + providers.add(new DelegatingHandlerMappingDescriptionProvider( + new ArrayList<>(providers))); + } + descriptionProviders = Collections.unmodifiableList(providers); + } + + @Override + public String getMappingName() { + return "dispatcherServlets"; + } + + @Override + public Map> describeMappings( + ApplicationContext context) { + if (context instanceof WebApplicationContext) { + return describeMappings((WebApplicationContext) context); + } + return Collections.emptyMap(); + } + + private Map> describeMappings( + WebApplicationContext context) { + Map> mappings = new HashMap<>(); + context.getBeansOfType(DispatcherServlet.class) + .forEach((name, dispatcherServlet) -> mappings.put(name, + describeMappings(new DispatcherServletHandlerMappings(name, + dispatcherServlet, context)))); + return mappings; + } + + private List describeMappings( + DispatcherServletHandlerMappings mappings) { + return mappings.getHandlerMappings().stream().flatMap(this::describe) + .collect(Collectors.toList()); + } + + private Stream describe( + T handlerMapping) { + return describe(handlerMapping, descriptionProviders).stream(); + } + + @SuppressWarnings("unchecked") + private static List describe( + T handlerMapping, + List> descriptionProviders) { + for (HandlerMappingDescriptionProvider descriptionProvider : descriptionProviders) { + if (descriptionProvider.getMappingClass().isInstance(handlerMapping)) { + return ((HandlerMappingDescriptionProvider) descriptionProvider) + .describe(handlerMapping); + } + } + return Collections.emptyList(); + } + + private interface HandlerMappingDescriptionProvider { + + Class getMappingClass(); + + List describe(T handlerMapping); + + } + + private static final class RequestMappingInfoHandlerMappingDescriptionProvider + implements + HandlerMappingDescriptionProvider { + + @Override + public Class getMappingClass() { + return RequestMappingInfoHandlerMapping.class; + } + + @Override + public List describe( + RequestMappingInfoHandlerMapping handlerMapping) { + Map handlerMethods = handlerMapping + .getHandlerMethods(); + return handlerMethods.entrySet().stream().map(this::describe) + .collect(Collectors.toList()); + } + + private DispatcherServletMappingDescription describe( + Entry mapping) { + return new DispatcherServletMappingDescription(mapping.getKey().toString(), + mapping.getValue().toString()); + } + + } + + private static final class UrlHandlerMappingDescriptionProvider + implements HandlerMappingDescriptionProvider { + + @Override + public Class getMappingClass() { + return AbstractUrlHandlerMapping.class; + } + + @Override + public List describe( + AbstractUrlHandlerMapping handlerMapping) { + return handlerMapping.getHandlerMap().entrySet().stream().map(this::describe) + .collect(Collectors.toList()); + } + + private DispatcherServletMappingDescription describe( + Entry mapping) { + return new DispatcherServletMappingDescription(mapping.getKey().toString(), + mapping.getValue().toString()); + } + + } + + private static final class DelegatingHandlerMappingDescriptionProvider + implements HandlerMappingDescriptionProvider { + + private final List> descriptionProviders; + + private DelegatingHandlerMappingDescriptionProvider( + List> descriptionProviders) { + this.descriptionProviders = descriptionProviders; + } + + @Override + public Class getMappingClass() { + return DelegatingHandlerMapping.class; + } + + @Override + public List describe( + DelegatingHandlerMapping handlerMapping) { + List descriptions = new ArrayList<>(); + for (HandlerMapping delegate : handlerMapping.getDelegates()) { + descriptions.addAll(DispatcherServletsMappingDescriptionProvider + .describe(delegate, this.descriptionProviders)); + } + return descriptions; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/FilterRegistrationMappingDescription.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/FilterRegistrationMappingDescription.java new file mode 100644 index 000000000000..dcd6c0d5f2e6 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/FilterRegistrationMappingDescription.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2018 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.web.servlet; + +import java.util.Collection; + +import javax.servlet.FilterRegistration; + +/** + * A {@link RegistrationMappingDescription} derived from a {@link FilterRegistration}. + * + * @author Andy Wilkinson + * @since 2.0.0 + */ +public class FilterRegistrationMappingDescription + extends RegistrationMappingDescription { + + /** + * Creates a new {@code FilterRegistrationMappingDescription} derived from the given + * {@code filterRegistration}. + * @param filterRegistration the filter registration + */ + public FilterRegistrationMappingDescription(FilterRegistration filterRegistration) { + super(filterRegistration); + } + + /** + * Returns the servlet name mappings for the registered filter. + * + * @return the mappings + */ + public Collection getServletNameMappings() { + return this.getRegistration().getServletNameMappings(); + } + + /** + * Returns the URL pattern mappings for the registered filter. + * + * @return the mappings + */ + public Collection getUrlPatternMappings() { + return this.getRegistration().getUrlPatternMappings(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/FiltersMappingDescriptionProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/FiltersMappingDescriptionProvider.java new file mode 100644 index 000000000000..83522138b765 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/FiltersMappingDescriptionProvider.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2018 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.web.servlet; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import javax.servlet.Filter; +import javax.servlet.ServletContext; + +import org.springframework.boot.actuate.web.MappingDescriptionProvider; +import org.springframework.context.ApplicationContext; +import org.springframework.web.context.WebApplicationContext; + +/** + * A {@link MappingDescriptionProvider} that describes that mappings of any {@link Filter + * Filters} registered with a {@link ServletContext}. + * + * @author Andy Wilkinson + * @since 2.0.0 + */ +public class FiltersMappingDescriptionProvider implements MappingDescriptionProvider { + + @Override + public List describeMappings( + ApplicationContext context) { + if (!(context instanceof WebApplicationContext)) { + return Collections.emptyList(); + } + return ((WebApplicationContext) context).getServletContext() + .getFilterRegistrations().values().stream() + .map(FilterRegistrationMappingDescription::new) + .collect(Collectors.toList()); + } + + @Override + public String getMappingName() { + return "servletFilters"; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/RegistrationMappingDescription.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/RegistrationMappingDescription.java new file mode 100644 index 000000000000..45fb38454a93 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/RegistrationMappingDescription.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2018 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.web.servlet; + +import javax.servlet.Registration; + +/** + * A mapping description derived from a {@link Registration}. + * + * @param type of the registration + * @author Andy Wilkinson + * @since 2.0.0 + */ +public class RegistrationMappingDescription { + + private final T registration; + + /** + * Creates a new {@link RegistrationMappingDescription} derived from the given + * {@code registration} and with the given {@code predicate}. + * @param registration the registration + */ + public RegistrationMappingDescription(T registration) { + this.registration = registration; + } + + /** + * Returns the name of the registered Filter or Servlet. + * + * @return the name + */ + public String getName() { + return this.registration.getName(); + } + + /** + * Returns the class name of the registered Filter or Servlet. + * + * @return the class name + */ + public String getClassName() { + return this.registration.getClassName(); + } + + /** + * Returns the registration that is being described. + * @return the registration + */ + protected final T getRegistration() { + return this.registration; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/ServletRegistrationMappingDescription.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/ServletRegistrationMappingDescription.java new file mode 100644 index 000000000000..6c2a0affd5c0 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/ServletRegistrationMappingDescription.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2018 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.web.servlet; + +import java.util.Collection; + +import javax.servlet.ServletRegistration; + +/** + * A mapping description derived from a {@link ServletRegistration}. + * + * @author Andy Wilkinson + * @since 2.0.0 + */ +public class ServletRegistrationMappingDescription + extends RegistrationMappingDescription { + + /** + * Creates a new {@code ServletRegistrationMappingDescription} derived from the given + * {@code servletRegistration}. + * @param servletRegistration the servlet registration + */ + public ServletRegistrationMappingDescription( + ServletRegistration servletRegistration) { + super(servletRegistration); + } + + /** + * Returns the mappings for the registered servlet. + * + * @return the mappings + */ + public Collection getMappings() { + return getRegistration().getMappings(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/ServletsMappingDescriptionProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/ServletsMappingDescriptionProvider.java new file mode 100644 index 000000000000..adeab11d8935 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/servlet/ServletsMappingDescriptionProvider.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2018 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.web.servlet; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import javax.servlet.Servlet; +import javax.servlet.ServletContext; + +import org.springframework.boot.actuate.web.MappingDescriptionProvider; +import org.springframework.context.ApplicationContext; +import org.springframework.web.context.WebApplicationContext; + +/** + * A {@link MappingDescriptionProvider} that describes that mappings of any {@link Servlet + * Servlets} registered with a {@link ServletContext}. + * + * @author Andy Wilkinson + * @since 2.0 + */ +public class ServletsMappingDescriptionProvider implements MappingDescriptionProvider { + + @Override + public List describeMappings( + ApplicationContext context) { + if (!(context instanceof WebApplicationContext)) { + return Collections.emptyList(); + } + return ((WebApplicationContext) context).getServletContext() + .getServletRegistrations().values().stream() + .map(ServletRegistrationMappingDescription::new) + .collect(Collectors.toList()); + } + + @Override + public String getMappingName() { + return "servlets"; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/MappingsEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/MappingsEndpointTests.java new file mode 100644 index 000000000000..6eb84f0e78e0 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/MappingsEndpointTests.java @@ -0,0 +1,215 @@ +/* + * Copyright 2012-2018 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.web; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import javax.servlet.FilterRegistration; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; + +import org.junit.Test; + +import org.springframework.boot.actuate.web.MappingsEndpoint.ApplicationMappings; +import org.springframework.boot.actuate.web.MappingsEndpoint.ContextMappings; +import org.springframework.boot.actuate.web.reactive.DispatcherHandlerMappingDescription; +import org.springframework.boot.actuate.web.reactive.DispatcherHandlersMappingDescriptionProvider; +import org.springframework.boot.actuate.web.servlet.DispatcherServletMappingDescription; +import org.springframework.boot.actuate.web.servlet.DispatcherServletsMappingDescriptionProvider; +import org.springframework.boot.actuate.web.servlet.FilterRegistrationMappingDescription; +import org.springframework.boot.actuate.web.servlet.FiltersMappingDescriptionProvider; +import org.springframework.boot.actuate.web.servlet.ServletRegistrationMappingDescription; +import org.springframework.boot.actuate.web.servlet.ServletsMappingDescriptionProvider; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mock.web.MockServletConfig; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.reactive.config.EnableWebFlux; +import org.springframework.web.reactive.function.server.RequestPredicates; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.RouterFunctions; +import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link MappingsEndpoint}. + * + * @author Andy Wilkinson + */ +public class MappingsEndpointTests { + + @SuppressWarnings("unchecked") + @Test + public void servletWebMappings() { + ServletContext servletContext = mock(ServletContext.class); + given(servletContext.getInitParameterNames()) + .willReturn(new Vector().elements()); + given(servletContext.getAttributeNames()) + .willReturn(new Vector().elements()); + FilterRegistration filterRegistration = mock(FilterRegistration.class); + given((Map) servletContext.getFilterRegistrations()) + .willReturn(Collections.singletonMap("testFilter", filterRegistration)); + ServletRegistration servletRegistration = mock(ServletRegistration.class); + given((Map) servletContext.getServletRegistrations()) + .willReturn(Collections.singletonMap("testServlet", servletRegistration)); + WebApplicationContextRunner runner = new WebApplicationContextRunner(() -> { + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.setServletContext(servletContext); + return context; + }).withUserConfiguration(EndpointConfiguration.class, + ServletWebConfiguration.class); + runner.run((context) -> { + ContextMappings contextMappings = contextMappings(context); + assertThat(contextMappings.getParentId()).isNull(); + assertThat(contextMappings.getMappings()) + .containsOnlyKeys("dispatcherServlets", "servletFilters", "servlets"); + Map> dispatcherServlets = mappings( + contextMappings, "dispatcherServlets"); + assertThat(dispatcherServlets).containsOnlyKeys("dispatcherServlet"); + List handlerMappings = dispatcherServlets + .get("dispatcherServlet"); + assertThat(handlerMappings).hasSize(1); + List servlets = mappings( + contextMappings, "servlets"); + assertThat(servlets).hasSize(1); + List filters = mappings(contextMappings, + "servletFilters"); + assertThat(filters).hasSize(1); + }); + } + + @Test + public void reactiveWebMappings() { + ReactiveWebApplicationContextRunner runner = new ReactiveWebApplicationContextRunner() + .withUserConfiguration(EndpointConfiguration.class, + ReactiveWebConfiguration.class); + runner.run((context) -> { + ContextMappings contextMappings = contextMappings(context); + assertThat(contextMappings.getParentId()).isNull(); + assertThat(contextMappings.getMappings()) + .containsOnlyKeys("dispatcherHandlers"); + Map> dispatcherHandlers = mappings( + contextMappings, "dispatcherHandlers"); + assertThat(dispatcherHandlers).containsOnlyKeys("webHandler"); + List handlerMappings = dispatcherHandlers + .get("webHandler"); + assertThat(handlerMappings).hasSize(3); + }); + } + + private ContextMappings contextMappings(ApplicationContext context) { + ApplicationMappings applicationMappings = context.getBean(MappingsEndpoint.class) + .mappings(); + assertThat(applicationMappings.getContexts()).containsOnlyKeys(context.getId()); + return applicationMappings.getContexts().get(context.getId()); + } + + @SuppressWarnings("unchecked") + private T mappings(ContextMappings contextMappings, String key) { + return (T) contextMappings.getMappings().get(key); + } + + @Configuration + static class EndpointConfiguration { + + @Bean + public MappingsEndpoint mappingsEndpoint( + Collection descriptionProviders, + ApplicationContext context) { + return new MappingsEndpoint(descriptionProviders, context); + } + + } + + @Configuration + @EnableWebFlux + @Controller + static class ReactiveWebConfiguration { + + @Bean + public DispatcherHandlersMappingDescriptionProvider dispatcherHandlersMappingDescriptionProvider() { + return new DispatcherHandlersMappingDescriptionProvider(); + } + + @Bean + public RouterFunction routerFunction() { + return RouterFunctions + .route(RequestPredicates.GET("/one"), + (request) -> ServerResponse.ok().build()) + .andRoute(RequestPredicates.POST("/two"), + (request) -> ServerResponse.ok().build()); + } + + @RequestMapping("/three") + public void three() { + + } + + } + + @Configuration + @EnableWebMvc + @Controller + static class ServletWebConfiguration { + + @Bean + public DispatcherServletsMappingDescriptionProvider dispatcherServletsMappingDescriptionProvider() { + return new DispatcherServletsMappingDescriptionProvider(); + } + + @Bean + public ServletsMappingDescriptionProvider servletsMappingDescriptionProvider() { + return new ServletsMappingDescriptionProvider(); + } + + @Bean + public FiltersMappingDescriptionProvider filtersMappingDescriptionProvider() { + return new FiltersMappingDescriptionProvider(); + } + + @Bean + public DispatcherServlet dispatcherServlet(WebApplicationContext context) + throws ServletException { + DispatcherServlet dispatcherServlet = new DispatcherServlet(context); + dispatcherServlet.init(new MockServletConfig()); + return dispatcherServlet; + } + + @RequestMapping("/three") + public void three() { + + } + + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServer.java index ed04430d716d..847ede37c57e 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -168,6 +168,12 @@ public void start() throws WebServerException { } } + public DeploymentManager getDeploymentManager() { + synchronized (this.monitor) { + return this.manager; + } + } + private void stopSilently() { try { if (this.undertow != null) {