From 56afc25304663e4e6d69578780cee0092e57b951 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 26 Oct 2017 14:07:50 +0200 Subject: [PATCH] Allow to customize the path of a web endpoint This commit introduces a endpoints..web.path generic property that allows to customize the path of an endpoint. By default the path is the same as the id of the endpoint. Such customization does not apply for the CloudFoundry specific endpoints. Closes gh-10181 --- ...CloudFoundryActuatorAutoConfiguration.java | 2 +- .../endpoint/DefaultEndpointPathResolver.java | 42 ++++++++++++++++ .../endpoint/EndpointAutoConfiguration.java | 13 ++++- ...FoundryActuatorAutoConfigurationTests.java | 16 ++++++ ...FoundryMvcWebEndpointIntegrationTests.java | 2 +- .../DefaultEndpointPathResolverTests.java | 49 +++++++++++++++++++ .../endpoint/web/EndpointPathResolver.java | 35 +++++++++++++ .../WebAnnotationEndpointDiscoverer.java | 17 +++++-- .../AbstractWebEndpointIntegrationTests.java | 2 +- .../WebAnnotationEndpointDiscovererTests.java | 26 ++++++++-- .../web/test/JerseyEndpointsRunner.java | 2 +- .../web/test/WebFluxEndpointsRunner.java | 2 +- .../web/test/WebMvcEndpointRunner.java | 2 +- .../appendix-application-properties.adoc | 20 ++++++++ ...figurationMetadataAnnotationProcessor.java | 4 ++ ...ationMetadataAnnotationProcessorTests.java | 36 ++++++++++---- 16 files changed, 244 insertions(+), 26 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/DefaultEndpointPathResolver.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/DefaultEndpointPathResolverTests.java create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointPathResolver.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java index 87d8eccc285..bebab073d69 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryActuatorAutoConfiguration.java @@ -82,7 +82,7 @@ public class CloudFoundryActuatorAutoConfiguration { RestTemplateBuilder builder) { WebAnnotationEndpointDiscoverer endpointDiscoverer = new WebAnnotationEndpointDiscoverer( this.applicationContext, parameterMapper, cachingConfigurationFactory, - endpointMediaTypes); + endpointMediaTypes, (id) -> id); return new CloudFoundryWebEndpointServletHandlerMapping( new EndpointMapping("/cloudfoundryapplication"), endpointDiscoverer.discoverEndpoints(), endpointMediaTypes, diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/DefaultEndpointPathResolver.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/DefaultEndpointPathResolver.java new file mode 100644 index 00000000000..ef521532f3a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/DefaultEndpointPathResolver.java @@ -0,0 +1,42 @@ +/* + * 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.endpoint; + +import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver; +import org.springframework.core.env.Environment; + +/** + * Default {@link EndpointPathResolver} implementation that use the + * {@link Environment} to determine if an endpoint has a custom path. + * + * @author Stephane Nicoll + */ +class DefaultEndpointPathResolver implements EndpointPathResolver { + + private final Environment environment; + + DefaultEndpointPathResolver(Environment environment) { + this.environment = environment; + } + + @Override + public String resolvePath(String endpointId) { + String key = String.format("endpoints.%s.web.path", endpointId); + return this.environment.getProperty(key, String.class, endpointId); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java index 35863b6a102..fea91af2e5e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.java @@ -26,6 +26,7 @@ import org.springframework.boot.actuate.endpoint.cache.CachingConfigurationFacto import org.springframework.boot.actuate.endpoint.convert.ConversionServiceOperationParameterMapper; import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; +import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver; import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation; import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -77,14 +78,22 @@ public class EndpointAutoConfiguration { return new EndpointMediaTypes(MEDIA_TYPES, MEDIA_TYPES); } + @Bean + @ConditionalOnMissingBean + public EndpointPathResolver endpointPathResolver( + Environment environment) { + return new DefaultEndpointPathResolver(environment); + } + @Bean public EndpointProvider webEndpointProvider( OperationParameterMapper parameterMapper, - DefaultCachingConfigurationFactory cachingConfigurationFactory) { + DefaultCachingConfigurationFactory cachingConfigurationFactory, + EndpointPathResolver endpointPathResolver) { Environment environment = this.applicationContext.getEnvironment(); WebAnnotationEndpointDiscoverer endpointDiscoverer = new WebAnnotationEndpointDiscoverer( this.applicationContext, parameterMapper, cachingConfigurationFactory, - endpointMediaTypes()); + endpointMediaTypes(), endpointPathResolver); return new EndpointProvider<>(environment, endpointDiscoverer, EndpointExposure.WEB); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java index 8c0f729b7c0..670ce822228 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryActuatorAutoConfigurationTests.java @@ -219,6 +219,22 @@ public class CloudFoundryActuatorAutoConfigurationTests { assertThat(endpoints.get(0).getId()).isEqualTo("test"); } + @Test + public void endpointPathCustomizationIsNotApplied() + throws Exception { + TestPropertyValues.of("endpoints.test.web.path=another/custom") + .applyTo(this.context); + this.context.register(TestConfiguration.class); + this.context.refresh(); + CloudFoundryWebEndpointServletHandlerMapping handlerMapping = getHandlerMapping(); + List> endpoints = (List>) handlerMapping + .getEndpoints(); + assertThat(endpoints.size()).isEqualTo(1); + assertThat(endpoints.get(0).getOperations()).hasSize(1); + assertThat(endpoints.get(0).getOperations().iterator().next() + .getRequestPredicate().getPath()).isEqualTo("test"); + } + private CloudFoundryWebEndpointServletHandlerMapping getHandlerMapping() { TestPropertyValues .of("VCAP_APPLICATION:---", "vcap.application.application_id:my-app-id", diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryMvcWebEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryMvcWebEndpointIntegrationTests.java index 6500dcb418d..1199f0da8f5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryMvcWebEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryMvcWebEndpointIntegrationTests.java @@ -219,7 +219,7 @@ public class CloudFoundryMvcWebEndpointIntegrationTests { DefaultConversionService.getSharedInstance()); return new WebAnnotationEndpointDiscoverer(applicationContext, parameterMapper, (id) -> new CachingConfiguration(0), - endpointMediaTypes); + endpointMediaTypes, (id) -> id); } @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/DefaultEndpointPathResolverTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/DefaultEndpointPathResolverTests.java new file mode 100644 index 00000000000..3e2c10fbaa5 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/DefaultEndpointPathResolverTests.java @@ -0,0 +1,49 @@ +/* + * 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.endpoint; + +import org.junit.Test; + +import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver; +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DefaultEndpointPathResolver}. + * + * @author Stephane Nicoll + */ +public class DefaultEndpointPathResolverTests { + + private final MockEnvironment environment = new MockEnvironment(); + + private final EndpointPathResolver resolver = new DefaultEndpointPathResolver( + this.environment); + + @Test + public void defaultConfiguration() { + assertThat(this.resolver.resolvePath("test")).isEqualTo("test"); + } + + @Test + public void userConfiguration() { + this.environment.setProperty("endpoints.test.web.path", "custom"); + assertThat(this.resolver.resolvePath("test")).isEqualTo("custom"); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointPathResolver.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointPathResolver.java new file mode 100644 index 00000000000..073ef44795c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointPathResolver.java @@ -0,0 +1,35 @@ +/* + * 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.endpoint.web; + +/** + * Resolve the path of an endpoint. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +@FunctionalInterface +public interface EndpointPathResolver { + + /** + * Resolve the path for the endpoint with the specified {@code endpointId}. + * @param endpointId the id of an endpoint + * @return the path of the endpoint + */ + String resolvePath(String endpointId); + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebAnnotationEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebAnnotationEndpointDiscoverer.java index d83199ffb50..ae949d753c6 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebAnnotationEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebAnnotationEndpointDiscoverer.java @@ -40,6 +40,7 @@ import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration; import org.springframework.boot.actuate.endpoint.cache.CachingConfigurationFactory; import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvoker; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; +import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver; import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate; import org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod; import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation; @@ -71,14 +72,17 @@ public class WebAnnotationEndpointDiscoverer extends * @param cachingConfigurationFactory the {@link CachingConfiguration} factory to use * @param endpointMediaTypes the media types produced and consumed by web endpoint * operations + * @param endpointPathResolver the {@link EndpointPathResolver} used to resolve + * endpoint paths */ public WebAnnotationEndpointDiscoverer(ApplicationContext applicationContext, OperationParameterMapper operationParameterMapper, CachingConfigurationFactory cachingConfigurationFactory, - EndpointMediaTypes endpointMediaTypes) { + EndpointMediaTypes endpointMediaTypes, + EndpointPathResolver endpointPathResolver) { super(applicationContext, new WebEndpointOperationFactory(operationParameterMapper, - endpointMediaTypes), + endpointMediaTypes, endpointPathResolver), WebEndpointOperation::getRequestPredicate, cachingConfigurationFactory); } @@ -121,10 +125,14 @@ public class WebAnnotationEndpointDiscoverer extends private final EndpointMediaTypes endpointMediaTypes; + private final EndpointPathResolver endpointPathResolver; + private WebEndpointOperationFactory(OperationParameterMapper parameterMapper, - EndpointMediaTypes endpointMediaTypes) { + EndpointMediaTypes endpointMediaTypes, + EndpointPathResolver endpointPathResolver) { this.parameterMapper = parameterMapper; this.endpointMediaTypes = endpointMediaTypes; + this.endpointPathResolver = endpointPathResolver; } @Override @@ -147,7 +155,8 @@ public class WebAnnotationEndpointDiscoverer extends } private String determinePath(String endpointId, Method operationMethod) { - StringBuilder path = new StringBuilder(endpointId); + StringBuilder path = new StringBuilder( + this.endpointPathResolver.resolvePath(endpointId)); Stream.of(operationMethod.getParameters()) .filter(( parameter) -> parameter.getAnnotation(Selector.class) != null) diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/AbstractWebEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/AbstractWebEndpointIntegrationTests.java index 218ce4acca5..10af0338612 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/AbstractWebEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/AbstractWebEndpointIntegrationTests.java @@ -385,7 +385,7 @@ public abstract class AbstractWebEndpointIntegrationTests new CachingConfiguration(0), - endpointMediaTypes()); + endpointMediaTypes(), (id) -> id); } @Bean diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebAnnotationEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebAnnotationEndpointDiscovererTests.java index 928df7d99f0..8c763338699 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebAnnotationEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebAnnotationEndpointDiscovererTests.java @@ -189,8 +189,8 @@ public class WebAnnotationEndpointDiscovererTests { @Test public void endpointMainReadOperationIsCachedWithMatchingId() { - load((id) -> new CachingConfiguration(500), TestEndpointConfiguration.class, - (discoverer) -> { + load((id) -> new CachingConfiguration(500), (id) -> id, + TestEndpointConfiguration.class, (discoverer) -> { Map> endpoints = mapEndpoints( discoverer.discoverEndpoints()); assertThat(endpoints).containsOnlyKeys("test"); @@ -237,12 +237,29 @@ public class WebAnnotationEndpointDiscovererTests { }); } + @Test + public void endpointPathCanBeCustomized() { + load((id) -> null, (id) -> "custom/" + id, + AdditionalOperationWebEndpointConfiguration.class, (discoverer) -> { + Map> endpoints = mapEndpoints( + discoverer.discoverEndpoints()); + assertThat(endpoints).containsOnlyKeys("test"); + EndpointInfo endpoint = endpoints.get("test"); + assertThat(requestPredicates(endpoint)).has(requestPredicates( + path("custom/test").httpMethod(WebEndpointHttpMethod.GET).consumes() + .produces("application/json"), + path("custom/test/{id}").httpMethod(WebEndpointHttpMethod.GET).consumes() + .produces("application/json"))); + }); + } + private void load(Class configuration, Consumer consumer) { - this.load((id) -> null, configuration, consumer); + this.load((id) -> null, (id) -> id, configuration, consumer); } private void load(CachingConfigurationFactory cachingConfigurationFactory, + EndpointPathResolver endpointPathResolver, Class configuration, Consumer consumer) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( configuration); @@ -254,7 +271,8 @@ public class WebAnnotationEndpointDiscovererTests { cachingConfigurationFactory, new EndpointMediaTypes( Collections.singletonList("application/json"), - Collections.singletonList("application/json")))); + Collections.singletonList("application/json")), + endpointPathResolver)); } finally { context.close(); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/JerseyEndpointsRunner.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/JerseyEndpointsRunner.java index 0d9df61e7bf..b294c3e08e8 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/JerseyEndpointsRunner.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/JerseyEndpointsRunner.java @@ -99,7 +99,7 @@ class JerseyEndpointsRunner extends AbstractWebEndpointRunner { WebAnnotationEndpointDiscoverer discoverer = new WebAnnotationEndpointDiscoverer( this.applicationContext, new ConversionServiceOperationParameterMapper(), (id) -> null, - endpointMediaTypes); + endpointMediaTypes, (id) -> id); Collection resources = new JerseyEndpointResourceFactory() .createEndpointResources(new EndpointMapping("/application"), discoverer.discoverEndpoints(), endpointMediaTypes); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebFluxEndpointsRunner.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebFluxEndpointsRunner.java index e6bbf51ce05..415106bed6a 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebFluxEndpointsRunner.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebFluxEndpointsRunner.java @@ -105,7 +105,7 @@ class WebFluxEndpointsRunner extends AbstractWebEndpointRunner { WebAnnotationEndpointDiscoverer discoverer = new WebAnnotationEndpointDiscoverer( this.applicationContext, new ConversionServiceOperationParameterMapper(), (id) -> null, - endpointMediaTypes); + endpointMediaTypes, (id) -> id); return new WebFluxEndpointHandlerMapping(new EndpointMapping("/application"), discoverer.discoverEndpoints(), endpointMediaTypes, new CorsConfiguration()); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebMvcEndpointRunner.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebMvcEndpointRunner.java index 3d5c6bf75c5..f19a6a7eeb1 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebMvcEndpointRunner.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/test/WebMvcEndpointRunner.java @@ -88,7 +88,7 @@ class WebMvcEndpointRunner extends AbstractWebEndpointRunner { WebAnnotationEndpointDiscoverer discoverer = new WebAnnotationEndpointDiscoverer( this.applicationContext, new ConversionServiceOperationParameterMapper(), (id) -> null, - endpointMediaTypes); + endpointMediaTypes, (id) -> id); return new WebMvcEndpointHandlerMapping(new EndpointMapping("/application"), discoverer.discoverEndpoints(), endpointMediaTypes, new CorsConfiguration()); diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 72c3ef2313d..628d762a08d 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -1077,18 +1077,21 @@ content into your application; rather pick only the properties that you need. endpoints.auditevents.enabled= # Enable the auditevents endpoint. endpoints.auditevents.jmx.enabled= # Expose the auditevents endpoint as a JMX MBean. endpoints.auditevents.web.enabled= # Expose the auditevents endpoint as a Web endpoint. + endpoints.auditevents.web.path=auditevents # Path of the auditevents endpoint. # AUTO-CONFIGURATION REPORT ENDPOINT ({sc-spring-boot-actuator-autoconfigure}/condition/AutoConfigurationReportEndpoint.{sc-ext}[AutoConfigurationReportEndpoint]) endpoints.autoconfig.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. endpoints.autoconfig.enabled= # Enable the autoconfig endpoint. endpoints.autoconfig.jmx.enabled= # Expose the autoconfig endpoint as a JMX MBean. endpoints.autoconfig.web.enabled= # Expose the autoconfig endpoint as a Web endpoint. + endpoints.autoconfig.web.path=autoconfig # Path of the autoconfig endpoint. # BEANS ENDPOINT ({sc-spring-boot-actuator}/beans/BeansEndpoint.{sc-ext}[BeansEndpoint]) endpoints.beans.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. endpoints.beans.enabled= # Enable the beans endpoint. endpoints.beans.jmx.enabled= # Expose the beans endpoint as a JMX MBean. endpoints.beans.web.enabled= # Expose the beans endpoint as a Web endpoint. + endpoints.beans.web.path=beans # Path of the beans endpoint. # CONFIGURATION PROPERTIES REPORT ENDPOINT ({sc-spring-boot-actuator}/context/properties/ConfigurationPropertiesReportEndpoint.{sc-ext}[ConfigurationPropertiesReportEndpoint]) endpoints.configprops.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. @@ -1096,6 +1099,7 @@ content into your application; rather pick only the properties that you need. endpoints.configprops.jmx.enabled= # Expose the configprops endpoint as a JMX MBean. endpoints.configprops.keys-to-sanitize=password,secret,key,token,.*credentials.*,vcap_services # Keys that should be sanitized. Keys can be simple strings that the property ends with or regular expressions. endpoints.configprops.web.enabled= # Expose the configprops endpoint as a Web endpoint. + endpoints.configprops.web.path=configprops # Path of the configprops endpoint. # ENDPOINT DEFAULT SETTINGS endpoints.default.enabled=true # Enable all endpoints by default. @@ -1108,94 +1112,110 @@ content into your application; rather pick only the properties that you need. endpoints.env.jmx.enabled= # Expose the env endpoint as a JMX MBean. endpoints.env.keys-to-sanitize=password,secret,key,token,.*credentials.*,vcap_services # Keys that should be sanitized. Keys can be simple strings that the property ends with or regular expressions. endpoints.env.web.enabled= # Expose the env endpoint as a Web endpoint. + endpoints.env.web.path=env # Path of the env endpoint. # FLYWAY ENDPOINT ({sc-spring-boot-actuator}/flyway/FlywayEndpoint.{sc-ext}[FlywayEndpoint]) endpoints.flyway.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. endpoints.flyway.enabled= # Enable the flyway endpoint. endpoints.flyway.jmx.enabled= # Expose the flyway endpoint as a JMX MBean. endpoints.flyway.web.enabled= # Expose the flyway endpoint as a Web endpoint. + endpoints.flyway.web.path=flyway # Path of the flyway endpoint. # HEALTH ENDPOINT ({sc-spring-boot-actuator}/health/HealthEndpoint.{sc-ext}[HealthEndpoint]) endpoints.health.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. endpoints.health.enabled= # Enable the health endpoint. endpoints.health.jmx.enabled= # Expose the health endpoint as a JMX MBean. endpoints.health.web.enabled= # Expose the health endpoint as a Web endpoint. + endpoints.health.web.path=health # Path of the health endpoint. # HEAP DUMP ENDPOINT ({sc-spring-boot-actuator}/management/HeapDumpWebEndpoint.{sc-ext}[HeapDumpWebEndpoint]) endpoints.heapdump.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. endpoints.heapdump.enabled= # Enable the heapdump endpoint. endpoints.heapdump.web.enabled= # Expose the heapdump endpoint as a Web endpoint. + endpoints.heapdump.web.path=heapdump # Path of the heapdump endpoint. # INFO ENDPOINT ({sc-spring-boot-actuator}/info/InfoEndpoint.{sc-ext}[InfoEndpoint]) endpoints.info.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. endpoints.info.enabled=true # Enable the info endpoint. endpoints.info.jmx.enabled=true # Expose the info endpoint as a JMX MBean. endpoints.info.web.enabled=true # Expose the info endpoint as a Web endpoint. + endpoints.info.web.path=info # Path of the info endpoint. # LIQUIBASE ENDPOINT ({sc-spring-boot-actuator}/liquibase/LiquibaseEndpoint.{sc-ext}[LiquibaseEndpoint]) endpoints.liquibase.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. endpoints.liquibase.enabled= # Enable the liquibase endpoint. endpoints.liquibase.jmx.enabled= # Expose the liquibase endpoint as a JMX MBean. endpoints.liquibase.web.enabled= # Expose the liquibase endpoint as a Web endpoint. + endpoints.liquibase.web.path=liquibase # Path of the liquibase endpoint. # LOG FILE ENDPOINT ({sc-spring-boot-actuator}/logging/LogFileWebEndpoint.{sc-ext}[LogFileWebEndpoint]) endpoints.logfile.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. endpoints.logfile.enabled= # Enable the logfile endpoint. endpoints.logfile.external-file= # External Logfile to be accessed. Can be used if the logfile is written by output redirect and not by the logging system itself. endpoints.logfile.web.enabled= # Expose the logfile endpoint as a Web endpoint. + endpoints.logfile.web.path=logfile # Path of the logfile endpoint. # LOGGERS ENDPOINT ({sc-spring-boot-actuator}/logging/LoggersEndpoint.{sc-ext}[LoggersEndpoint]) endpoints.loggers.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. endpoints.loggers.enabled= # Enable the loggers endpoint. endpoints.loggers.jmx.enabled= # Expose the loggers endpoint as a JMX MBean. endpoints.loggers.web.enabled= # Expose the loggers endpoint as a Web endpoint. + endpoints.loggers.web.path=loggers # Path of the loggers endpoint. # REQUEST MAPPING ENDPOINT ({sc-spring-boot-actuator-autoconfigure}/web/servlet/RequestMappingEndpoint.{sc-ext}[RequestMappingEndpoint]) endpoints.mappings.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. endpoints.mappings.enabled= # Enable the mappings endpoint. endpoints.mappings.jmx.enabled= # Expose the mappings endpoint as a JMX MBean. endpoints.mappings.web.enabled= # Expose the mappings endpoint as a Web endpoint. + endpoints.mappings.web.path=mappings # Path of the mappings endpoint. # METRICS ENDPOINT ({sc-spring-boot-actuator}/metrics/MetricsEndpoint.{sc-ext}[MetricsEndpoint]) endpoints.metrics.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. endpoints.metrics.enabled= # Enable the metrics endpoint. endpoints.metrics.jmx.enabled= # Expose the metrics endpoint as a JMX MBean. endpoints.metrics.web.enabled= # Expose the metrics endpoint as a Web endpoint. + endpoints.metrics.web.path=metrics # Path of the metrics endpoint. # PROMETHEUS ENDPOINT ({sc-spring-boot-actuator}/metrics/export/prometheus/PrometheusScrapeEndpoint.{sc-ext}[PrometheusScrapeEndpoint]) endpoints.prometheus.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. endpoints.prometheus.enabled= # Enable the metrics endpoint. endpoints.prometheus.web.enabled= # Expose the metrics endpoint as a Web endpoint. + endpoints.prometheus.web.path=prometheus # Path of the prometheus endpoint. # SESSIONS ENDPOINT ({sc-spring-boot-actuator}/session/SessionsEndpoint.{sc-ext}[SessionsEndpoint]) endpoints.sessions.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. endpoints.sessions.enabled= # Enable the sessions endpoint. endpoints.sessions.jmx.enabled= # Expose the sessions endpoint as a JMX MBean. endpoints.sessions.web.enabled= # Expose the sessions endpoint as a Web endpoint. + endpoints.sessions.web.path=sessions # Path of the sessions endpoint. # SHUTDOWN ENDPOINT ({sc-spring-boot-actuator}/context/ShutdownEndpoint.{sc-ext}[ShutdownEndpoint]) endpoints.shutdown.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. endpoints.shutdown.enabled=false # Enable the shutdown endpoint. endpoints.shutdown.jmx.enabled=false # Expose the shutdown endpoint as a JMX MBean. endpoints.shutdown.web.enabled=false # Expose the shutdown endpoint as a Web endpoint. + endpoints.shutdown.web.path=shutdown # Path of the shutdown endpoint. # STATUS ENDPOINT ({sc-spring-boot-actuator}/health/StatusEndpoint.{sc-ext}[StatusEndpoint]) endpoints.status.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. endpoints.status.enabled=true # Enable the status endpoint. endpoints.status.jmx.enabled=true # Expose the status endpoint as a JMX MBean. endpoints.status.web.enabled=true # Expose the status endpoint as a Web endpoint. + endpoints.status.web.path=status # Path of the status endpoint. # THREAD DUMP ENDPOINT ({sc-spring-boot-actuator}/management/ThreadDumpEndpoint.{sc-ext}[ThreadDumpEndpoint]) endpoints.threaddump.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. endpoints.threaddump.enabled= # Enable the threaddump endpoint. endpoints.threaddump.jmx.enabled= # Expose the threaddump endpoint as a JMX MBean. endpoints.threaddump.web.enabled= # Expose the threaddump endpoint as a Web endpoint. + endpoints.threaddump.web.path=threaddump # Path of the threaddump endpoint. # TRACE ENDPOINT ({sc-spring-boot-actuator}/trace/TraceEndpoint.{sc-ext}[TraceEndpoint]) endpoints.trace.cache.time-to-live=0 # Maximum time in milliseconds that a response can be cached. endpoints.trace.enabled= # Enable the trace endpoint. endpoints.trace.jmx.enabled= # Expose the trace endpoint as a JMX MBean. endpoints.trace.web.enabled= # Expose the trace endpoint as a Web endpoint. + endpoints.trace.web.path=trace # Path of the trace endpoint. # MANAGEMENT HTTP SERVER ({sc-spring-boot-actuator-autoconfigure}/web/server/ManagementServerProperties.{sc-ext}[ManagementServerProperties]) management.server.add-application-context-header=false # Add the "X-Application-Context" HTTP header in each response. Requires a custom management.server.port. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java index 1d264fe818d..7c798bde0d8 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java @@ -394,6 +394,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor type, null, String.format("Expose the %s endpoint as a Web endpoint.", endpointId), enabledByDefault, null)); + this.metadataCollector.add(ItemMetadata.newProperty( + endpointKey(endpointId), "web.path", String.class.getName(), type, + null, String.format("Path of the %s endpoint.", endpointId), + endpointId, null)); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java index 111a6ad3bbe..c4b47c34027 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java @@ -539,8 +539,9 @@ public class ConfigurationMetadataAnnotationProcessorTests { assertThat(metadata).has(enabledFlag("simple", null)); assertThat(metadata).has(jmxEnabledFlag("simple", null)); assertThat(metadata).has(webEnabledFlag("simple", null)); + assertThat(metadata).has(webPath("simple")); assertThat(metadata).has(cacheTtl("simple")); - assertThat(metadata.getItems()).hasSize(5); + assertThat(metadata.getItems()).hasSize(6); } @Test @@ -551,8 +552,9 @@ public class ConfigurationMetadataAnnotationProcessorTests { assertThat(metadata).has(enabledFlag("disabled", false)); assertThat(metadata).has(jmxEnabledFlag("disabled", false)); assertThat(metadata).has(webEnabledFlag("disabled", false)); + assertThat(metadata).has(webPath("disabled")); assertThat(metadata).has(cacheTtl("disabled")); - assertThat(metadata.getItems()).hasSize(5); + assertThat(metadata.getItems()).hasSize(6); } @Test @@ -563,8 +565,9 @@ public class ConfigurationMetadataAnnotationProcessorTests { assertThat(metadata).has(enabledFlag("enabled", true)); assertThat(metadata).has(jmxEnabledFlag("enabled", true)); assertThat(metadata).has(webEnabledFlag("enabled", true)); + assertThat(metadata).has(webPath("enabled")); assertThat(metadata).has(cacheTtl("enabled")); - assertThat(metadata.getItems()).hasSize(5); + assertThat(metadata.getItems()).hasSize(6); } @Test @@ -577,8 +580,9 @@ public class ConfigurationMetadataAnnotationProcessorTests { assertThat(metadata).has(enabledFlag("customprops", null)); assertThat(metadata).has(jmxEnabledFlag("customprops", null)); assertThat(metadata).has(webEnabledFlag("customprops", null)); + assertThat(metadata).has(webPath("customprops")); assertThat(metadata).has(cacheTtl("customprops")); - assertThat(metadata.getItems()).hasSize(6); + assertThat(metadata.getItems()).hasSize(7); } @Test @@ -599,8 +603,9 @@ public class ConfigurationMetadataAnnotationProcessorTests { Metadata.withGroup("endpoints.web").fromSource(OnlyWebEndpoint.class)); assertThat(metadata).has(enabledFlag("web", null)); assertThat(metadata).has(webEnabledFlag("web", null)); + assertThat(metadata).has(webPath("web")); assertThat(metadata).has(cacheTtl("web")); - assertThat(metadata.getItems()).hasSize(4); + assertThat(metadata.getItems()).hasSize(5); } @Test @@ -613,8 +618,9 @@ public class ConfigurationMetadataAnnotationProcessorTests { assertThat(metadata).has(enabledFlag("incremental", null)); assertThat(metadata).has(jmxEnabledFlag("incremental", null)); assertThat(metadata).has(webEnabledFlag("incremental", null)); + assertThat(metadata).has(webPath("incremental")); assertThat(metadata).has(cacheTtl("incremental")); - assertThat(metadata.getItems()).hasSize(5); + assertThat(metadata.getItems()).hasSize(6); project.replaceText(IncrementalEndpoint.class, "id = \"incremental\"", "id = \"incremental\", defaultEnablement = org.springframework.boot." + "configurationsample.DefaultEnablement.DISABLED"); @@ -624,8 +630,9 @@ public class ConfigurationMetadataAnnotationProcessorTests { assertThat(metadata).has(enabledFlag("incremental", false)); assertThat(metadata).has(jmxEnabledFlag("incremental", false)); assertThat(metadata).has(webEnabledFlag("incremental", false)); + assertThat(metadata).has(webPath("incremental")); assertThat(metadata).has(cacheTtl("incremental")); - assertThat(metadata.getItems()).hasSize(5); + assertThat(metadata.getItems()).hasSize(6); } @Test @@ -638,8 +645,9 @@ public class ConfigurationMetadataAnnotationProcessorTests { assertThat(metadata).has(enabledFlag("incremental", null)); assertThat(metadata).has(jmxEnabledFlag("incremental", null)); assertThat(metadata).has(webEnabledFlag("incremental", null)); + assertThat(metadata).has(webPath("incremental")); assertThat(metadata).has(cacheTtl("incremental")); - assertThat(metadata.getItems()).hasSize(5); + assertThat(metadata.getItems()).hasSize(6); project.replaceText(IncrementalEndpoint.class, "id = \"incremental\"", "id = \"incremental\", exposure = org.springframework.boot." + "configurationsample.EndpointExposure.WEB"); @@ -648,8 +656,9 @@ public class ConfigurationMetadataAnnotationProcessorTests { .fromSource(IncrementalEndpoint.class)); assertThat(metadata).has(enabledFlag("incremental", null)); assertThat(metadata).has(webEnabledFlag("incremental", null)); + assertThat(metadata).has(webPath("incremental")); assertThat(metadata).has(cacheTtl("incremental")); - assertThat(metadata.getItems()).hasSize(4); + assertThat(metadata.getItems()).hasSize(5); } @Test @@ -671,8 +680,9 @@ public class ConfigurationMetadataAnnotationProcessorTests { assertThat(metadata).has(enabledFlag("incremental", null)); assertThat(metadata).has(jmxEnabledFlag("incremental", null)); assertThat(metadata).has(webEnabledFlag("incremental", null)); + assertThat(metadata).has(webPath("incremental")); assertThat(metadata).has(cacheTtl("incremental")); - assertThat(metadata.getItems()).hasSize(5); + assertThat(metadata.getItems()).hasSize(6); } private Metadata.MetadataItemCondition enabledFlag(String endpointId, @@ -696,6 +706,12 @@ public class ConfigurationMetadataAnnotationProcessorTests { .format("Expose the %s endpoint as a Web endpoint.", endpointId)); } + private Metadata.MetadataItemCondition webPath(String endpointId) { + return Metadata.withProperty("endpoints." + endpointId + ".web.path") + .ofType(String.class).withDefaultValue(endpointId).withDescription(String + .format("Path of the %s endpoint.", endpointId)); + } + private Metadata.MetadataItemCondition cacheTtl(String endpointId) { return Metadata.withProperty("endpoints." + endpointId + ".cache.time-to-live") .ofType(Long.class).withDefaultValue(0).withDescription(