Rework mappings endpoint

Improve the structure of the response and include mappings from
WebFlux and Servlet and Filter registrations in addition to the
mappings from Spring MVC.

Closes gh-9979
This commit is contained in:
Andy Wilkinson
2018-01-16 18:24:33 +00:00
parent 1f26a0314c
commit 67a299020b
46 changed files with 1905 additions and 447 deletions

View File

@@ -435,6 +435,11 @@
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-webtestclient</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>

View File

@@ -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[]

View File

@@ -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]

View File

@@ -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<Collection<MappingDescriptionProvider>> 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();
}
}
}

View File

@@ -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<AbstractUrlHandlerMapping> handlerMappings = Collections.emptyList();
private List<AbstractHandlerMethodMapping<?>> 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<AbstractUrlHandlerMapping> handlerMappings) {
this.handlerMappings = handlerMappings;
}
/**
* Set the method mappings.
* @param methodMappings the method mappings
*/
public void setMethodMappings(List<AbstractHandlerMethodMapping<?>> methodMappings) {
this.methodMappings = methodMappings;
}
@ReadOperation
public Map<String, Object> mappings() {
Map<String, Object> 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<String, Object> result) {
if (applicationContext != null) {
for (Entry<String, AbstractHandlerMethodMapping> bean : applicationContext
.getBeansOfType(AbstractHandlerMethodMapping.class).entrySet()) {
@SuppressWarnings("unchecked")
Map<?, HandlerMethod> methods = bean.getValue().getHandlerMethods();
for (Entry<?, HandlerMethod> method : methods.entrySet()) {
Map<String, String> 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<String, Object> result) {
if (applicationContext != null) {
Map<String, AbstractUrlHandlerMapping> mappings = applicationContext
.getBeansOfType(AbstractUrlHandlerMapping.class);
for (Entry<String, AbstractUrlHandlerMapping> mapping : mappings.entrySet()) {
Map<String, Object> handlers = getHandlerMap(mapping.getValue());
for (Entry<String, Object> handler : handlers.entrySet()) {
result.put(handler.getKey(),
Collections.singletonMap("bean", mapping.getKey()));
}
}
}
}
private Map<String, Object> 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<AbstractUrlHandlerMapping> handlerMappings,
Map<String, Object> result) {
for (AbstractUrlHandlerMapping mapping : handlerMappings) {
Map<String, Object> handlers = mapping.getHandlerMap();
for (Map.Entry<String, Object> entry : handlers.entrySet()) {
Class<? extends Object> handlerClass = entry.getValue().getClass();
result.put(entry.getKey(),
Collections.singletonMap("type", handlerClass.getName()));
}
}
}
protected void extractMethodMappings(
Collection<AbstractHandlerMethodMapping<?>> methodMappings,
Map<String, Object> result) {
for (AbstractHandlerMethodMapping<?> mapping : methodMappings) {
Map<?, HandlerMethod> methods = mapping.getHandlerMethods();
for (Map.Entry<?, HandlerMethod> entry : methods.entrySet()) {
result.put(String.valueOf(entry.getKey()), Collections
.singletonMap("method", String.valueOf(entry.getValue())));
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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,\

View File

@@ -26,33 +26,26 @@ import java.util.stream.Stream;
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 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWit
*
* @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<? extends Enum<?>> enumType) {
return StringUtils
@@ -160,7 +132,9 @@ public abstract class AbstractEndpointDocumentationTests {
DispatcherServletAutoConfiguration.class, EndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class,
WebMvcEndpointManagementContextConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
WebFluxEndpointManagementContextConfiguration.class,
PropertyPlaceholderAutoConfiguration.class, WebFluxAutoConfiguration.class,
HttpHandlerAutoConfiguration.class })
static class BaseDocumentationConfiguration {
}

View File

@@ -49,7 +49,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Andy Wilkinson
*/
public class AuditEventsEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
extends MockMvcEndpointDocumentationTests {
@MockBean
private AuditEventRepository repository;

View File

@@ -45,7 +45,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*
* @author Andy Wilkinson
*/
public class BeansEndpointDocumentationTests extends AbstractEndpointDocumentationTests {
public class BeansEndpointDocumentationTests extends MockMvcEndpointDocumentationTests {
@Test
public void beans() throws Exception {

View File

@@ -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 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Andy Wilkinson
*/
public class ConditionsReportEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
extends MockMvcEndpointDocumentationTests {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();

View File

@@ -38,7 +38,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Andy Wilkinson
*/
public class ConfigurationPropertiesReportEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
extends MockMvcEndpointDocumentationTests {
@Test
public void configProps() throws Exception {

View File

@@ -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 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/
@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.");

View File

@@ -45,7 +45,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Andy Wilkinson
*/
@AutoConfigureTestDatabase
public class FlywayEndpointDocumentationTests extends AbstractEndpointDocumentationTests {
public class FlywayEndpointDocumentationTests extends MockMvcEndpointDocumentationTests {
@Test
public void flyway() throws Exception {

View File

@@ -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 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*
* @author Andy Wilkinson
*/
public class HealthEndpointDocumentationTests extends AbstractEndpointDocumentationTests {
public class HealthEndpointDocumentationTests extends MockMvcEndpointDocumentationTests {
@Test
public void health() throws Exception {

View File

@@ -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 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Andy Wilkinson
*/
public class HeapDumpWebEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
extends MockMvcEndpointDocumentationTests {
@Test
public void heapDump() throws Exception {

View File

@@ -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 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*
* @author Andy Wilkinson
*/
public class InfoEndpointDocumentationTests extends AbstractEndpointDocumentationTests {
public class InfoEndpointDocumentationTests extends MockMvcEndpointDocumentationTests {
@Test
public void info() throws Exception {

View File

@@ -43,7 +43,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Andy Wilkinson
*/
public class LiquibaseEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
extends MockMvcEndpointDocumentationTests {
@Test
public void liquibase() throws Exception {

View File

@@ -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 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/
@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 {

View File

@@ -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 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Andy Wilkinson
*/
public class LoggersEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
extends MockMvcEndpointDocumentationTests {
private static final List<FieldDescriptor> levelFields = Arrays.asList(
fieldWithPath("configuredLevel")

View File

@@ -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<MappingDescriptionProvider> descriptionProviders,
ConfigurableApplicationContext context) {
return new MappingsEndpoint(descriptionProviders, context);
}
}
}

View File

@@ -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<MappingDescriptionProvider> descriptionProviders,
ConfigurableApplicationContext context) {
return new MappingsEndpoint(descriptionProviders, context);
}
}
}

View File

@@ -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 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Andy Wilkinson
*/
public class MetricsEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
extends MockMvcEndpointDocumentationTests {
@Test
public void metricNames() throws Exception {

View File

@@ -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;
}
}

View File

@@ -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 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Andy Wilkinson
*/
public class PrometheusScrapeEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
extends MockMvcEndpointDocumentationTests {
@Test
public void prometheus() throws Exception {

View File

@@ -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 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Andy Wilkinson
*/
public class ScheduledTasksEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
extends MockMvcEndpointDocumentationTests {
@Test
public void scheduledTasks() throws Exception {

View File

@@ -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 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/
@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));

View File

@@ -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 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Andy Wilkinson
*/
public class ShutdownEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
extends MockMvcEndpointDocumentationTests {
@Test
public void shutdown() throws Exception {

View File

@@ -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 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Andy Wilkinson
*/
public class ThreadDumpEndpointDocumentationTests
extends AbstractEndpointDocumentationTests {
extends MockMvcEndpointDocumentationTests {
@Test
public void threadDump() throws Exception {

View File

@@ -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.health.HealthEndpointAutoC
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<?>[] {});
}

View File

@@ -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<String, Object> result = this.endpoint.mappings();
assertThat(result).hasSize(1);
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) 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<String, Object> result = this.endpoint.mappings();
assertThat(result).hasSize(1);
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) result.get("/foo");
assertThat(map.get("bean")).isEqualTo("mapping");
}
@Test
public void beanUrlMappingsProxy() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
MappingConfiguration.class);
this.endpoint.setApplicationContext(context);
Map<String, Object> result = this.endpoint.mappings();
assertThat(result).hasSize(1);
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) 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<String, Object> result = this.endpoint.mappings();
assertThat(result).hasSize(2);
assertThat(result.keySet()).filteredOn((key) -> key.contains("[/actuator/test]"))
.hasOnlyOneElementSatisfying(
(key) -> assertThat((Map<String, Object>) result.get(key))
.containsOnlyKeys("bean", "method"));
assertThat(result.keySet()).filteredOn((key) -> key.contains("[/actuator]"))
.hasOnlyOneElementSatisfying(
(key) -> assertThat((Map<String, Object>) result.get(key))
.containsOnlyKeys("bean", "method"));
}
@SuppressWarnings("unchecked")
@Test
public void concreteMethodMappings() {
WebMvcEndpointHandlerMapping mapping = createHandlerMapping();
this.endpoint.setMethodMappings(Collections.singletonList(mapping));
Map<String, Object> result = this.endpoint.mappings();
assertThat(result).hasSize(2);
assertThat(result.keySet()).filteredOn((key) -> key.contains("[/actuator/test]"))
.hasOnlyOneElementSatisfying(
(key) -> assertThat((Map<String, Object>) result.get(key))
.containsOnlyKeys("method"));
assertThat(result.keySet()).filteredOn((key) -> key.contains("[/actuator]"))
.hasOnlyOneElementSatisfying(
(key) -> assertThat((Map<String, Object>) 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;
}
}
}

View File

@@ -66,6 +66,17 @@
<artifactId>jest</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
@@ -191,6 +202,11 @@
<artifactId>spring-data-redis</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-webmvc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-solr</artifactId>
@@ -281,22 +297,6 @@
<artifactId>reactor-netty</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>

View File

@@ -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);
}

View File

@@ -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<MappingDescriptionProvider> descriptionProviders;
private final ApplicationContext context;
public MappingsEndpoint(Collection<MappingDescriptionProvider> descriptionProviders,
ApplicationContext context) {
this.descriptionProviders = descriptionProviders;
this.context = context;
}
@ReadOperation
public ApplicationMappings mappings() {
ApplicationContext target = this.context;
Map<String, ContextMappings> 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<String, Object> 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<String, ContextMappings> contextMappings;
private ApplicationMappings(Map<String, ContextMappings> contextMappings) {
this.contextMappings = contextMappings;
}
public Map<String, ContextMappings> 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<String, Object> mappings;
private final String parentId;
private ContextMappings(Map<String, Object> mappings, String parentId) {
this.mappings = mappings;
this.parentId = parentId;
}
public String getParentId() {
return this.parentId;
}
public Map<String, Object> getMappings() {
return this.mappings;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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<HandlerMappingDescriptionProvider<? extends HandlerMapping>> descriptionProviders = Arrays
.asList(new RequestMappingInfoHandlerMappingDescriptionProvider(),
new UrlHandlerMappingDescriptionProvider(),
new RouterFunctionMappingDescriptionProvider());
@Override
public String getMappingName() {
return "dispatcherHandlers";
}
@Override
public Map<String, List<DispatcherHandlerMappingDescription>> describeMappings(
ApplicationContext context) {
Map<String, List<DispatcherHandlerMappingDescription>> mappings = new HashMap<>();
context.getBeansOfType(DispatcherHandler.class).forEach(
(name, handler) -> mappings.put(name, describeMappings(handler)));
return mappings;
}
private List<DispatcherHandlerMappingDescription> describeMappings(
DispatcherHandler dispatcherHandler) {
return dispatcherHandler.getHandlerMappings().stream().flatMap(this::describe)
.collect(Collectors.toList());
}
@SuppressWarnings("unchecked")
private <T extends HandlerMapping> Stream<DispatcherHandlerMappingDescription> describe(
T handlerMapping) {
for (HandlerMappingDescriptionProvider<?> descriptionProvider : descriptionProviders) {
if (descriptionProvider.getMappingClass().isInstance(handlerMapping)) {
return ((HandlerMappingDescriptionProvider<T>) descriptionProvider)
.describe(handlerMapping).stream();
}
}
return Stream.empty();
}
private interface HandlerMappingDescriptionProvider<T extends HandlerMapping> {
Class<T> getMappingClass();
List<DispatcherHandlerMappingDescription> describe(T handlerMapping);
}
private static final class RequestMappingInfoHandlerMappingDescriptionProvider
implements
HandlerMappingDescriptionProvider<RequestMappingInfoHandlerMapping> {
@Override
public Class<RequestMappingInfoHandlerMapping> getMappingClass() {
return RequestMappingInfoHandlerMapping.class;
}
@Override
public List<DispatcherHandlerMappingDescription> describe(
RequestMappingInfoHandlerMapping handlerMapping) {
Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping
.getHandlerMethods();
return handlerMethods.entrySet().stream().map(this::describe)
.collect(Collectors.toList());
}
private DispatcherHandlerMappingDescription describe(
Entry<RequestMappingInfo, HandlerMethod> mapping) {
return new DispatcherHandlerMappingDescription(mapping.getKey().toString(),
mapping.getValue().toString());
}
}
private static final class UrlHandlerMappingDescriptionProvider
implements HandlerMappingDescriptionProvider<AbstractUrlHandlerMapping> {
@Override
public Class<AbstractUrlHandlerMapping> getMappingClass() {
return AbstractUrlHandlerMapping.class;
}
@Override
public List<DispatcherHandlerMappingDescription> describe(
AbstractUrlHandlerMapping handlerMapping) {
return handlerMapping.getHandlerMap().entrySet().stream().map(this::describe)
.collect(Collectors.toList());
}
private DispatcherHandlerMappingDescription describe(
Entry<PathPattern, Object> mapping) {
return new DispatcherHandlerMappingDescription(
mapping.getKey().getPatternString(), mapping.getValue().toString());
}
}
private static final class RouterFunctionMappingDescriptionProvider
implements HandlerMappingDescriptionProvider<RouterFunctionMapping> {
@Override
public Class<RouterFunctionMapping> getMappingClass() {
return RouterFunctionMapping.class;
}
@Override
public List<DispatcherHandlerMappingDescription> 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<DispatcherHandlerMappingDescription> 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<ServerRequest, Mono<Resource>> lookupFunction) {
}
@Override
public void unknown(RouterFunction<?> routerFunction) {
}
}
}

View File

@@ -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<HandlerMapping> getHandlerMappings() {
List<HandlerMapping> 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<Context> 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
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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<HandlerMappingDescriptionProvider<? extends HandlerMapping>> descriptionProviders;
static {
List<HandlerMappingDescriptionProvider<? extends HandlerMapping>> 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<String, List<DispatcherServletMappingDescription>> describeMappings(
ApplicationContext context) {
if (context instanceof WebApplicationContext) {
return describeMappings((WebApplicationContext) context);
}
return Collections.emptyMap();
}
private Map<String, List<DispatcherServletMappingDescription>> describeMappings(
WebApplicationContext context) {
Map<String, List<DispatcherServletMappingDescription>> mappings = new HashMap<>();
context.getBeansOfType(DispatcherServlet.class)
.forEach((name, dispatcherServlet) -> mappings.put(name,
describeMappings(new DispatcherServletHandlerMappings(name,
dispatcherServlet, context))));
return mappings;
}
private List<DispatcherServletMappingDescription> describeMappings(
DispatcherServletHandlerMappings mappings) {
return mappings.getHandlerMappings().stream().flatMap(this::describe)
.collect(Collectors.toList());
}
private <T extends HandlerMapping> Stream<DispatcherServletMappingDescription> describe(
T handlerMapping) {
return describe(handlerMapping, descriptionProviders).stream();
}
@SuppressWarnings("unchecked")
private static <T extends HandlerMapping> List<DispatcherServletMappingDescription> describe(
T handlerMapping,
List<HandlerMappingDescriptionProvider<?>> descriptionProviders) {
for (HandlerMappingDescriptionProvider<?> descriptionProvider : descriptionProviders) {
if (descriptionProvider.getMappingClass().isInstance(handlerMapping)) {
return ((HandlerMappingDescriptionProvider<T>) descriptionProvider)
.describe(handlerMapping);
}
}
return Collections.emptyList();
}
private interface HandlerMappingDescriptionProvider<T extends HandlerMapping> {
Class<T> getMappingClass();
List<DispatcherServletMappingDescription> describe(T handlerMapping);
}
private static final class RequestMappingInfoHandlerMappingDescriptionProvider
implements
HandlerMappingDescriptionProvider<RequestMappingInfoHandlerMapping> {
@Override
public Class<RequestMappingInfoHandlerMapping> getMappingClass() {
return RequestMappingInfoHandlerMapping.class;
}
@Override
public List<DispatcherServletMappingDescription> describe(
RequestMappingInfoHandlerMapping handlerMapping) {
Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping
.getHandlerMethods();
return handlerMethods.entrySet().stream().map(this::describe)
.collect(Collectors.toList());
}
private DispatcherServletMappingDescription describe(
Entry<RequestMappingInfo, HandlerMethod> mapping) {
return new DispatcherServletMappingDescription(mapping.getKey().toString(),
mapping.getValue().toString());
}
}
private static final class UrlHandlerMappingDescriptionProvider
implements HandlerMappingDescriptionProvider<AbstractUrlHandlerMapping> {
@Override
public Class<AbstractUrlHandlerMapping> getMappingClass() {
return AbstractUrlHandlerMapping.class;
}
@Override
public List<DispatcherServletMappingDescription> describe(
AbstractUrlHandlerMapping handlerMapping) {
return handlerMapping.getHandlerMap().entrySet().stream().map(this::describe)
.collect(Collectors.toList());
}
private DispatcherServletMappingDescription describe(
Entry<String, Object> mapping) {
return new DispatcherServletMappingDescription(mapping.getKey().toString(),
mapping.getValue().toString());
}
}
private static final class DelegatingHandlerMappingDescriptionProvider
implements HandlerMappingDescriptionProvider<DelegatingHandlerMapping> {
private final List<HandlerMappingDescriptionProvider<?>> descriptionProviders;
private DelegatingHandlerMappingDescriptionProvider(
List<HandlerMappingDescriptionProvider<?>> descriptionProviders) {
this.descriptionProviders = descriptionProviders;
}
@Override
public Class<DelegatingHandlerMapping> getMappingClass() {
return DelegatingHandlerMapping.class;
}
@Override
public List<DispatcherServletMappingDescription> describe(
DelegatingHandlerMapping handlerMapping) {
List<DispatcherServletMappingDescription> descriptions = new ArrayList<>();
for (HandlerMapping delegate : handlerMapping.getDelegates()) {
descriptions.addAll(DispatcherServletsMappingDescriptionProvider
.describe(delegate, this.descriptionProviders));
}
return descriptions;
}
}
}

View File

@@ -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<FilterRegistration> {
/**
* 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<String> getServletNameMappings() {
return this.getRegistration().getServletNameMappings();
}
/**
* Returns the URL pattern mappings for the registered filter.
*
* @return the mappings
*/
public Collection<String> getUrlPatternMappings() {
return this.getRegistration().getUrlPatternMappings();
}
}

View File

@@ -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<FilterRegistrationMappingDescription> 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";
}
}

View File

@@ -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 <T> type of the registration
* @author Andy Wilkinson
* @since 2.0.0
*/
public class RegistrationMappingDescription<T extends Registration> {
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;
}
}

View File

@@ -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<ServletRegistration> {
/**
* 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<String> getMappings() {
return getRegistration().getMappings();
}
}

View File

@@ -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<ServletRegistrationMappingDescription> 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";
}
}

View File

@@ -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<String>().elements());
given(servletContext.getAttributeNames())
.willReturn(new Vector<String>().elements());
FilterRegistration filterRegistration = mock(FilterRegistration.class);
given((Map<String, FilterRegistration>) servletContext.getFilterRegistrations())
.willReturn(Collections.singletonMap("testFilter", filterRegistration));
ServletRegistration servletRegistration = mock(ServletRegistration.class);
given((Map<String, ServletRegistration>) 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<String, List<DispatcherServletMappingDescription>> dispatcherServlets = mappings(
contextMappings, "dispatcherServlets");
assertThat(dispatcherServlets).containsOnlyKeys("dispatcherServlet");
List<DispatcherServletMappingDescription> handlerMappings = dispatcherServlets
.get("dispatcherServlet");
assertThat(handlerMappings).hasSize(1);
List<ServletRegistrationMappingDescription> servlets = mappings(
contextMappings, "servlets");
assertThat(servlets).hasSize(1);
List<FilterRegistrationMappingDescription> 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<String, List<DispatcherHandlerMappingDescription>> dispatcherHandlers = mappings(
contextMappings, "dispatcherHandlers");
assertThat(dispatcherHandlers).containsOnlyKeys("webHandler");
List<DispatcherHandlerMappingDescription> 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> T mappings(ContextMappings contextMappings, String key) {
return (T) contextMappings.getMappings().get(key);
}
@Configuration
static class EndpointConfiguration {
@Bean
public MappingsEndpoint mappingsEndpoint(
Collection<MappingDescriptionProvider> descriptionProviders,
ApplicationContext context) {
return new MappingsEndpoint(descriptionProviders, context);
}
}
@Configuration
@EnableWebFlux
@Controller
static class ReactiveWebConfiguration {
@Bean
public DispatcherHandlersMappingDescriptionProvider dispatcherHandlersMappingDescriptionProvider() {
return new DispatcherHandlersMappingDescriptionProvider();
}
@Bean
public RouterFunction<ServerResponse> 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() {
}
}
}

View File

@@ -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 class UndertowServletWebServer implements WebServer {
}
}
public DeploymentManager getDeploymentManager() {
synchronized (this.monitor) {
return this.manager;
}
}
private void stopSilently() {
try {
if (this.undertow != null) {