From c5786c218dcb8fbb600fba7a3da7b2fd08c0f5a7 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Sat, 13 Oct 2018 20:05:07 -0700 Subject: [PATCH 1/6] Polish --- .../metrics/OnlyOnceLoggingDenyMeterFilter.java | 2 +- .../CloudFoundryWebFluxEndpointIntegrationTests.java | 7 ------- .../actuate/endpoint/web/PathMappedEndpointsTests.java | 2 +- .../metrics/web/reactive/server/MetricsWebFilterTests.java | 2 +- 4 files changed, 3 insertions(+), 10 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/OnlyOnceLoggingDenyMeterFilter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/OnlyOnceLoggingDenyMeterFilter.java index 3538a6f2a5..1b70bcff36 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/OnlyOnceLoggingDenyMeterFilter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/OnlyOnceLoggingDenyMeterFilter.java @@ -28,7 +28,7 @@ import org.slf4j.LoggerFactory; import org.springframework.util.Assert; /** - * {@link MeterFilter} to log only once a warning message and deny {@link Meter.Id}. + * {@link MeterFilter} to log only once a warning message and deny {@code Meter.Id}. * * @author Jon Schneider * @author Dmytro Nosan diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java index 9e6ae48cfc..f868ccecdf 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointIntegrationTests.java @@ -167,13 +167,6 @@ public class CloudFoundryWebFluxEndpointIntegrationTests { .jsonPath("_links.test-part").doesNotExist())); } - private AnnotationConfigReactiveWebServerApplicationContext createApplicationContext( - Class... config) { - AnnotationConfigReactiveWebServerApplicationContext context = new AnnotationConfigReactiveWebServerApplicationContext(); - context.register(config); - return context; - } - private ContextConsumer withWebTestClient( Consumer clientConsumer) { return (context) -> { diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpointsTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpointsTests.java index a142c1b5fa..1a7f0c2f85 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpointsTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpointsTests.java @@ -91,7 +91,7 @@ public class PathMappedEndpointsTests { @Test public void getPathWhenMissingIdShouldReturnNull() { PathMappedEndpoints mapped = createTestMapped(null); - assertThat(mapped.getRootPath("xx")).isNull(); + assertThat(mapped.getPath("xx")).isNull(); } @Test diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/MetricsWebFilterTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/MetricsWebFilterTests.java index 75d425686b..f082257831 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/MetricsWebFilterTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/reactive/server/MetricsWebFilterTests.java @@ -48,7 +48,7 @@ public class MetricsWebFilterTests { MockClock clock = new MockClock(); this.registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, clock); this.webFilter = new MetricsWebFilter(this.registry, - new DefaultWebFluxTagsProvider(), REQUEST_METRICS_NAME); + new DefaultWebFluxTagsProvider(), REQUEST_METRICS_NAME, true); } @Test From 3105a3888454924d702c2cfe64f04b7c392e03b9 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Sat, 13 Oct 2018 20:04:47 -0700 Subject: [PATCH 2/6] Introduce EndpointID to enforce naming rules Add an `EndpointID` class to enforce the naming rules that we support for actuator endpoints. We now ensure that all endpoint names contain only letters and numbers and must begin with a lower-case letter. Existing public classes and interfaces have been changes so that String based `endpointId` methods are deprecated and strongly typed versions are preferred instead. A few public classes that we're not expecting to be used directly have been changed without deprecated methods being introduced. See gh-14773 --- .../cloudfoundry/AccessLevel.java | 14 +-- .../CloudFoundrySecurityInterceptor.java | 8 +- ...dFoundryWebFluxEndpointHandlerMapping.java | 11 ++- .../CloudFoundrySecurityInterceptor.java | 9 +- ...undryWebEndpointServletHandlerMapping.java | 10 +- .../ExposeExcludePropertyEndpointFilter.java | 2 +- .../jmx/DefaultEndpointObjectNameFactory.java | 3 +- .../web/MappingWebEndpointPathMapper.java | 9 +- .../security/reactive/EndpointRequest.java | 12 ++- .../security/servlet/EndpointRequest.java | 12 ++- ...loudFoundryWebEndpointDiscovererTests.java | 3 +- ...FoundryActuatorAutoConfigurationTests.java | 11 ++- ...FoundryActuatorAutoConfigurationTests.java | 7 +- .../CloudFoundrySecurityInterceptorTests.java | 27 ++++-- ...oseExcludePropertyEndpointFilterTests.java | 33 +++---- ...DefaultEndpointObjectNameFactoryTests.java | 20 ++-- .../MappingWebEndpointPathMapperTests.java | 6 +- .../WebEndpointAutoConfigurationTests.java | 3 +- .../reactive/EndpointRequestTests.java | 15 +-- .../servlet/EndpointRequestTests.java | 15 +-- .../endpoint/AbstractExposableEndpoint.java | 19 +++- .../boot/actuate/endpoint/EndpointId.java | 90 +++++++++++++++++ .../actuate/endpoint/ExposableEndpoint.java | 11 +++ .../AbstractDiscoveredEndpoint.java | 19 ++++ .../DiscoveredOperationsFactory.java | 11 ++- .../actuate/endpoint/annotation/Endpoint.java | 5 +- .../annotation/EndpointDiscoverer.java | 69 +++++++++---- .../invoke/OperationInvokerAdvisor.java | 19 ++++ .../cache/CachingOperationInvokerAdvisor.java | 1 + .../actuate/endpoint/jmx/EndpointMBean.java | 2 +- .../endpoint/jmx/JmxEndpointExporter.java | 2 +- .../endpoint/jmx/MBeanInfoFactory.java | 2 +- .../jmx/annotation/DiscoveredJmxEndpoint.java | 4 +- .../annotation/DiscoveredJmxOperation.java | 5 +- .../jmx/annotation/JmxEndpointDiscoverer.java | 16 ++++ .../endpoint/web/EndpointLinksResolver.java | 5 +- .../endpoint/web/PathMappedEndpoints.java | 49 +++++++++- .../boot/actuate/endpoint/web/PathMapper.java | 32 ++++++- .../web/ServletEndpointRegistrar.java | 2 +- .../ControllerEndpointDiscoverer.java | 16 ++++ .../DiscoveredControllerEndpoint.java | 3 +- .../annotation/DiscoveredServletEndpoint.java | 3 +- .../web/annotation/DiscoveredWebEndpoint.java | 3 +- .../annotation/DiscoveredWebOperation.java | 8 +- .../annotation/RequestPredicateFactory.java | 3 +- .../annotation/ServletEndpointDiscoverer.java | 16 ++++ .../web/annotation/WebEndpointDiscoverer.java | 16 ++++ .../actuate/endpoint/EndpointIdTests.java | 96 +++++++++++++++++++ .../DiscoveredOperationsFactoryTests.java | 46 +++++---- .../annotation/EndpointDiscovererTests.java | 88 ++++++++++------- .../CachingOperationInvokerAdvisorTests.java | 21 ++-- .../jmx/JmxEndpointExporterTests.java | 5 +- .../DiscoveredJmxOperationTests.java | 5 +- .../JmxEndpointDiscovererTests.java | 44 +++++---- .../web/EndpointLinksResolverTests.java | 7 +- .../web/PathMappedEndpointsTests.java | 38 ++++---- .../web/ServletEndpointRegistrarTests.java | 4 +- .../ControllerEndpointDiscovererTests.java | 19 ++-- .../ServletEndpointDiscovererTests.java | 12 ++- .../WebEndpointDiscovererTests.java | 55 ++++++----- ...ControllerEndpointHandlerMappingTests.java | 13 +-- ...ControllerEndpointHandlerMappingTests.java | 13 +-- 62 files changed, 834 insertions(+), 293 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java create mode 100644 spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EndpointIdTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/AccessLevel.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/AccessLevel.java index 38f7cc5c6f..e1f43db98c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/AccessLevel.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/AccessLevel.java @@ -40,19 +40,19 @@ public enum AccessLevel { public static final String REQUEST_ATTRIBUTE = "cloudFoundryAccessLevel"; - private final List endpointIds; + private final List ids; - AccessLevel(String... endpointIds) { - this.endpointIds = Arrays.asList(endpointIds); + AccessLevel(String... ids) { + this.ids = Arrays.asList(ids); } /** - * Returns if the access level should allow access to the specified endpoint path. - * @param endpointId the endpoint ID to check + * Returns if the access level should allow access to the specified ID. + * @param id the ID to check * @return {@code true} if access is allowed */ - public boolean isAccessAllowed(String endpointId) { - return this.endpointIds.isEmpty() || this.endpointIds.contains(endpointId); + public boolean isAccessAllowed(String id) { + return this.ids.isEmpty() || this.ids.contains(id); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundrySecurityInterceptor.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundrySecurityInterceptor.java index 760b669090..355900d466 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundrySecurityInterceptor.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundrySecurityInterceptor.java @@ -59,7 +59,7 @@ class CloudFoundrySecurityInterceptor { this.applicationId = applicationId; } - Mono preHandle(ServerWebExchange exchange, String endpointId) { + Mono preHandle(ServerWebExchange exchange, String id) { ServerHttpRequest request = exchange.getRequest(); if (CorsUtils.isPreFlightRequest(request)) { return SUCCESS; @@ -72,7 +72,7 @@ class CloudFoundrySecurityInterceptor { return Mono.error(new CloudFoundryAuthorizationException( Reason.SERVICE_UNAVAILABLE, "Cloud controller URL is not available")); } - return check(exchange, endpointId).then(SUCCESS).doOnError(this::logError) + return check(exchange, id).then(SUCCESS).doOnError(this::logError) .onErrorResume(this::getErrorResponse); } @@ -80,13 +80,13 @@ class CloudFoundrySecurityInterceptor { logger.error(ex.getMessage(), ex); } - private Mono check(ServerWebExchange exchange, String path) { + private Mono check(ServerWebExchange exchange, String id) { try { Token token = getToken(exchange.getRequest()); return this.tokenValidator.validate(token) .then(this.cloudFoundrySecurityService .getAccessLevel(token.toString(), this.applicationId)) - .filter((accessLevel) -> accessLevel.isAccessAllowed(path)) + .filter((accessLevel) -> accessLevel.isAccessAllowed(id)) .switchIfEmpty(Mono.error(new CloudFoundryAuthorizationException( Reason.ACCESS_DENIED, "Access denied"))) .doOnSuccess((accessLevel) -> exchange.getAttributes() diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java index 1cee0e313f..f8e1811adc 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryWebFluxEndpointHandlerMapping.java @@ -27,6 +27,7 @@ import reactor.core.publisher.Mono; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; @@ -70,7 +71,7 @@ class CloudFoundryWebFluxEndpointHandlerMapping protected ReactiveWebOperation wrapReactiveWebOperation(ExposableWebEndpoint endpoint, WebOperation operation, ReactiveWebOperation reactiveWebOperation) { return new SecureReactiveWebOperation(reactiveWebOperation, - this.securityInterceptor, endpoint.getId()); + this.securityInterceptor, endpoint.getEndpointId()); } @Override @@ -113,10 +114,11 @@ class CloudFoundryWebFluxEndpointHandlerMapping private final CloudFoundrySecurityInterceptor securityInterceptor; - private final String endpointId; + private final EndpointId endpointId; SecureReactiveWebOperation(ReactiveWebOperation delegate, - CloudFoundrySecurityInterceptor securityInterceptor, String endpointId) { + CloudFoundrySecurityInterceptor securityInterceptor, + EndpointId endpointId) { this.delegate = delegate; this.securityInterceptor = securityInterceptor; this.endpointId = endpointId; @@ -125,7 +127,8 @@ class CloudFoundryWebFluxEndpointHandlerMapping @Override public Mono> handle(ServerWebExchange exchange, Map body) { - return this.securityInterceptor.preHandle(exchange, this.endpointId) + return this.securityInterceptor + .preHandle(exchange, this.endpointId.toLowerCaseString()) .flatMap((securityResponse) -> flatMapResponse(exchange, body, securityResponse)); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundrySecurityInterceptor.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundrySecurityInterceptor.java index cdca8b6aba..809e8a6930 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundrySecurityInterceptor.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundrySecurityInterceptor.java @@ -28,6 +28,7 @@ import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryA import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException.Reason; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.Token; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.util.StringUtils; @@ -59,7 +60,7 @@ class CloudFoundrySecurityInterceptor { this.applicationId = applicationId; } - SecurityResponse preHandle(HttpServletRequest request, String endpointId) { + SecurityResponse preHandle(HttpServletRequest request, EndpointId endpointId) { if (CorsUtils.isPreFlightRequest(request)) { return SecurityResponse.success(); } @@ -90,12 +91,14 @@ class CloudFoundrySecurityInterceptor { return SecurityResponse.success(); } - private void check(HttpServletRequest request, String endpointId) throws Exception { + private void check(HttpServletRequest request, EndpointId endpointId) + throws Exception { Token token = getToken(request); this.tokenValidator.validate(token); AccessLevel accessLevel = this.cloudFoundrySecurityService .getAccessLevel(token.toString(), this.applicationId); - if (!accessLevel.isAccessAllowed(endpointId)) { + if (!accessLevel.isAccessAllowed( + (endpointId != null) ? endpointId.toLowerCaseString() : "")) { throw new CloudFoundryAuthorizationException(Reason.ACCESS_DENIED, "Access denied"); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryWebEndpointServletHandlerMapping.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryWebEndpointServletHandlerMapping.java index 646c5d70dc..5b20e0fcbf 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryWebEndpointServletHandlerMapping.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryWebEndpointServletHandlerMapping.java @@ -27,6 +27,7 @@ import javax.servlet.http.HttpServletResponse; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; @@ -68,7 +69,7 @@ class CloudFoundryWebEndpointServletHandlerMapping protected ServletWebOperation wrapServletWebOperation(ExposableWebEndpoint endpoint, WebOperation operation, ServletWebOperation servletWebOperation) { return new SecureServletWebOperation(servletWebOperation, - this.securityInterceptor, endpoint.getId()); + this.securityInterceptor, endpoint.getEndpointId()); } @Override @@ -76,7 +77,7 @@ class CloudFoundryWebEndpointServletHandlerMapping protected Map> links(HttpServletRequest request, HttpServletResponse response) { SecurityResponse securityResponse = this.securityInterceptor.preHandle(request, - ""); + null); if (!securityResponse.getStatus().equals(HttpStatus.OK)) { sendFailureResponse(response, securityResponse); } @@ -115,10 +116,11 @@ class CloudFoundryWebEndpointServletHandlerMapping private final CloudFoundrySecurityInterceptor securityInterceptor; - private final String endpointId; + private final EndpointId endpointId; SecureServletWebOperation(ServletWebOperation delegate, - CloudFoundrySecurityInterceptor securityInterceptor, String endpointId) { + CloudFoundrySecurityInterceptor securityInterceptor, + EndpointId endpointId) { this.delegate = delegate; this.securityInterceptor = securityInterceptor; this.endpointId = endpointId; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilter.java index d61b0370c9..c435e84d94 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilter.java @@ -110,7 +110,7 @@ public class ExposeExcludePropertyEndpointFilter> } private boolean contains(Set items, ExposableEndpoint endpoint) { - return items.contains(endpoint.getId().toLowerCase(Locale.ENGLISH)); + return items.contains(endpoint.getEndpointId().toLowerCaseString()); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/DefaultEndpointObjectNameFactory.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/DefaultEndpointObjectNameFactory.java index c513b4933e..4a37c099a0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/DefaultEndpointObjectNameFactory.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/DefaultEndpointObjectNameFactory.java @@ -52,7 +52,8 @@ class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory { throws MalformedObjectNameException { StringBuilder builder = new StringBuilder(this.properties.getDomain()); builder.append(":type=Endpoint"); - builder.append(",name=" + StringUtils.capitalize(endpoint.getId())); + builder.append( + ",name=" + StringUtils.capitalize(endpoint.getEndpointId().toString())); String baseName = builder.toString(); if (this.mBeanServer != null && hasMBean(baseName)) { builder.append(",context=" + this.contextId); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/MappingWebEndpointPathMapper.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/MappingWebEndpointPathMapper.java index 6f4c1d078a..64bdbf39fd 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/MappingWebEndpointPathMapper.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/MappingWebEndpointPathMapper.java @@ -18,6 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web; import java.util.Map; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.web.PathMapper; /** @@ -35,8 +36,14 @@ class MappingWebEndpointPathMapper implements PathMapper { } @Override + @Deprecated public String getRootPath(String endpointId) { - return this.pathMapping.getOrDefault(endpointId, endpointId); + return getRootPath(EndpointId.of(endpointId)); + } + + @Override + public String getRootPath(EndpointId endpointId) { + return this.pathMapping.getOrDefault(endpointId, endpointId.toLowerCaseString()); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequest.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequest.java index 942e979d72..15ed8c5e2d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequest.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequest.java @@ -31,6 +31,7 @@ import reactor.core.publisher.Mono; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; import org.springframework.boot.security.reactive.ApplicationContextServerWebExchangeMatcher; @@ -208,9 +209,12 @@ public final class EndpointRequest { .map(pathMappedEndpoints::getPath); } - private String getEndpointId(Object source) { + private EndpointId getEndpointId(Object source) { + if (source instanceof EndpointId) { + return (EndpointId) source; + } if (source instanceof String) { - return (String) source; + return (EndpointId.of((String) source)); } if (source instanceof Class) { return getEndpointId((Class) source); @@ -218,12 +222,12 @@ public final class EndpointRequest { throw new IllegalStateException("Unsupported source " + source); } - private String getEndpointId(Class source) { + private EndpointId getEndpointId(Class source) { Endpoint annotation = AnnotatedElementUtils.getMergedAnnotation(source, Endpoint.class); Assert.state(annotation != null, () -> "Class " + source + " is not annotated with @Endpoint"); - return annotation.id(); + return EndpointId.of(annotation.id()); } private List getDelegateMatchers(Set paths) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java index f3633bd7b1..625ec9f7c4 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java @@ -31,6 +31,7 @@ import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; import org.springframework.boot.autoconfigure.security.servlet.RequestMatcherProvider; @@ -256,9 +257,12 @@ public final class EndpointRequest { .map(pathMappedEndpoints::getPath); } - private String getEndpointId(Object source) { + private EndpointId getEndpointId(Object source) { + if (source instanceof EndpointId) { + return (EndpointId) source; + } if (source instanceof String) { - return (String) source; + return (EndpointId.of((String) source)); } if (source instanceof Class) { return getEndpointId((Class) source); @@ -266,12 +270,12 @@ public final class EndpointRequest { throw new IllegalStateException("Unsupported source " + source); } - private String getEndpointId(Class source) { + private EndpointId getEndpointId(Class source) { Endpoint annotation = AnnotatedElementUtils.getMergedAnnotation(source, Endpoint.class); Assert.state(annotation != null, () -> "Class " + source + " is not annotated with @Endpoint"); - return annotation.id(); + return EndpointId.of(annotation.id()); } private List getDelegateMatchers( diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java index 31acdc8034..b40ea50a86 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java @@ -23,6 +23,7 @@ import java.util.function.Function; import org.junit.Test; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.InvocationContext; import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; @@ -57,7 +58,7 @@ public class CloudFoundryWebEndpointDiscovererTests { Collection endpoints = discoverer.getEndpoints(); assertThat(endpoints.size()).isEqualTo(2); for (ExposableWebEndpoint endpoint : endpoints) { - if (endpoint.getId().equals("health")) { + if (endpoint.getEndpointId().equals(EndpointId.of("health"))) { WebOperation operation = endpoint.getOperations().iterator().next(); assertThat(operation.invoke(new InvocationContext( mock(SecurityContext.class), Collections.emptyMap()))) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java index 111366da84..3a7e47bc5d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java @@ -33,6 +33,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfi import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; @@ -243,9 +244,10 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests { context); Collection endpoints = handlerMapping .getEndpoints(); - List endpointIds = endpoints.stream() - .map(ExposableEndpoint::getId).collect(Collectors.toList()); - assertThat(endpointIds).contains("test"); + List endpointIds = endpoints.stream() + .map(ExposableEndpoint::getEndpointId) + .collect(Collectors.toList()); + assertThat(endpointIds).contains(EndpointId.of("test")); }); } @@ -261,7 +263,8 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests { Collection endpoints = handlerMapping .getEndpoints(); ExposableWebEndpoint endpoint = endpoints.stream() - .filter((candidate) -> "test".equals(candidate.getId())) + .filter((candidate) -> EndpointId.of("test") + .equals(candidate.getEndpointId())) .findFirst().get(); assertThat(endpoint.getOperations()).hasSize(1); WebOperation operation = endpoint.getOperations().iterator().next(); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java index 94e6afde96..7dd77459ba 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundryActuatorAutoConfigurationTests.java @@ -26,6 +26,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAu import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; @@ -235,7 +236,8 @@ public class CloudFoundryActuatorAutoConfigurationTests { Collection endpoints = handlerMapping .getEndpoints(); assertThat(endpoints.stream() - .filter((candidate) -> "test".equals(candidate.getId())) + .filter((candidate) -> EndpointId.of("test") + .equals(candidate.getEndpointId())) .findFirst()).isNotEmpty(); }); } @@ -253,7 +255,8 @@ public class CloudFoundryActuatorAutoConfigurationTests { Collection endpoints = handlerMapping .getEndpoints(); ExposableWebEndpoint endpoint = endpoints.stream() - .filter((candidate) -> "test".equals(candidate.getId())) + .filter((candidate) -> EndpointId.of("test") + .equals(candidate.getEndpointId())) .findFirst().get(); Collection operations = endpoint.getOperations(); assertThat(operations).hasSize(1); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundrySecurityInterceptorTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundrySecurityInterceptorTests.java index d1b4da936d..d43668c5e8 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundrySecurityInterceptorTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/servlet/CloudFoundrySecurityInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.CloudFoundryAuthorizationException.Reason; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.Token; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.mock.web.MockHttpServletRequest; @@ -65,13 +66,15 @@ public class CloudFoundrySecurityInterceptorTests { this.request.setMethod("OPTIONS"); this.request.addHeader(HttpHeaders.ORIGIN, "http://example.com"); this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); - SecurityResponse response = this.interceptor.preHandle(this.request, "/a"); + SecurityResponse response = this.interceptor.preHandle(this.request, + EndpointId.of("test")); assertThat(response.getStatus()).isEqualTo(HttpStatus.OK); } @Test public void preHandleWhenTokenIsMissingShouldReturnFalse() { - SecurityResponse response = this.interceptor.preHandle(this.request, "/a"); + SecurityResponse response = this.interceptor.preHandle(this.request, + EndpointId.of("test")); assertThat(response.getStatus()) .isEqualTo(Reason.MISSING_AUTHORIZATION.getStatus()); } @@ -79,7 +82,8 @@ public class CloudFoundrySecurityInterceptorTests { @Test public void preHandleWhenTokenIsNotBearerShouldReturnFalse() { this.request.addHeader("Authorization", mockAccessToken()); - SecurityResponse response = this.interceptor.preHandle(this.request, "/a"); + SecurityResponse response = this.interceptor.preHandle(this.request, + EndpointId.of("test")); assertThat(response.getStatus()) .isEqualTo(Reason.MISSING_AUTHORIZATION.getStatus()); } @@ -89,7 +93,8 @@ public class CloudFoundrySecurityInterceptorTests { this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator, this.securityService, null); this.request.addHeader("Authorization", "bearer " + mockAccessToken()); - SecurityResponse response = this.interceptor.preHandle(this.request, "/a"); + SecurityResponse response = this.interceptor.preHandle(this.request, + EndpointId.of("test")); assertThat(response.getStatus()) .isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus()); } @@ -99,7 +104,8 @@ public class CloudFoundrySecurityInterceptorTests { this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator, null, "my-app-id"); this.request.addHeader("Authorization", "bearer " + mockAccessToken()); - SecurityResponse response = this.interceptor.preHandle(this.request, "/a"); + SecurityResponse response = this.interceptor.preHandle(this.request, + EndpointId.of("test")); assertThat(response.getStatus()) .isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus()); } @@ -110,7 +116,8 @@ public class CloudFoundrySecurityInterceptorTests { this.request.addHeader("Authorization", "bearer " + accessToken); given(this.securityService.getAccessLevel(accessToken, "my-app-id")) .willReturn(AccessLevel.RESTRICTED); - SecurityResponse response = this.interceptor.preHandle(this.request, "/a"); + SecurityResponse response = this.interceptor.preHandle(this.request, + EndpointId.of("test")); assertThat(response.getStatus()).isEqualTo(Reason.ACCESS_DENIED.getStatus()); } @@ -120,7 +127,8 @@ public class CloudFoundrySecurityInterceptorTests { this.request.addHeader("Authorization", "Bearer " + accessToken); given(this.securityService.getAccessLevel(accessToken, "my-app-id")) .willReturn(AccessLevel.FULL); - SecurityResponse response = this.interceptor.preHandle(this.request, "/a"); + SecurityResponse response = this.interceptor.preHandle(this.request, + EndpointId.of("test")); ArgumentCaptor tokenArgumentCaptor = ArgumentCaptor.forClass(Token.class); verify(this.tokenValidator).validate(tokenArgumentCaptor.capture()); Token token = tokenArgumentCaptor.getValue(); @@ -136,7 +144,8 @@ public class CloudFoundrySecurityInterceptorTests { this.request.addHeader("Authorization", "Bearer " + accessToken); given(this.securityService.getAccessLevel(accessToken, "my-app-id")) .willReturn(AccessLevel.RESTRICTED); - SecurityResponse response = this.interceptor.preHandle(this.request, "info"); + SecurityResponse response = this.interceptor.preHandle(this.request, + EndpointId.of("info")); ArgumentCaptor tokenArgumentCaptor = ArgumentCaptor.forClass(Token.class); verify(this.tokenValidator).validate(tokenArgumentCaptor.capture()); Token token = tokenArgumentCaptor.getValue(); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilterTests.java index 0b6b62555b..e386c989a2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilterTests.java @@ -23,6 +23,7 @@ import org.junit.rules.ExpectedException; import org.mockito.MockitoAnnotations; import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.mock.env.MockEnvironment; @@ -81,43 +82,43 @@ public class ExposeExcludePropertyEndpointFilterTests { @Test public void matchWhenExposeIsEmptyAndExcludeIsEmptyAndInDefaultShouldMatch() { setupFilter("", ""); - assertThat(match("def")).isTrue(); + assertThat(match(EndpointId.of("def"))).isTrue(); } @Test public void matchWhenExposeIsEmptyAndExcludeIsEmptyAndNotInDefaultShouldNotMatch() { setupFilter("", ""); - assertThat(match("bar")).isFalse(); + assertThat(match(EndpointId.of("bar"))).isFalse(); } @Test public void matchWhenExposeMatchesAndExcludeIsEmptyShouldMatch() { setupFilter("bar", ""); - assertThat(match("bar")).isTrue(); + assertThat(match(EndpointId.of("bar"))).isTrue(); } @Test public void matchWhenExposeDoesNotMatchAndExcludeIsEmptyShouldNotMatch() { setupFilter("bar", ""); - assertThat(match("baz")).isFalse(); + assertThat(match(EndpointId.of("baz"))).isFalse(); } @Test public void matchWhenExposeMatchesAndExcludeMatchesShouldNotMatch() { setupFilter("bar,baz", "baz"); - assertThat(match("baz")).isFalse(); + assertThat(match(EndpointId.of("baz"))).isFalse(); } @Test public void matchWhenExposeMatchesAndExcludeDoesNotMatchShouldMatch() { setupFilter("bar,baz", "buz"); - assertThat(match("baz")).isTrue(); + assertThat(match(EndpointId.of("baz"))).isTrue(); } @Test public void matchWhenExposeMatchesWithDifferentCaseShouldMatch() { setupFilter("bar", ""); - assertThat(match("bAr")).isTrue(); + assertThat(match(EndpointId.of("bAr"))).isTrue(); } @Test @@ -127,23 +128,23 @@ public class ExposeExcludePropertyEndpointFilterTests { environment.setProperty("foo.exclude", ""); this.filter = new ExposeExcludePropertyEndpointFilter<>( DifferentTestExposableWebEndpoint.class, environment, "foo"); - assertThat(match("baz")).isTrue(); + assertThat(match(EndpointId.of("baz"))).isTrue(); } @Test public void matchWhenIncludeIsAsteriskShouldMatchAll() { setupFilter("*", "buz"); - assertThat(match("bar")).isTrue(); - assertThat(match("baz")).isTrue(); - assertThat(match("buz")).isFalse(); + assertThat(match(EndpointId.of("bar"))).isTrue(); + assertThat(match(EndpointId.of("baz"))).isTrue(); + assertThat(match(EndpointId.of("buz"))).isFalse(); } @Test public void matchWhenExcludeIsAsteriskShouldMatchNone() { setupFilter("bar,baz,buz", "*"); - assertThat(match("bar")).isFalse(); - assertThat(match("baz")).isFalse(); - assertThat(match("buz")).isFalse(); + assertThat(match(EndpointId.of("bar"))).isFalse(); + assertThat(match(EndpointId.of("baz"))).isFalse(); + assertThat(match(EndpointId.of("buz"))).isFalse(); } private void setupFilter(String include, String exclude) { @@ -155,9 +156,9 @@ public class ExposeExcludePropertyEndpointFilterTests { } @SuppressWarnings({ "rawtypes", "unchecked" }) - private boolean match(String id) { + private boolean match(EndpointId id) { ExposableEndpoint endpoint = mock(TestExposableWebEndpoint.class); - given(endpoint.getId()).willReturn(id); + given(endpoint.getEndpointId()).willReturn(id); return ((EndpointFilter) this.filter).match(endpoint); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/DefaultEndpointObjectNameFactoryTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/DefaultEndpointObjectNameFactoryTests.java index 08ebe3e580..0b34899bc9 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/DefaultEndpointObjectNameFactoryTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/DefaultEndpointObjectNameFactoryTests.java @@ -24,6 +24,7 @@ import javax.management.ObjectName; import org.junit.Test; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint; import org.springframework.mock.env.MockEnvironment; import org.springframework.util.ObjectUtils; @@ -50,22 +51,23 @@ public class DefaultEndpointObjectNameFactoryTests { @Test public void generateObjectName() { - ObjectName objectName = generateObjectName(endpoint("Test")); + ObjectName objectName = generateObjectName(endpoint(EndpointId.of("test"))); assertThat(objectName.toString()) .isEqualTo("org.springframework.boot:type=Endpoint,name=Test"); } @Test public void generateObjectNameWithCapitalizedId() { - ObjectName objectName = generateObjectName(endpoint("test")); + ObjectName objectName = generateObjectName( + endpoint(EndpointId.of("testEndpoint"))); assertThat(objectName.toString()) - .isEqualTo("org.springframework.boot:type=Endpoint,name=Test"); + .isEqualTo("org.springframework.boot:type=Endpoint,name=TestEndpoint"); } @Test public void generateObjectNameWithCustomDomain() { this.properties.setDomain("com.example.acme"); - ObjectName objectName = generateObjectName(endpoint("test")); + ObjectName objectName = generateObjectName(endpoint(EndpointId.of("test"))); assertThat(objectName.toString()) .isEqualTo("com.example.acme:type=Endpoint,name=Test"); } @@ -73,7 +75,7 @@ public class DefaultEndpointObjectNameFactoryTests { @Test public void generateObjectNameWithUniqueNames() { this.properties.setUniqueNames(true); - ExposableJmxEndpoint endpoint = endpoint("test"); + ExposableJmxEndpoint endpoint = endpoint(EndpointId.of("test")); String id = ObjectUtils.getIdentityHexString(endpoint); ObjectName objectName = generateObjectName(endpoint); assertThat(objectName.toString()).isEqualTo( @@ -84,7 +86,7 @@ public class DefaultEndpointObjectNameFactoryTests { public void generateObjectNameWithStaticNames() { this.properties.getStaticNames().setProperty("counter", "42"); this.properties.getStaticNames().setProperty("foo", "bar"); - ObjectName objectName = generateObjectName(endpoint("test")); + ObjectName objectName = generateObjectName(endpoint(EndpointId.of("test"))); assertThat(objectName.getKeyProperty("counter")).isEqualTo("42"); assertThat(objectName.getKeyProperty("foo")).isEqualTo("bar"); assertThat(objectName.toString()) @@ -99,7 +101,7 @@ public class DefaultEndpointObjectNameFactoryTests { null)).willReturn( Collections.singleton(new ObjectName( "org.springframework.boot:type=Endpoint,name=Test"))); - ObjectName objectName = generateObjectName(endpoint("test")); + ObjectName objectName = generateObjectName(endpoint(EndpointId.of("test"))); assertThat(objectName.toString()).isEqualTo( "org.springframework.boot:type=Endpoint,name=Test,context=testContext"); @@ -115,9 +117,9 @@ public class DefaultEndpointObjectNameFactoryTests { } } - private ExposableJmxEndpoint endpoint(String id) { + private ExposableJmxEndpoint endpoint(EndpointId id) { ExposableJmxEndpoint endpoint = mock(ExposableJmxEndpoint.class); - given(endpoint.getId()).willReturn(id); + given(endpoint.getEndpointId()).willReturn(id); return endpoint; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/MappingWebEndpointPathMapperTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/MappingWebEndpointPathMapperTests.java index 0a1fffdfd7..a16acdddbd 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/MappingWebEndpointPathMapperTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/MappingWebEndpointPathMapperTests.java @@ -20,6 +20,8 @@ import java.util.Collections; import org.junit.Test; +import org.springframework.boot.actuate.endpoint.EndpointId; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -33,14 +35,14 @@ public class MappingWebEndpointPathMapperTests { public void defaultConfiguration() { MappingWebEndpointPathMapper mapper = new MappingWebEndpointPathMapper( Collections.emptyMap()); - assertThat(mapper.getRootPath("test")).isEqualTo("test"); + assertThat(mapper.getRootPath(EndpointId.of("test"))).isEqualTo("test"); } @Test public void userConfiguration() { MappingWebEndpointPathMapper mapper = new MappingWebEndpointPathMapper( Collections.singletonMap("test", "custom")); - assertThat(mapper.getRootPath("test")).isEqualTo("custom"); + assertThat(mapper.getRootPath(EndpointId.of("test"))).isEqualTo("custom"); } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java index 54cb1a1309..b662a99009 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/WebEndpointAutoConfigurationTests.java @@ -20,6 +20,7 @@ import org.junit.Test; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.PathMapper; @@ -65,7 +66,7 @@ public class WebEndpointAutoConfigurationTests { .run((context) -> { assertThat(context).hasSingleBean(PathMapper.class); String pathMapping = context.getBean(PathMapper.class) - .getRootPath("health"); + .getRootPath(EndpointId.of("health")); assertThat(pathMapping).isEqualTo("healthcheck"); }); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequestTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequestTests.java index e8178c3f14..52cd393267 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequestTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/EndpointRequestTests.java @@ -23,6 +23,7 @@ import org.assertj.core.api.AssertDelegateTarget; import org.junit.Test; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; @@ -129,9 +130,9 @@ public class EndpointRequestTests { ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint() .excluding(FooEndpoint.class, BazServletEndpoint.class); List> endpoints = new ArrayList<>(); - endpoints.add(mockEndpoint("foo", "foo")); - endpoints.add(mockEndpoint("bar", "bar")); - endpoints.add(mockEndpoint("baz", "baz")); + endpoints.add(mockEndpoint(EndpointId.of("foo"), "foo")); + endpoints.add(mockEndpoint(EndpointId.of("bar"), "bar")); + endpoints.add(mockEndpoint(EndpointId.of("baz"), "baz")); PathMappedEndpoints pathMappedEndpoints = new PathMappedEndpoints("/actuator", () -> endpoints); assertMatcher(matcher, pathMappedEndpoints).doesNotMatch("/actuator/foo"); @@ -202,14 +203,14 @@ public class EndpointRequestTests { private PathMappedEndpoints mockPathMappedEndpoints(String basePath) { List> endpoints = new ArrayList<>(); - endpoints.add(mockEndpoint("foo", "foo")); - endpoints.add(mockEndpoint("bar", "bar")); + endpoints.add(mockEndpoint(EndpointId.of("foo"), "foo")); + endpoints.add(mockEndpoint(EndpointId.of("bar"), "bar")); return new PathMappedEndpoints(basePath, () -> endpoints); } - private TestEndpoint mockEndpoint(String id, String rootPath) { + private TestEndpoint mockEndpoint(EndpointId id, String rootPath) { TestEndpoint endpoint = mock(TestEndpoint.class); - given(endpoint.getId()).willReturn(id); + given(endpoint.getEndpointId()).willReturn(id); given(endpoint.getRootPath()).willReturn(rootPath); return endpoint; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java index 4d194c7393..3ea45365ad 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java @@ -25,6 +25,7 @@ import org.assertj.core.api.AssertDelegateTarget; import org.junit.Test; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; @@ -163,9 +164,9 @@ public class EndpointRequestTests { RequestMatcher matcher = EndpointRequest.toAnyEndpoint() .excluding(FooEndpoint.class, BazServletEndpoint.class); List> endpoints = new ArrayList<>(); - endpoints.add(mockEndpoint("foo", "foo")); - endpoints.add(mockEndpoint("bar", "bar")); - endpoints.add(mockEndpoint("baz", "baz")); + endpoints.add(mockEndpoint(EndpointId.of("foo"), "foo")); + endpoints.add(mockEndpoint(EndpointId.of("bar"), "bar")); + endpoints.add(mockEndpoint(EndpointId.of("baz"), "baz")); PathMappedEndpoints pathMappedEndpoints = new PathMappedEndpoints("/actuator", () -> endpoints); assertMatcher(matcher, pathMappedEndpoints).doesNotMatch("/actuator/foo"); @@ -258,14 +259,14 @@ public class EndpointRequestTests { private PathMappedEndpoints mockPathMappedEndpoints(String basePath) { List> endpoints = new ArrayList<>(); - endpoints.add(mockEndpoint("foo", "foo")); - endpoints.add(mockEndpoint("bar", "bar")); + endpoints.add(mockEndpoint(EndpointId.of("foo"), "foo")); + endpoints.add(mockEndpoint(EndpointId.of("bar"), "bar")); return new PathMappedEndpoints(basePath, () -> endpoints); } - private TestEndpoint mockEndpoint(String id, String rootPath) { + private TestEndpoint mockEndpoint(EndpointId id, String rootPath) { TestEndpoint endpoint = mock(TestEndpoint.class); - given(endpoint.getId()).willReturn(id); + given(endpoint.getEndpointId()).willReturn(id); given(endpoint.getRootPath()).willReturn(rootPath); return endpoint; } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AbstractExposableEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AbstractExposableEndpoint.java index c7a87cdf2b..e2e762ab75 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AbstractExposableEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/AbstractExposableEndpoint.java @@ -33,7 +33,7 @@ import org.springframework.util.Assert; public abstract class AbstractExposableEndpoint implements ExposableEndpoint { - private final String id; + private final EndpointId id; private boolean enabledByDefault; @@ -44,9 +44,24 @@ public abstract class AbstractExposableEndpoint * @param id the endpoint id * @param enabledByDefault if the endpoint is enabled by default * @param operations the endpoint operations + * @deprecated since 2.0.6 in favor of + * {@link #AbstractExposableEndpoint(EndpointId, boolean, Collection)} */ + @Deprecated public AbstractExposableEndpoint(String id, boolean enabledByDefault, Collection operations) { + this(EndpointId.of(id), enabledByDefault, operations); + } + + /** + * Create a new {@link AbstractExposableEndpoint} instance. + * @param id the endpoint id + * @param enabledByDefault if the endpoint is enabled by default + * @param operations the endpoint operations + * @since 2.0.6 + */ + public AbstractExposableEndpoint(EndpointId id, boolean enabledByDefault, + Collection operations) { Assert.notNull(id, "ID must not be null"); Assert.notNull(operations, "Operations must not be null"); this.id = id; @@ -56,7 +71,7 @@ public abstract class AbstractExposableEndpoint @Override public String getId() { - return this.id; + return this.id.toString(); } @Override diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java new file mode 100644 index 0000000000..246ff3c29d --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java @@ -0,0 +1,90 @@ +/* + * 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.endpoint; + +import java.util.Locale; +import java.util.regex.Pattern; + +import org.springframework.util.Assert; + +/** + * An identifier for an actuator endpoint. Endpoint IDs may contain only letters and + * numbers and must begin with a lower-case letter. Case is ignored when comparing + * endpoint IDs. + * + * @author Phillip Webb + * @since 2.0.6 + */ +public final class EndpointId { + + private static final Pattern ALPHA_NUMERIC = Pattern.compile("[a-zA-Z0-9]+"); + + private final String value; + + private final String lowerCaseValue; + + private EndpointId(String value) { + Assert.hasText(value, "Value must not be empty"); + Assert.isTrue(ALPHA_NUMERIC.matcher(value).matches(), + "Value must be alpha-numeric"); + Assert.isTrue(!Character.isDigit(value.charAt(0)), + "Value must not start with a number"); + Assert.isTrue(!Character.isUpperCase(value.charAt(0)), + "Value must not start with an uppercase letter"); + this.value = value; + this.lowerCaseValue = value.toLowerCase(Locale.ENGLISH); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + return toLowerCaseString().equals(((EndpointId) obj).toLowerCaseString()); + } + + @Override + public int hashCode() { + return toLowerCaseString().hashCode(); + } + + /** + * Return a lower-case version of the endpoint ID. + * @return the lower-case endpoint ID + */ + public String toLowerCaseString() { + return this.lowerCaseValue; + } + + @Override + public String toString() { + return this.value; + } + + /** + * Factory method to create a new {@link EndpointId} of the specified value. + * @param value the endpoint ID value + * @return an {@link EndpointId} instance + */ + public static EndpointId of(String value) { + return new EndpointId(value); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ExposableEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ExposableEndpoint.java index 64dce2f9d1..f9984ff20e 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ExposableEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ExposableEndpoint.java @@ -31,9 +31,20 @@ public interface ExposableEndpoint { /** * Returns the id of the endpoint. * @return the id + * @deprecated since 2.0.6 in favor of {@link #getEndpointId()} */ + @Deprecated String getId(); + /** + * Return the endpoint ID. + * @return the endpoint ID + * @since 2.0.6 + */ + default EndpointId getEndpointId() { + return EndpointId.of(getId()); + } + /** * Returns if the endpoint is enabled by default. * @return if the endpoint is enabled by default diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/AbstractDiscoveredEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/AbstractDiscoveredEndpoint.java index 6cd73db8e8..6c582f9fe0 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/AbstractDiscoveredEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/AbstractDiscoveredEndpoint.java @@ -19,6 +19,7 @@ package org.springframework.boot.actuate.endpoint.annotation; import java.util.Collection; import org.springframework.boot.actuate.endpoint.AbstractExposableEndpoint; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.core.style.ToStringCreator; @@ -46,10 +47,28 @@ public abstract class AbstractDiscoveredEndpoint * @param id the ID of the endpoint * @param enabledByDefault if the endpoint is enabled by default * @param operations the endpoint operations + * @deprecated since 2.0.6 in favor of + * {@link #AbstractDiscoveredEndpoint(EndpointDiscoverer, Object, EndpointId, boolean, Collection)} */ + @Deprecated public AbstractDiscoveredEndpoint(EndpointDiscoverer discoverer, Object endpointBean, String id, boolean enabledByDefault, Collection operations) { + this(discoverer, endpointBean, EndpointId.of(id), enabledByDefault, operations); + } + + /** + * Create a new {@link AbstractDiscoveredEndpoint} instance. + * @param discoverer the discoverer that discovered the endpoint + * @param endpointBean the primary source bean + * @param id the ID of the endpoint + * @param enabledByDefault if the endpoint is enabled by default + * @param operations the endpoint operations + * @since 2.0.6 + */ + public AbstractDiscoveredEndpoint(EndpointDiscoverer discoverer, + Object endpointBean, EndpointId id, boolean enabledByDefault, + Collection operations) { super(id, enabledByDefault, operations); Assert.notNull(discoverer, "Discoverer must not be null"); Assert.notNull(endpointBean, "EndpointBean must not be null"); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/DiscoveredOperationsFactory.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/DiscoveredOperationsFactory.java index 6a900109a0..3397f5f7e3 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/DiscoveredOperationsFactory.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/DiscoveredOperationsFactory.java @@ -24,6 +24,7 @@ import java.util.EnumMap; import java.util.Map; import java.util.Objects; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; @@ -68,20 +69,20 @@ abstract class DiscoveredOperationsFactory { this.invokerAdvisors = invokerAdvisors; } - public Collection createOperations(String id, Object target) { + public Collection createOperations(EndpointId id, Object target) { return MethodIntrospector.selectMethods(target.getClass(), (MetadataLookup) (method) -> createOperation(id, target, method)) .values(); } - private O createOperation(String endpointId, Object target, Method method) { + private O createOperation(EndpointId endpointId, Object target, Method method) { return OPERATION_TYPES.entrySet().stream() .map((entry) -> createOperation(endpointId, target, method, entry.getKey(), entry.getValue())) .filter(Objects::nonNull).findFirst().orElse(null); } - private O createOperation(String endpointId, Object target, Method method, + private O createOperation(EndpointId endpointId, Object target, Method method, OperationType operationType, Class annotationType) { AnnotationAttributes annotationAttributes = AnnotatedElementUtils .getMergedAnnotationAttributes(method, annotationType); @@ -96,7 +97,7 @@ abstract class DiscoveredOperationsFactory { return createOperation(endpointId, operationMethod, invoker); } - private OperationInvoker applyAdvisors(String endpointId, + private OperationInvoker applyAdvisors(EndpointId endpointId, OperationMethod operationMethod, OperationInvoker invoker) { if (this.invokerAdvisors != null) { for (OperationInvokerAdvisor advisor : this.invokerAdvisors) { @@ -107,7 +108,7 @@ abstract class DiscoveredOperationsFactory { return invoker; } - protected abstract O createOperation(String endpointId, + protected abstract O createOperation(EndpointId endpointId, DiscoveredOperationMethod operationMethod, OperationInvoker invoker); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/Endpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/Endpoint.java index 94fff19801..ea5a396b6b 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/Endpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/Endpoint.java @@ -22,6 +22,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.boot.actuate.endpoint.EndpointId; + /** * Identifies a type as being an actuator endpoint that provides information about the * running application. Endpoints can be exposed over a variety of technologies including @@ -52,8 +54,9 @@ import java.lang.annotation.Target; public @interface Endpoint { /** - * The id of the endpoint. + * The id of the endpoint (must follow {@link EndpointId} rules). * @return the id + * @see EndpointId */ String id() default ""; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java index 57aac62bc9..92d59d3d03 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscoverer.java @@ -32,6 +32,7 @@ import java.util.stream.Collectors; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.EndpointsSupplier; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.Operation; @@ -101,7 +102,7 @@ public abstract class EndpointDiscoverer, O exten return new DiscoveredOperationsFactory(parameterValueMapper, invokerAdvisors) { @Override - protected O createOperation(String endpointId, + protected O createOperation(EndpointId endpointId, DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { return EndpointDiscoverer.this.createOperation(endpointId, operationMethod, invoker); @@ -125,7 +126,7 @@ public abstract class EndpointDiscoverer, O exten } private Collection createEndpointBeans() { - Map byId = new LinkedHashMap<>(); + Map byId = new LinkedHashMap<>(); String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors( this.applicationContext, Endpoint.class); for (String beanName : beanNames) { @@ -145,7 +146,7 @@ public abstract class EndpointDiscoverer, O exten } private void addExtensionBeans(Collection endpointBeans) { - Map byId = endpointBeans.stream() + Map byId = endpointBeans.stream() .collect(Collectors.toMap(EndpointBean::getId, (bean) -> bean)); String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors( this.applicationContext, EndpointExtension.class); @@ -189,7 +190,7 @@ public abstract class EndpointDiscoverer, O exten private E convertToEndpoint(EndpointBean endpointBean) { MultiValueMap indexed = new LinkedMultiValueMap<>(); - String id = endpointBean.getId(); + EndpointId id = endpointBean.getId(); addOperations(indexed, id, endpointBean.getBean(), false); if (endpointBean.getExtensions().size() > 1) { String extensionBeans = endpointBean.getExtensions().stream() @@ -209,7 +210,7 @@ public abstract class EndpointDiscoverer, O exten endpointBean.isEnabledByDefault(), operations); } - private void addOperations(MultiValueMap indexed, String id, + private void addOperations(MultiValueMap indexed, EndpointId id, Object target, boolean replaceLast) { Set replacedLast = new HashSet<>(); Collection operations = this.operationsFactory.createOperations(id, target); @@ -339,7 +340,25 @@ public abstract class EndpointDiscoverer, O exten * @param enabledByDefault if the endpoint is enabled by default * @param operations the endpoint operations * @return a created endpoint (a {@link DiscoveredEndpoint} is recommended) + * @since 2.0.6 */ + protected E createEndpoint(Object endpointBean, EndpointId id, + boolean enabledByDefault, Collection operations) { + return createEndpoint(endpointBean, (id != null) ? id.toString() : null, + enabledByDefault, operations); + } + + /** + * Factory method called to create the {@link ExposableEndpoint endpoint}. + * @param endpointBean the source endpoint bean + * @param id the ID of the endpoint + * @param enabledByDefault if the endpoint is enabled by default + * @param operations the endpoint operations + * @return a created endpoint (a {@link DiscoveredEndpoint} is recommended) + * @deprecated Since 2.0.6 in favor of + * {@link #createEndpoint(Object, EndpointId, boolean, Collection)} + */ + @Deprecated protected abstract E createEndpoint(Object endpointBean, String id, boolean enabledByDefault, Collection operations); @@ -349,7 +368,24 @@ public abstract class EndpointDiscoverer, O exten * @param operationMethod the operation method * @param invoker the invoker to use * @return a created operation + * @since 2.0.6 */ + protected O createOperation(EndpointId endpointId, + DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { + return createOperation((endpointId != null) ? endpointId.toString() : null, + operationMethod, invoker); + } + + /** + * Factory method to create an {@link Operation endpoint operation}. + * @param endpointId the endpoint id + * @param operationMethod the operation method + * @param invoker the invoker to use + * @return a created operation + * @deprecated since 2.0.6 in favor of + * {@link #createOperation(EndpointId, DiscoveredOperationMethod, OperationInvoker)} + */ + @Deprecated protected abstract O createOperation(String endpointId, DiscoveredOperationMethod operationMethod, OperationInvoker invoker); @@ -414,7 +450,7 @@ public abstract class EndpointDiscoverer, O exten private final Object bean; - private final String id; + private final EndpointId id; private boolean enabledByDefault; @@ -426,14 +462,15 @@ public abstract class EndpointDiscoverer, O exten AnnotationAttributes attributes = AnnotatedElementUtils .findMergedAnnotationAttributes(bean.getClass(), Endpoint.class, true, true); - this.beanName = beanName; - this.bean = bean; - this.id = attributes.getString("id"); - this.enabledByDefault = (Boolean) attributes.get("enableByDefault"); - this.filter = getFilter(this.bean.getClass()); - Assert.state(StringUtils.hasText(this.id), + String id = attributes.getString("id"); + Assert.state(StringUtils.hasText(id), () -> "No @Endpoint id attribute specified for " + bean.getClass().getName()); + this.beanName = beanName; + this.bean = bean; + this.id = EndpointId.of(id); + this.enabledByDefault = (Boolean) attributes.get("enableByDefault"); + this.filter = getFilter(this.bean.getClass()); } public void addExtension(ExtensionBean extensionBean) { @@ -461,7 +498,7 @@ public abstract class EndpointDiscoverer, O exten return this.bean; } - public String getId() { + public EndpointId getId() { return this.id; } @@ -484,7 +521,7 @@ public abstract class EndpointDiscoverer, O exten private final Object bean; - private final String endpointId; + private final EndpointId endpointId; private final Class filter; @@ -500,7 +537,7 @@ public abstract class EndpointDiscoverer, O exten true); Assert.state(endpointAttributes != null, () -> "Extension " + endpointType.getName() + " does not specify an endpoint"); - this.endpointId = endpointAttributes.getString("id"); + this.endpointId = EndpointId.of(endpointAttributes.getString("id")); this.filter = attributes.getClass("filter"); } @@ -512,7 +549,7 @@ public abstract class EndpointDiscoverer, O exten return this.bean; } - public String getEndpointId() { + public EndpointId getEndpointId() { return this.endpointId; } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/OperationInvokerAdvisor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/OperationInvokerAdvisor.java index 841cf4d0b7..ab0d0edd72 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/OperationInvokerAdvisor.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/OperationInvokerAdvisor.java @@ -16,6 +16,7 @@ package org.springframework.boot.actuate.endpoint.invoke; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.OperationType; /** @@ -34,7 +35,25 @@ public interface OperationInvokerAdvisor { * @param parameters the operation parameters * @param invoker the invoker to advise * @return an potentially new operation invoker with support for additional features + * @since 2.0.6 */ + default OperationInvoker apply(EndpointId endpointId, OperationType operationType, + OperationParameters parameters, OperationInvoker invoker) { + return apply((endpointId != null) ? endpointId.toString() : null, operationType, + parameters, invoker); + } + + /** + * Apply additional functionality to the given invoker. + * @param endpointId the endpoint ID + * @param operationType the operation type + * @param parameters the operation parameters + * @param invoker the invoker to advise + * @return an potentially new operation invoker with support for additional features + * @deprecated since 2.0.6 in favor of + * {@link #apply(EndpointId, OperationType, OperationParameters, OperationInvoker)} + */ + @Deprecated OperationInvoker apply(String endpointId, OperationType operationType, OperationParameters parameters, OperationInvoker invoker); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerAdvisor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerAdvisor.java index a1503249b3..4e37f4e30a 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerAdvisor.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerAdvisor.java @@ -40,6 +40,7 @@ public class CachingOperationInvokerAdvisor implements OperationInvokerAdvisor { } @Override + @Deprecated public OperationInvoker apply(String endpointId, OperationType operationType, OperationParameters parameters, OperationInvoker invoker) { if (operationType == OperationType.READ && !hasMandatoryParameter(parameters)) { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java index 7d1c38bf64..a3a87929fe 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/EndpointMBean.java @@ -89,7 +89,7 @@ public class EndpointMBean implements DynamicMBean { throws MBeanException, ReflectionException { JmxOperation operation = this.operations.get(actionName); if (operation == null) { - String message = "Endpoint with id '" + this.endpoint.getId() + String message = "Endpoint with id '" + this.endpoint.getEndpointId() + "' has no operation named " + actionName; throw new ReflectionException(new IllegalArgumentException(message), message); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpointExporter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpointExporter.java index 33aa7e64c4..9750c59e45 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpointExporter.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpointExporter.java @@ -136,7 +136,7 @@ public class JmxEndpointExporter } private String getEndpointDescription(ExposableJmxEndpoint endpoint) { - return "endpoint '" + endpoint.getId() + "'"; + return "endpoint '" + endpoint.getEndpointId() + "'"; } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/MBeanInfoFactory.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/MBeanInfoFactory.java index 953625a873..63f86e44d6 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/MBeanInfoFactory.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/MBeanInfoFactory.java @@ -58,7 +58,7 @@ class MBeanInfoFactory { } private String getDescription(ExposableJmxEndpoint endpoint) { - return "MBean operations for endpoint " + endpoint.getId(); + return "MBean operations for endpoint " + endpoint.getEndpointId(); } private ModelMBeanOperationInfo[] getMBeanOperations(ExposableJmxEndpoint endpoint) { diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/DiscoveredJmxEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/DiscoveredJmxEndpoint.java index 7b7b9fce09..9d336dcbb3 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/DiscoveredJmxEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/DiscoveredJmxEndpoint.java @@ -18,6 +18,7 @@ package org.springframework.boot.actuate.endpoint.jmx.annotation; import java.util.Collection; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredEndpoint; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint; @@ -32,7 +33,8 @@ class DiscoveredJmxEndpoint extends AbstractDiscoveredEndpoint implements ExposableJmxEndpoint { DiscoveredJmxEndpoint(EndpointDiscoverer discoverer, Object endpointBean, - String id, boolean enabledByDefault, Collection operations) { + EndpointId id, boolean enabledByDefault, + Collection operations) { super(discoverer, endpointBean, id, enabledByDefault, operations); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/DiscoveredJmxOperation.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/DiscoveredJmxOperation.java index 1f1d3d80c9..22ff75c3fd 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/DiscoveredJmxOperation.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/DiscoveredJmxOperation.java @@ -26,6 +26,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredOperation; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; @@ -59,8 +60,8 @@ class DiscoveredJmxOperation extends AbstractDiscoveredOperation implements JmxO private final List parameters; - DiscoveredJmxOperation(String endpointId, DiscoveredOperationMethod operationMethod, - OperationInvoker invoker) { + DiscoveredJmxOperation(EndpointId endpointId, + DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { super(operationMethod, invoker); Method method = operationMethod.getMethod(); this.name = method.getName(); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscoverer.java index 264afdfcf5..7037475ce7 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscoverer.java @@ -19,6 +19,7 @@ package org.springframework.boot.actuate.endpoint.jmx.annotation; import java.util.Collection; import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; @@ -54,15 +55,30 @@ public class JmxEndpointDiscoverer } @Override + @Deprecated protected ExposableJmxEndpoint createEndpoint(Object endpointBean, String id, boolean enabledByDefault, Collection operations) { + return createEndpoint(endpointBean, EndpointId.of(id), enabledByDefault, + operations); + } + + @Override + protected ExposableJmxEndpoint createEndpoint(Object endpointBean, EndpointId id, + boolean enabledByDefault, Collection operations) { return new DiscoveredJmxEndpoint(this, endpointBean, id, enabledByDefault, operations); } @Override + @Deprecated protected JmxOperation createOperation(String endpointId, DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { + return createOperation(EndpointId.of(endpointId), operationMethod, invoker); + } + + @Override + protected JmxOperation createOperation(EndpointId endpointId, + DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { return new DiscoveredJmxOperation(endpointId, operationMethod, invoker); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointLinksResolver.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointLinksResolver.java index de2348b524..88385c7cdf 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointLinksResolver.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/EndpointLinksResolver.java @@ -76,8 +76,9 @@ public class EndpointLinksResolver { collectLinks(links, (ExposableWebEndpoint) endpoint, normalizedUrl); } else if (endpoint instanceof PathMappedEndpoint) { - links.put(endpoint.getId(), createLink(normalizedUrl, - ((PathMappedEndpoint) endpoint).getRootPath())); + String rootPath = ((PathMappedEndpoint) endpoint).getRootPath(); + Link link = createLink(normalizedUrl, rootPath); + links.put(endpoint.getEndpointId().toLowerCaseString(), link); } } return links; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpoints.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpoints.java index b2d270fb20..a3392e87be 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpoints.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpoints.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.EndpointsSupplier; import org.springframework.util.Assert; @@ -37,7 +38,7 @@ public class PathMappedEndpoints implements Iterable { private final String basePath; - private final Map endpoints; + private final Map endpoints; /** * Create a new {@link PathMappedEndpoints} instance for the given supplier. @@ -62,13 +63,14 @@ public class PathMappedEndpoints implements Iterable { this.endpoints = getEndpoints(suppliers); } - private Map getEndpoints( + private Map getEndpoints( Collection> suppliers) { - Map endpoints = new LinkedHashMap<>(); + Map endpoints = new LinkedHashMap<>(); suppliers.forEach((supplier) -> { supplier.getEndpoints().forEach((endpoint) -> { if (endpoint instanceof PathMappedEndpoint) { - endpoints.put(endpoint.getId(), (PathMappedEndpoint) endpoint); + endpoints.put(endpoint.getEndpointId(), + (PathMappedEndpoint) endpoint); } }); }); @@ -88,8 +90,21 @@ public class PathMappedEndpoints implements Iterable { * endpoint cannot be found. * @param endpointId the endpoint ID * @return the root path or {@code null} + * @deprecated since 2.0.6 in favor of {@link #getRootPath(EndpointId)} */ + @Deprecated public String getRootPath(String endpointId) { + return getRootPath(EndpointId.of(endpointId)); + } + + /** + * Return the root path for the endpoint with the given ID or {@code null} if the + * endpoint cannot be found. + * @param endpointId the endpoint ID + * @return the root path or {@code null} + * @since 2.0.6 + */ + public String getRootPath(EndpointId endpointId) { PathMappedEndpoint endpoint = getEndpoint(endpointId); return (endpoint != null) ? endpoint.getRootPath() : null; } @@ -99,8 +114,21 @@ public class PathMappedEndpoints implements Iterable { * endpoint cannot be found. * @param endpointId the endpoint ID * @return the full path or {@code null} + * @deprecated since 2.0.6 in favor of {@link #getPath(EndpointId)} */ + @Deprecated public String getPath(String endpointId) { + return getPath(EndpointId.of(endpointId)); + } + + /** + * Return the full path for the endpoint with the given ID or {@code null} if the + * endpoint cannot be found. + * @param endpointId the endpoint ID + * @return the full path or {@code null} + * @since 2.0.6 + */ + public String getPath(EndpointId endpointId) { return getPath(getEndpoint(endpointId)); } @@ -125,8 +153,21 @@ public class PathMappedEndpoints implements Iterable { * endpoint cannot be found. * @param endpointId the endpoint ID * @return the path mapped endpoint or {@code null} + * @deprecated since 2.0.6 in favor of {@link #getEndpoint(EndpointId)} */ + @Deprecated public PathMappedEndpoint getEndpoint(String endpointId) { + return getEndpoint(EndpointId.of(endpointId)); + } + + /** + * Return the {@link PathMappedEndpoint} with the given ID or {@code null} if the + * endpoint cannot be found. + * @param endpointId the endpoint ID + * @return the path mapped endpoint or {@code null} + * @since 2.0.6 + */ + public PathMappedEndpoint getEndpoint(EndpointId endpointId) { return this.endpoints.get(endpointId); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/PathMapper.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/PathMapper.java index b9116b278d..12af95b1fd 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/PathMapper.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/PathMapper.java @@ -16,6 +16,8 @@ package org.springframework.boot.actuate.endpoint.web; +import org.springframework.boot.actuate.endpoint.EndpointId; + /** * Strategy interface used to provide a mapping between an endpoint ID and the root path * where it will be exposed. @@ -31,15 +33,41 @@ public interface PathMapper { * Resolve the root path for the endpoint with the specified {@code endpointId}. * @param endpointId the id of an endpoint * @return the path of the endpoint + * @since 2.0.6 */ + default String getRootPath(EndpointId endpointId) { + return getRootPath((endpointId != null) ? endpointId.toString() : null); + } + + /** + * Resolve the root path for the endpoint with the specified {@code endpointId}. + * @param endpointId the id of an endpoint + * @return the path of the endpoint + * @deprecated since 2.0.6 in favor of {@link #getRootPath(EndpointId)} + */ + @Deprecated String getRootPath(String endpointId); /** * Returns an {@link PathMapper} that uses the endpoint ID as the path. - * @return an {@link PathMapper} that uses the endpoint ID as the path + * @return an {@link PathMapper} that uses the lowercase endpoint ID as the path */ static PathMapper useEndpointId() { - return (endpointId) -> endpointId; + return new PathMapper() { + + @Override + @Deprecated + public String getRootPath(String endpointId) { + return getRootPath(EndpointId.of(endpointId)); + } + + @Override + public String getRootPath(EndpointId endpointId) { + return endpointId.toLowerCaseString(); + } + + }; + } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrar.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrar.java index 3d4ba2aa45..dec24a8794 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrar.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrar.java @@ -67,7 +67,7 @@ public class ServletEndpointRegistrar implements ServletContextInitializer { private void register(ServletContext servletContext, ExposableServletEndpoint endpoint) { - String name = endpoint.getId() + "-actuator-endpoint"; + String name = endpoint.getEndpointId().toLowerCaseString() + "-actuator-endpoint"; String path = this.basePath + "/" + endpoint.getRootPath(); String urlMapping = path.endsWith("/") ? path + "*" : path + "/*"; EndpointServlet endpointServlet = endpoint.getEndpointServlet(); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscoverer.java index 7df0d22e42..8842b20ce4 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscoverer.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.Collections; import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; @@ -67,16 +68,31 @@ public class ControllerEndpointDiscoverer } @Override + @Deprecated protected ExposableControllerEndpoint createEndpoint(Object endpointBean, String id, boolean enabledByDefault, Collection operations) { + return createEndpoint(endpointBean, (id != null) ? EndpointId.of(id) : null, + enabledByDefault, operations); + } + + @Override + protected ExposableControllerEndpoint createEndpoint(Object endpointBean, + EndpointId id, boolean enabledByDefault, Collection operations) { String rootPath = this.endpointPathMapper.getRootPath(id); return new DiscoveredControllerEndpoint(this, endpointBean, id, rootPath, enabledByDefault); } @Override + @Deprecated protected Operation createOperation(String endpointId, DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { + return createOperation(EndpointId.of(endpointId), operationMethod, invoker); + } + + @Override + protected Operation createOperation(EndpointId endpointId, + DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { throw new IllegalStateException( "ControllerEndpoints must not declare operations"); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredControllerEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredControllerEndpoint.java index 550386af4e..43a3376c29 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredControllerEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredControllerEndpoint.java @@ -18,6 +18,7 @@ package org.springframework.boot.actuate.endpoint.web.annotation; import java.util.Collections; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredEndpoint; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; @@ -33,7 +34,7 @@ class DiscoveredControllerEndpoint extends AbstractDiscoveredEndpoint private final String rootPath; DiscoveredControllerEndpoint(EndpointDiscoverer discoverer, Object endpointBean, - String id, String rootPath, boolean enabledByDefault) { + EndpointId id, String rootPath, boolean enabledByDefault) { super(discoverer, endpointBean, id, enabledByDefault, Collections.emptyList()); this.rootPath = rootPath; } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredServletEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredServletEndpoint.java index 94932f97fa..c857fc351b 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredServletEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredServletEndpoint.java @@ -19,6 +19,7 @@ package org.springframework.boot.actuate.endpoint.web.annotation; import java.util.Collections; import java.util.function.Supplier; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredEndpoint; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; @@ -39,7 +40,7 @@ class DiscoveredServletEndpoint extends AbstractDiscoveredEndpoint private final EndpointServlet endpointServlet; DiscoveredServletEndpoint(EndpointDiscoverer discoverer, Object endpointBean, - String id, String rootPath, boolean enabledByDefault) { + EndpointId id, String rootPath, boolean enabledByDefault) { super(discoverer, endpointBean, id, enabledByDefault, Collections.emptyList()); String beanType = endpointBean.getClass().getName(); Assert.state(endpointBean instanceof Supplier, diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebEndpoint.java index 17acb60389..36d34883a0 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebEndpoint.java @@ -18,6 +18,7 @@ package org.springframework.boot.actuate.endpoint.web.annotation; import java.util.Collection; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredEndpoint; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; @@ -34,7 +35,7 @@ class DiscoveredWebEndpoint extends AbstractDiscoveredEndpoint private final String rootPath; DiscoveredWebEndpoint(EndpointDiscoverer discoverer, Object endpointBean, - String id, String rootPath, boolean enabledByDefault, + EndpointId id, String rootPath, boolean enabledByDefault, Collection operations) { super(discoverer, endpointBean, id, enabledByDefault, operations); this.rootPath = rootPath; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebOperation.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebOperation.java index a2bc7071c7..6502d62602 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebOperation.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/DiscoveredWebOperation.java @@ -23,6 +23,7 @@ import java.util.stream.Stream; import org.reactivestreams.Publisher; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredOperation; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.annotation.Selector; @@ -51,8 +52,9 @@ class DiscoveredWebOperation extends AbstractDiscoveredOperation implements WebO private final WebOperationRequestPredicate requestPredicate; - DiscoveredWebOperation(String endpointId, DiscoveredOperationMethod operationMethod, - OperationInvoker invoker, WebOperationRequestPredicate requestPredicate) { + DiscoveredWebOperation(EndpointId endpointId, + DiscoveredOperationMethod operationMethod, OperationInvoker invoker, + WebOperationRequestPredicate requestPredicate) { super(operationMethod, invoker); Method method = operationMethod.getMethod(); this.id = getId(endpointId, method); @@ -60,7 +62,7 @@ class DiscoveredWebOperation extends AbstractDiscoveredOperation implements WebO this.requestPredicate = requestPredicate; } - private String getId(String endpointId, Method method) { + private String getId(EndpointId endpointId, Method method) { return endpointId + Stream.of(method.getParameters()).filter(this::hasSelector) .map(this::dashName).collect(Collectors.joining()); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/RequestPredicateFactory.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/RequestPredicateFactory.java index 8ce5291b81..44c9476c92 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/RequestPredicateFactory.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/RequestPredicateFactory.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.annotation.Selector; @@ -50,7 +51,7 @@ class RequestPredicateFactory { this.endpointMediaTypes = endpointMediaTypes; } - public WebOperationRequestPredicate getRequestPredicate(String endpointId, + public WebOperationRequestPredicate getRequestPredicate(EndpointId endpointId, String rootPath, DiscoveredOperationMethod operationMethod) { Method method = operationMethod.getMethod(); String path = getPath(rootPath, method); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscoverer.java index f00857c2db..fa127b40c0 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscoverer.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.Collections; import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; @@ -66,16 +67,31 @@ public class ServletEndpointDiscoverer } @Override + @Deprecated protected ExposableServletEndpoint createEndpoint(Object endpointBean, String id, boolean enabledByDefault, Collection operations) { + return createEndpoint(endpointBean, EndpointId.of(id), enabledByDefault, + operations); + } + + @Override + protected ExposableServletEndpoint createEndpoint(Object endpointBean, EndpointId id, + boolean enabledByDefault, Collection operations) { String rootPath = this.endpointPathMapper.getRootPath(id); return new DiscoveredServletEndpoint(this, endpointBean, id, rootPath, enabledByDefault); } @Override + @Deprecated protected Operation createOperation(String endpointId, DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { + return createOperation(EndpointId.of(endpointId), operationMethod, invoker); + } + + @Override + protected Operation createOperation(EndpointId endpointId, + DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { throw new IllegalStateException("ServletEndpoints must not declare operations"); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscoverer.java index 05226a11ce..e6feb3f98a 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscoverer.java @@ -19,6 +19,7 @@ package org.springframework.boot.actuate.endpoint.web.annotation; import java.util.Collection; import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; @@ -68,16 +69,31 @@ public class WebEndpointDiscoverer } @Override + @Deprecated protected ExposableWebEndpoint createEndpoint(Object endpointBean, String id, boolean enabledByDefault, Collection operations) { + return createEndpoint(endpointBean, EndpointId.of(id), enabledByDefault, + operations); + } + + @Override + protected ExposableWebEndpoint createEndpoint(Object endpointBean, EndpointId id, + boolean enabledByDefault, Collection operations) { String rootPath = this.endpointPathMapper.getRootPath(id); return new DiscoveredWebEndpoint(this, endpointBean, id, rootPath, enabledByDefault, operations); } @Override + @Deprecated protected WebOperation createOperation(String endpointId, DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { + return createOperation(EndpointId.of(endpointId), operationMethod, invoker); + } + + @Override + protected WebOperation createOperation(EndpointId endpointId, + DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { String rootPath = this.endpointPathMapper.getRootPath(endpointId); WebOperationRequestPredicate requestPredicate = this.requestPredicateFactory .getRequestPredicate(endpointId, rootPath, operationMethod); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EndpointIdTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EndpointIdTests.java new file mode 100644 index 0000000000..9c11e8f2ef --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EndpointIdTests.java @@ -0,0 +1,96 @@ +/* + * 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.endpoint; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link EndpointId}. + * + * @author Phillip Webb + */ +public class EndpointIdTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void ofWhenNullThorowsException() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Value must not be empty"); + EndpointId.of(null); + } + + @Test + public void ofWhenEmptyThrowsException() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Value must not be empty"); + EndpointId.of(""); + } + + @Test + public void ofWhenContainsDashThrowsException() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Value must be alpha-numeric"); + EndpointId.of("foo-bar"); + } + + @Test + public void ofWhenHasBadCharThrowsException() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Value must be alpha-numeric"); + EndpointId.of("foo!bar"); + } + + @Test + public void ofWhenStartsWithNumberThrowsException() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Value must not start with a number"); + EndpointId.of("1foo"); + } + + @Test + public void ofWhenStartsWithUppercaseLetterThrowsException() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Value must not start with an uppercase letter"); + EndpointId.of("Foo"); + } + + @Test + public void equalsAndHashCode() { + EndpointId one = EndpointId.of("foobar"); + EndpointId two = EndpointId.of("fooBar"); + EndpointId three = EndpointId.of("barfoo"); + assertThat(one.hashCode()).isEqualTo(two.hashCode()); + assertThat(one).isEqualTo(one).isEqualTo(two).isNotEqualTo(three); + } + + @Test + public void toLowerCaseStringReturnsLowercase() { + assertThat(EndpointId.of("fooBar").toLowerCaseString()).isEqualTo("foobar"); + } + + @Test + public void toStringReturnsString() { + assertThat(EndpointId.of("fooBar").toString()).isEqualTo("fooBar"); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/DiscoveredOperationsFactoryTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/DiscoveredOperationsFactoryTests.java index 34e546dd67..f8fc5d3e3e 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/DiscoveredOperationsFactoryTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/DiscoveredOperationsFactoryTests.java @@ -25,6 +25,7 @@ import java.util.Map; import org.junit.Before; import org.junit.Test; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.InvocationContext; import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.SecurityContext; @@ -60,8 +61,8 @@ public class DiscoveredOperationsFactoryTests { @Test public void createOperationsWhenHasReadMethodShouldCreateOperation() { - Collection operations = this.factory.createOperations("test", - new ExampleRead()); + Collection operations = this.factory + .createOperations(EndpointId.of("test"), new ExampleRead()); assertThat(operations).hasSize(1); TestOperation operation = getFirst(operations); assertThat(operation.getType()).isEqualTo(OperationType.READ); @@ -69,8 +70,8 @@ public class DiscoveredOperationsFactoryTests { @Test public void createOperationsWhenHasWriteMethodShouldCreateOperation() { - Collection operations = this.factory.createOperations("test", - new ExampleWrite()); + Collection operations = this.factory + .createOperations(EndpointId.of("test"), new ExampleWrite()); assertThat(operations).hasSize(1); TestOperation operation = getFirst(operations); assertThat(operation.getType()).isEqualTo(OperationType.WRITE); @@ -78,8 +79,8 @@ public class DiscoveredOperationsFactoryTests { @Test public void createOperationsWhenHasDeleteMethodShouldCreateOperation() { - Collection operations = this.factory.createOperations("test", - new ExampleDelete()); + Collection operations = this.factory + .createOperations(EndpointId.of("test"), new ExampleDelete()); assertThat(operations).hasSize(1); TestOperation operation = getFirst(operations); assertThat(operation.getType()).isEqualTo(OperationType.DELETE); @@ -87,8 +88,8 @@ public class DiscoveredOperationsFactoryTests { @Test public void createOperationsWhenMultipleShouldReturnMultiple() { - Collection operations = this.factory.createOperations("test", - new ExampleMultiple()); + Collection operations = this.factory + .createOperations(EndpointId.of("test"), new ExampleMultiple()); assertThat(operations).hasSize(2); assertThat(operations.stream().map(TestOperation::getType)) .containsOnly(OperationType.READ, OperationType.WRITE); @@ -96,8 +97,8 @@ public class DiscoveredOperationsFactoryTests { @Test public void createOperationsShouldProvideOperationMethod() { - TestOperation operation = getFirst( - this.factory.createOperations("test", new ExampleWithParams())); + TestOperation operation = getFirst(this.factory + .createOperations(EndpointId.of("test"), new ExampleWithParams())); OperationMethod operationMethod = operation.getOperationMethod(); assertThat(operationMethod.getMethod().getName()).isEqualTo("read"); assertThat(operationMethod.getParameters().hasParameters()).isTrue(); @@ -105,8 +106,8 @@ public class DiscoveredOperationsFactoryTests { @Test public void createOperationsShouldProviderInvoker() { - TestOperation operation = getFirst( - this.factory.createOperations("test", new ExampleWithParams())); + TestOperation operation = getFirst(this.factory + .createOperations(EndpointId.of("test"), new ExampleWithParams())); Map params = Collections.singletonMap("name", 123); Object result = operation .invoke(new InvocationContext(mock(SecurityContext.class), params)); @@ -118,10 +119,10 @@ public class DiscoveredOperationsFactoryTests { TestOperationInvokerAdvisor advisor = new TestOperationInvokerAdvisor(); this.invokerAdvisors.add(advisor); TestOperation operation = getFirst( - this.factory.createOperations("test", new ExampleRead())); + this.factory.createOperations(EndpointId.of("test"), new ExampleRead())); operation.invoke(new InvocationContext(mock(SecurityContext.class), Collections.emptyMap())); - assertThat(advisor.getEndpointId()).isEqualTo("test"); + assertThat(advisor.getEndpointId()).isEqualTo(EndpointId.of("test")); assertThat(advisor.getOperationType()).isEqualTo(OperationType.READ); assertThat(advisor.getParameters()).isEmpty(); } @@ -189,7 +190,7 @@ public class DiscoveredOperationsFactoryTests { } @Override - protected TestOperation createOperation(String endpointId, + protected TestOperation createOperation(EndpointId endpointId, DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { return new TestOperation(endpointId, operationMethod, invoker); } @@ -198,7 +199,7 @@ public class DiscoveredOperationsFactoryTests { static class TestOperation extends AbstractDiscoveredOperation { - TestOperation(String endpointId, DiscoveredOperationMethod operationMethod, + TestOperation(EndpointId endpointId, DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { super(operationMethod, invoker); } @@ -207,14 +208,14 @@ public class DiscoveredOperationsFactoryTests { static class TestOperationInvokerAdvisor implements OperationInvokerAdvisor { - private String endpointId; + private EndpointId endpointId; private OperationType operationType; private OperationParameters parameters; @Override - public OperationInvoker apply(String endpointId, OperationType operationType, + public OperationInvoker apply(EndpointId endpointId, OperationType operationType, OperationParameters parameters, OperationInvoker invoker) { this.endpointId = endpointId; this.operationType = operationType; @@ -222,7 +223,14 @@ public class DiscoveredOperationsFactoryTests { return invoker; } - public String getEndpointId() { + @Override + @Deprecated + public OperationInvoker apply(String endpointId, OperationType operationType, + OperationParameters parameters, OperationInvoker invoker) { + throw new IllegalStateException(); + } + + public EndpointId getEndpointId() { return this.endpointId; } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscovererTests.java index b38c076ef6..23dda7dabc 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscovererTests.java @@ -37,6 +37,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; @@ -125,10 +126,11 @@ public class EndpointDiscovererTests { public void getEndpointsWhenHasSubclassedEndpointShouldReturnEndpoint() { load(TestEndpointSubclassConfiguration.class, (context) -> { TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context); - Map endpoints = mapEndpoints( + Map endpoints = mapEndpoints( discoverer.getEndpoints()); - assertThat(endpoints).containsOnlyKeys("test"); - Map operations = mapOperations(endpoints.get("test")); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test")); + Map operations = mapOperations( + endpoints.get(EndpointId.of("test"))); assertThat(operations).hasSize(5); assertThat(operations).containsKeys(testEndpointMethods()); assertThat(operations).containsKeys(ReflectionUtils.findMethod( @@ -151,10 +153,11 @@ public class EndpointDiscovererTests { load(TestEndpointConfiguration.class, (context) -> { TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context, (endpointId) -> 0L); - Map endpoints = mapEndpoints( + Map endpoints = mapEndpoints( discoverer.getEndpoints()); - assertThat(endpoints).containsOnlyKeys("test"); - Map operations = mapOperations(endpoints.get("test")); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test")); + Map operations = mapOperations( + endpoints.get(EndpointId.of("test"))); operations.values().forEach((operation) -> assertThat(operation.getInvoker()) .isNotInstanceOf(CachingOperationInvoker.class)); }); @@ -165,10 +168,11 @@ public class EndpointDiscovererTests { load(TestEndpointConfiguration.class, (context) -> { TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context, (endpointId) -> (endpointId.equals("foo") ? 500L : 0L)); - Map endpoints = mapEndpoints( + Map endpoints = mapEndpoints( discoverer.getEndpoints()); - assertThat(endpoints).containsOnlyKeys("test"); - Map operations = mapOperations(endpoints.get("test")); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test")); + Map operations = mapOperations( + endpoints.get(EndpointId.of("test"))); operations.values().forEach((operation) -> assertThat(operation.getInvoker()) .isNotInstanceOf(CachingOperationInvoker.class)); }); @@ -178,11 +182,13 @@ public class EndpointDiscovererTests { public void getEndpointsWhenTtlSetByIdAndIdMatchesShouldCacheInvokeCalls() { load(TestEndpointConfiguration.class, (context) -> { TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context, - (endpointId) -> (endpointId.equals("test") ? 500L : 0L)); - Map endpoints = mapEndpoints( + (endpointId) -> (endpointId.equals(EndpointId.of("test")) ? 500L + : 0L)); + Map endpoints = mapEndpoints( discoverer.getEndpoints()); - assertThat(endpoints).containsOnlyKeys("test"); - Map operations = mapOperations(endpoints.get("test")); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test")); + Map operations = mapOperations( + endpoints.get(EndpointId.of("test"))); TestOperation getAll = operations.get(findTestEndpointMethod("getAll")); TestOperation getOne = operations .get(findTestEndpointMethod("getOne", String.class)); @@ -201,9 +207,9 @@ public class EndpointDiscovererTests { public void getEndpointsWhenHasSpecializedFiltersInNonSpecializedDiscovererShouldFilterEndpoints() { load(SpecializedEndpointsConfiguration.class, (context) -> { TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context); - Map endpoints = mapEndpoints( + Map endpoints = mapEndpoints( discoverer.getEndpoints()); - assertThat(endpoints).containsOnlyKeys("test"); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test")); }); } @@ -212,9 +218,10 @@ public class EndpointDiscovererTests { load(SpecializedEndpointsConfiguration.class, (context) -> { SpecializedEndpointDiscoverer discoverer = new SpecializedEndpointDiscoverer( context); - Map endpoints = mapEndpoints( + Map endpoints = mapEndpoints( discoverer.getEndpoints()); - assertThat(endpoints).containsOnlyKeys("test", "specialized"); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"), + EndpointId.of("specialized")); }); } @@ -223,10 +230,10 @@ public class EndpointDiscovererTests { load(SpecializedEndpointsConfiguration.class, (context) -> { SpecializedEndpointDiscoverer discoverer = new SpecializedEndpointDiscoverer( context); - Map endpoints = mapEndpoints( + Map endpoints = mapEndpoints( discoverer.getEndpoints()); Map operations = mapOperations( - endpoints.get("specialized")); + endpoints.get(EndpointId.of("specialized"))); assertThat(operations).containsKeys( ReflectionUtils.findMethod(SpecializedExtension.class, "getSpecial")); @@ -238,10 +245,10 @@ public class EndpointDiscovererTests { load(SubSpecializedEndpointsConfiguration.class, (context) -> { SpecializedEndpointDiscoverer discoverer = new SpecializedEndpointDiscoverer( context); - Map endpoints = mapEndpoints( + Map endpoints = mapEndpoints( discoverer.getEndpoints()); Map operations = mapOperations( - endpoints.get("specialized")); + endpoints.get(EndpointId.of("specialized"))); assertThat(operations).containsKeys( ReflectionUtils.findMethod(SpecializedTestEndpoint.class, "getAll")); assertThat(operations).containsKeys(ReflectionUtils.findMethod( @@ -261,18 +268,19 @@ public class EndpointDiscovererTests { }; SpecializedEndpointDiscoverer discoverer = new SpecializedEndpointDiscoverer( context, Collections.singleton(filter)); - Map endpoints = mapEndpoints( + Map endpoints = mapEndpoints( discoverer.getEndpoints()); - assertThat(endpoints).containsOnlyKeys("test"); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test")); }); } private void hasTestEndpoint(AnnotationConfigApplicationContext context) { TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context); - Map endpoints = mapEndpoints( + Map endpoints = mapEndpoints( discoverer.getEndpoints()); - assertThat(endpoints).containsOnlyKeys("test"); - Map operations = mapOperations(endpoints.get("test")); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test")); + Map operations = mapOperations( + endpoints.get(EndpointId.of("test"))); assertThat(operations).hasSize(4); assertThat(operations).containsKeys(); } @@ -290,14 +298,15 @@ public class EndpointDiscovererTests { return ReflectionUtils.findMethod(TestEndpoint.class, name, paramTypes); } - private > Map mapEndpoints( + private > Map mapEndpoints( Collection endpoints) { - Map byId = new LinkedHashMap<>(); + Map byId = new LinkedHashMap<>(); endpoints.forEach((endpoint) -> { - E existing = byId.put(endpoint.getId(), endpoint); + E existing = byId.put(endpoint.getEndpointId(), endpoint); if (existing != null) { - throw new AssertionError(String.format( - "Found endpoints with duplicate id '%s'", endpoint.getId())); + throw new AssertionError( + String.format("Found endpoints with duplicate id '%s'", + endpoint.getEndpointId())); } }); return byId; @@ -513,6 +522,12 @@ public class EndpointDiscovererTests { @Override protected TestExposableEndpoint createEndpoint(Object endpointBean, String id, boolean enabledByDefault, Collection operations) { + throw new IllegalStateException(); + } + + @Override + protected TestExposableEndpoint createEndpoint(Object endpointBean, EndpointId id, + boolean enabledByDefault, Collection operations) { return new TestExposableEndpoint(this, endpointBean, id, enabledByDefault, operations); } @@ -548,6 +563,13 @@ public class EndpointDiscovererTests { protected SpecializedExposableEndpoint createEndpoint(Object endpointBean, String id, boolean enabledByDefault, Collection operations) { + throw new IllegalStateException(); + } + + @Override + protected SpecializedExposableEndpoint createEndpoint(Object endpointBean, + EndpointId id, boolean enabledByDefault, + Collection operations) { return new SpecializedExposableEndpoint(this, endpointBean, id, enabledByDefault, operations); } @@ -569,7 +591,7 @@ public class EndpointDiscovererTests { static class TestExposableEndpoint extends AbstractDiscoveredEndpoint { TestExposableEndpoint(EndpointDiscoverer discoverer, Object endpointBean, - String id, boolean enabledByDefault, + EndpointId id, boolean enabledByDefault, Collection operations) { super(discoverer, endpointBean, id, enabledByDefault, operations); } @@ -580,7 +602,7 @@ public class EndpointDiscovererTests { extends AbstractDiscoveredEndpoint { SpecializedExposableEndpoint(EndpointDiscoverer discoverer, - Object endpointBean, String id, boolean enabledByDefault, + Object endpointBean, EndpointId id, boolean enabledByDefault, Collection operations) { super(discoverer, endpointBean, id, enabledByDefault, operations); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerAdvisorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerAdvisorTests.java index 921de6b724..95b3311640 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerAdvisorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerAdvisorTests.java @@ -24,6 +24,7 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; @@ -63,8 +64,8 @@ public class CachingOperationInvokerAdvisorTests { @Test public void applyWhenOperationIsNotReadShouldNotAddAdvise() { OperationParameters parameters = getParameters("get"); - OperationInvoker advised = this.advisor.apply("foo", OperationType.WRITE, - parameters, this.invoker); + OperationInvoker advised = this.advisor.apply(EndpointId.of("foo"), + OperationType.WRITE, parameters, this.invoker); assertThat(advised).isSameAs(this.invoker); } @@ -72,8 +73,8 @@ public class CachingOperationInvokerAdvisorTests { public void applyWhenHasAtLeaseOneMandatoryParameterShouldNotAddAdvise() { OperationParameters parameters = getParameters("getWithParameters", String.class, String.class); - OperationInvoker advised = this.advisor.apply("foo", OperationType.READ, - parameters, this.invoker); + OperationInvoker advised = this.advisor.apply(EndpointId.of("foo"), + OperationType.READ, parameters, this.invoker); assertThat(advised).isSameAs(this.invoker); } @@ -81,8 +82,8 @@ public class CachingOperationInvokerAdvisorTests { public void applyWhenTimeToLiveReturnsNullShouldNotAddAdvise() { OperationParameters parameters = getParameters("get"); given(this.timeToLive.apply(any())).willReturn(null); - OperationInvoker advised = this.advisor.apply("foo", OperationType.READ, - parameters, this.invoker); + OperationInvoker advised = this.advisor.apply(EndpointId.of("foo"), + OperationType.READ, parameters, this.invoker); assertThat(advised).isSameAs(this.invoker); verify(this.timeToLive).apply("foo"); } @@ -91,8 +92,8 @@ public class CachingOperationInvokerAdvisorTests { public void applyWhenTimeToLiveIsZeroShouldNotAddAdvise() { OperationParameters parameters = getParameters("get"); given(this.timeToLive.apply(any())).willReturn(0L); - OperationInvoker advised = this.advisor.apply("foo", OperationType.READ, - parameters, this.invoker); + OperationInvoker advised = this.advisor.apply(EndpointId.of("foo"), + OperationType.READ, parameters, this.invoker); assertThat(advised).isSameAs(this.invoker); verify(this.timeToLive).apply("foo"); } @@ -121,8 +122,8 @@ public class CachingOperationInvokerAdvisorTests { } private void assertAdviseIsApplied(OperationParameters parameters) { - OperationInvoker advised = this.advisor.apply("foo", OperationType.READ, - parameters, this.invoker); + OperationInvoker advised = this.advisor.apply(EndpointId.of("foo"), + OperationType.READ, parameters, this.invoker); assertThat(advised).isInstanceOf(CachingOperationInvoker.class); assertThat(ReflectionTestUtils.getField(advised, "invoker")) .isEqualTo(this.invoker); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpointExporterTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpointExporterTests.java index b196e96363..de5fd1b1ca 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpointExporterTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/JmxEndpointExporterTests.java @@ -190,9 +190,8 @@ public class JmxEndpointExporterTests { @Override public ObjectName getObjectName(ExposableJmxEndpoint endpoint) throws MalformedObjectNameException { - return (endpoint != null) - ? new ObjectName("boot:type=Endpoint,name=" + endpoint.getId()) - : null; + return (endpoint != null) ? new ObjectName( + "boot:type=Endpoint,name=" + endpoint.getEndpointId()) : null; } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/DiscoveredJmxOperationTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/DiscoveredJmxOperationTests.java index 107432b399..9a1e50af7a 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/DiscoveredJmxOperationTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/DiscoveredJmxOperationTests.java @@ -25,6 +25,7 @@ import java.util.Map; import org.junit.Test; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; @@ -126,8 +127,8 @@ public class DiscoveredJmxOperationTests { annotationAttributes.put("produces", "application/xml"); DiscoveredOperationMethod operationMethod = new DiscoveredOperationMethod(method, OperationType.READ, annotationAttributes); - DiscoveredJmxOperation operation = new DiscoveredJmxOperation("test", - operationMethod, mock(OperationInvoker.class)); + DiscoveredJmxOperation operation = new DiscoveredJmxOperation( + EndpointId.of("test"), operationMethod, mock(OperationInvoker.class)); return operation; } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscovererTests.java index 2dec175552..f4085b7fbc 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscovererTests.java @@ -28,6 +28,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; @@ -70,10 +71,10 @@ public class JmxEndpointDiscovererTests { @Test public void getEndpointsShouldDiscoverStandardEndpoints() { load(TestEndpoint.class, (discoverer) -> { - Map endpoints = discover(discoverer); - assertThat(endpoints).containsOnlyKeys("test"); + Map endpoints = discover(discoverer); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test")); Map operationByName = mapOperations( - endpoints.get("test").getOperations()); + endpoints.get(EndpointId.of("test")).getOperations()); assertThat(operationByName).containsOnlyKeys("getAll", "getSomething", "update", "deleteSomething"); JmxOperation getAll = operationByName.get("getAll"); @@ -106,8 +107,9 @@ public class JmxEndpointDiscovererTests { @Test public void getEndpointsWhenHasFilteredEndpointShouldOnlyDiscoverJmxEndpoints() { load(MultipleEndpointsConfiguration.class, (discoverer) -> { - Map endpoints = discover(discoverer); - assertThat(endpoints).containsOnlyKeys("test", "jmx"); + Map endpoints = discover(discoverer); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"), + EndpointId.of("jmx")); }); } @@ -125,19 +127,19 @@ public class JmxEndpointDiscovererTests { @Test public void getEndpointsWhenHasJmxExtensionShouldOverrideStandardEndpoint() { load(OverriddenOperationJmxEndpointConfiguration.class, (discoverer) -> { - Map endpoints = discover(discoverer); - assertThat(endpoints).containsOnlyKeys("test"); - assertJmxTestEndpoint(endpoints.get("test")); + Map endpoints = discover(discoverer); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test")); + assertJmxTestEndpoint(endpoints.get(EndpointId.of("test"))); }); } @Test public void getEndpointsWhenHasJmxExtensionWithNewOperationAddsExtraOperation() { load(AdditionalOperationJmxEndpointConfiguration.class, (discoverer) -> { - Map endpoints = discover(discoverer); - assertThat(endpoints).containsOnlyKeys("test"); + Map endpoints = discover(discoverer); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test")); Map operationByName = mapOperations( - endpoints.get("test").getOperations()); + endpoints.get(EndpointId.of("test")).getOperations()); assertThat(operationByName).containsOnlyKeys("getAll", "getSomething", "update", "deleteSomething", "getAnother"); JmxOperation getAnother = operationByName.get("getAnother"); @@ -150,10 +152,10 @@ public class JmxEndpointDiscovererTests { @Test public void getEndpointsWhenHasCacheWithTtlShouldCacheReadOperationWithTtlValue() { load(TestEndpoint.class, (id) -> 500L, (discoverer) -> { - Map endpoints = discover(discoverer); - assertThat(endpoints).containsOnlyKeys("test"); + Map endpoints = discover(discoverer); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test")); Map operationByName = mapOperations( - endpoints.get("test").getOperations()); + endpoints.get(EndpointId.of("test")).getOperations()); assertThat(operationByName).containsOnlyKeys("getAll", "getSomething", "update", "deleteSomething"); JmxOperation getAll = operationByName.get("getAll"); @@ -167,10 +169,11 @@ public class JmxEndpointDiscovererTests { public void getEndpointsShouldCacheReadOperations() { load(AdditionalOperationJmxEndpointConfiguration.class, (id) -> 500L, (discoverer) -> { - Map endpoints = discover(discoverer); - assertThat(endpoints).containsOnlyKeys("test"); + Map endpoints = discover( + discoverer); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test")); Map operationByName = mapOperations( - endpoints.get("test").getOperations()); + endpoints.get(EndpointId.of("test")).getOperations()); assertThat(operationByName).containsOnlyKeys("getAll", "getSomething", "update", "deleteSomething", "getAnother"); JmxOperation getAll = operationByName.get("getAll"); @@ -285,10 +288,11 @@ public class JmxEndpointDiscovererTests { assertThat(parameter.getType()).isEqualTo(type); } - private Map discover(JmxEndpointDiscoverer discoverer) { - Map byId = new HashMap<>(); + private Map discover( + JmxEndpointDiscoverer discoverer) { + Map byId = new HashMap<>(); discoverer.getEndpoints() - .forEach((endpoint) -> byId.put(endpoint.getId(), endpoint)); + .forEach((endpoint) -> byId.put(endpoint.getEndpointId(), endpoint)); return byId; } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointLinksResolverTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointLinksResolverTests.java index 32ae0fe60e..b97e33550f 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointLinksResolverTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/EndpointLinksResolverTests.java @@ -24,6 +24,7 @@ import java.util.Map; import org.assertj.core.api.Condition; import org.junit.Test; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.web.annotation.ExposableControllerEndpoint; @@ -62,7 +63,7 @@ public class EndpointLinksResolverTests { operations.add(operationWithPath("/alpha", "alpha")); operations.add(operationWithPath("/alpha/{name}", "alpha-name")); ExposableWebEndpoint endpoint = mock(ExposableWebEndpoint.class); - given(endpoint.getId()).willReturn("alpha"); + given(endpoint.getEndpointId()).willReturn(EndpointId.of("alpha")); given(endpoint.isEnableByDefault()).willReturn(true); given(endpoint.getOperations()).willReturn(operations); String requestUrl = "https://api.example.com/actuator"; @@ -80,7 +81,7 @@ public class EndpointLinksResolverTests { @Test public void resolvedLinksContainsALinkForServletEndpoint() { ExposableServletEndpoint servletEndpoint = mock(ExposableServletEndpoint.class); - given(servletEndpoint.getId()).willReturn("alpha"); + given(servletEndpoint.getEndpointId()).willReturn(EndpointId.of("alpha")); given(servletEndpoint.isEnableByDefault()).willReturn(true); given(servletEndpoint.getRootPath()).willReturn("alpha"); String requestUrl = "https://api.example.com/actuator"; @@ -97,7 +98,7 @@ public class EndpointLinksResolverTests { public void resolvedLinksContainsALinkForControllerEndpoint() { ExposableControllerEndpoint controllerEndpoint = mock( ExposableControllerEndpoint.class); - given(controllerEndpoint.getId()).willReturn("alpha"); + given(controllerEndpoint.getEndpointId()).willReturn(EndpointId.of("alpha")); given(controllerEndpoint.isEnableByDefault()).willReturn(true); given(controllerEndpoint.getRootPath()).willReturn("alpha"); String requestUrl = "https://api.example.com/actuator"; diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpointsTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpointsTests.java index 1a7f0c2f85..6674efc808 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpointsTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/PathMappedEndpointsTests.java @@ -24,6 +24,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.EndpointsSupplier; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.Operation; @@ -60,38 +61,41 @@ public class PathMappedEndpointsTests { public void iteratorShouldReturnPathMappedEndpoints() { PathMappedEndpoints mapped = createTestMapped(null); assertThat(mapped).hasSize(2); - assertThat(mapped).extracting("id").containsExactly("e2", "e3"); + assertThat(mapped).extracting("endpointId").containsExactly(EndpointId.of("e2"), + EndpointId.of("e3")); } @Test public void streamShouldReturnPathMappedEndpoints() { PathMappedEndpoints mapped = createTestMapped(null); assertThat(mapped.stream()).hasSize(2); - assertThat(mapped.stream()).extracting("id").containsExactly("e2", "e3"); + assertThat(mapped.stream()).extracting("endpointId") + .containsExactly(EndpointId.of("e2"), EndpointId.of("e3")); } @Test public void getRootPathWhenContainsIdShouldReturnRootPath() { PathMappedEndpoints mapped = createTestMapped(null); - assertThat(mapped.getRootPath("e2")).isEqualTo("p2"); + assertThat(mapped.getRootPath(EndpointId.of("e2"))).isEqualTo("p2"); } @Test public void getRootPathWhenMissingIdShouldReturnNull() { PathMappedEndpoints mapped = createTestMapped(null); - assertThat(mapped.getRootPath("xx")).isNull(); + assertThat(mapped.getRootPath(EndpointId.of("xx"))).isNull(); } @Test public void getPathWhenContainsIdShouldReturnRootPath() { - assertThat(createTestMapped(null).getPath("e2")).isEqualTo("/p2"); - assertThat(createTestMapped("/x").getPath("e2")).isEqualTo("/x/p2"); + assertThat(createTestMapped(null).getPath(EndpointId.of("e2"))).isEqualTo("/p2"); + assertThat(createTestMapped("/x").getPath(EndpointId.of("e2"))) + .isEqualTo("/x/p2"); } @Test public void getPathWhenMissingIdShouldReturnNull() { PathMappedEndpoints mapped = createTestMapped(null); - assertThat(mapped.getPath("xx")).isNull(); + assertThat(mapped.getPath(EndpointId.of("xx"))).isNull(); } @Test @@ -110,34 +114,34 @@ public class PathMappedEndpointsTests { @Test public void getEndpointWhenContainsIdShouldReturnPathMappedEndpoint() { PathMappedEndpoints mapped = createTestMapped(null); - assertThat(mapped.getEndpoint("e2").getRootPath()).isEqualTo("p2"); + assertThat(mapped.getEndpoint(EndpointId.of("e2")).getRootPath()).isEqualTo("p2"); } @Test public void getEndpointWhenMissingIdShouldReturnNull() { PathMappedEndpoints mapped = createTestMapped(null); - assertThat(mapped.getEndpoint("xx")).isNull(); + assertThat(mapped.getEndpoint(EndpointId.of("xx"))).isNull(); } private PathMappedEndpoints createTestMapped(String basePath) { List> endpoints = new ArrayList<>(); - endpoints.add(mockEndpoint("e1")); - endpoints.add(mockEndpoint("e2", "p2")); - endpoints.add(mockEndpoint("e3", "p3")); - endpoints.add(mockEndpoint("e4")); + endpoints.add(mockEndpoint(EndpointId.of("e1"))); + endpoints.add(mockEndpoint(EndpointId.of("e2"), "p2")); + endpoints.add(mockEndpoint(EndpointId.of("e3"), "p3")); + endpoints.add(mockEndpoint(EndpointId.of("e4"))); return new PathMappedEndpoints(basePath, () -> endpoints); } - private TestPathMappedEndpoint mockEndpoint(String id, String rootPath) { + private TestPathMappedEndpoint mockEndpoint(EndpointId id, String rootPath) { TestPathMappedEndpoint endpoint = mock(TestPathMappedEndpoint.class); - given(endpoint.getId()).willReturn(id); + given(endpoint.getEndpointId()).willReturn(id); given(endpoint.getRootPath()).willReturn(rootPath); return endpoint; } - private TestEndpoint mockEndpoint(String id) { + private TestEndpoint mockEndpoint(EndpointId id) { TestEndpoint endpoint = mock(TestEndpoint.class); - given(endpoint.getId()).willReturn(id); + given(endpoint.getEndpointId()).willReturn(id); return endpoint; } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrarTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrarTests.java index 96cfc705ab..51b3d4f220 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrarTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/ServletEndpointRegistrarTests.java @@ -35,6 +35,8 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.springframework.boot.actuate.endpoint.EndpointId; + import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -124,7 +126,7 @@ public class ServletEndpointRegistrarTests { private ExposableServletEndpoint mockEndpoint(EndpointServlet endpointServlet) { ExposableServletEndpoint endpoint = mock(ExposableServletEndpoint.class); - given(endpoint.getId()).willReturn("test"); + given(endpoint.getEndpointId()).willReturn(EndpointId.of("test")); given(endpoint.getEndpointServlet()).willReturn(endpointServlet); given(endpoint.getRootPath()).willReturn("test"); return endpoint; diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscovererTests.java index e3cd4c7562..0a4d42b6f2 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ControllerEndpointDiscovererTests.java @@ -26,6 +26,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredEndpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; @@ -70,7 +71,8 @@ public class ControllerEndpointDiscovererTests { .getEndpoints(); assertThat(endpoints).hasSize(1); ExposableControllerEndpoint endpoint = endpoints.iterator().next(); - assertThat(endpoint.getId()).isEqualTo("testcontroller"); + assertThat(endpoint.getEndpointId()) + .isEqualTo(EndpointId.of("testcontroller")); assertThat(endpoint.getController()) .isInstanceOf(TestControllerEndpoint.class); assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class); @@ -87,7 +89,8 @@ public class ControllerEndpointDiscovererTests { .getEndpoints(); assertThat(endpoints).hasSize(1); ExposableControllerEndpoint endpoint = endpoints.iterator().next(); - assertThat(endpoint.getId()).isEqualTo("testcontroller"); + assertThat(endpoint.getEndpointId()) + .isEqualTo(EndpointId.of("testcontroller")); assertThat(endpoint.getController()) .isInstanceOf(TestProxyControllerEndpoint.class); assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class); @@ -102,7 +105,8 @@ public class ControllerEndpointDiscovererTests { .getEndpoints(); assertThat(endpoints).hasSize(1); ExposableControllerEndpoint endpoint = endpoints.iterator().next(); - assertThat(endpoint.getId()).isEqualTo("testrestcontroller"); + assertThat(endpoint.getEndpointId()) + .isEqualTo(EndpointId.of("testrestcontroller")); assertThat(endpoint.getController()) .isInstanceOf(TestRestControllerEndpoint.class); })); @@ -118,7 +122,8 @@ public class ControllerEndpointDiscovererTests { .getEndpoints(); assertThat(endpoints).hasSize(1); ExposableControllerEndpoint endpoint = endpoints.iterator().next(); - assertThat(endpoint.getId()).isEqualTo("testrestcontroller"); + assertThat(endpoint.getEndpointId()) + .isEqualTo(EndpointId.of("testrestcontroller")); assertThat(endpoint.getController()) .isInstanceOf(TestProxyRestControllerEndpoint.class); assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class); @@ -131,9 +136,11 @@ public class ControllerEndpointDiscovererTests { .run(assertDiscoverer((discoverer) -> { Collection endpoints = discoverer .getEndpoints(); - List ids = endpoints.stream().map(ExposableEndpoint::getId) + List ids = endpoints.stream() + .map(ExposableEndpoint::getEndpointId) .collect(Collectors.toList()); - assertThat(ids).containsOnly("testcontroller", "testrestcontroller"); + assertThat(ids).containsOnly(EndpointId.of("testcontroller"), + EndpointId.of("testrestcontroller")); })); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscovererTests.java index 7ae9093cb2..3f9a2868ba 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/ServletEndpointDiscovererTests.java @@ -33,6 +33,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredEndpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; @@ -79,7 +80,8 @@ public class ServletEndpointDiscovererTests { .getEndpoints(); assertThat(endpoints).hasSize(1); ExposableServletEndpoint endpoint = endpoints.iterator().next(); - assertThat(endpoint.getId()).isEqualTo("testservlet"); + assertThat(endpoint.getEndpointId()) + .isEqualTo(EndpointId.of("testservlet")); assertThat(endpoint.getEndpointServlet()).isNotNull(); assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class); })); @@ -95,7 +97,8 @@ public class ServletEndpointDiscovererTests { .getEndpoints(); assertThat(endpoints).hasSize(1); ExposableServletEndpoint endpoint = endpoints.iterator().next(); - assertThat(endpoint.getId()).isEqualTo("testservlet"); + assertThat(endpoint.getEndpointId()) + .isEqualTo(EndpointId.of("testservlet")); assertThat(endpoint.getEndpointServlet()).isNotNull(); assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class); })); @@ -107,9 +110,10 @@ public class ServletEndpointDiscovererTests { .run(assertDiscoverer((discoverer) -> { Collection endpoints = discoverer .getEndpoints(); - List ids = endpoints.stream().map(ExposableEndpoint::getId) + List ids = endpoints.stream() + .map(ExposableEndpoint::getEndpointId) .collect(Collectors.toList()); - assertThat(ids).containsOnly("testservlet"); + assertThat(ids).containsOnly(EndpointId.of("testservlet")); })); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscovererTests.java index cf31c236d8..2eb59000f7 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscovererTests.java @@ -33,6 +33,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; @@ -91,19 +92,19 @@ public class WebEndpointDiscovererTests { @Test public void getEndpointsWhenHasFilteredEndpointShouldOnlyDiscoverWebEndpoints() { load(MultipleEndpointsConfiguration.class, (discoverer) -> { - Map endpoints = mapEndpoints( + Map endpoints = mapEndpoints( discoverer.getEndpoints()); - assertThat(endpoints).containsOnlyKeys("test"); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test")); }); } @Test public void getEndpointsWhenHasWebExtensionShouldOverrideStandardEndpoint() { load(OverriddenOperationWebEndpointExtensionConfiguration.class, (discoverer) -> { - Map endpoints = mapEndpoints( + Map endpoints = mapEndpoints( discoverer.getEndpoints()); - assertThat(endpoints).containsOnlyKeys("test"); - ExposableWebEndpoint endpoint = endpoints.get("test"); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test")); + ExposableWebEndpoint endpoint = endpoints.get(EndpointId.of("test")); assertThat(requestPredicates(endpoint)).has( requestPredicates(path("test").httpMethod(WebEndpointHttpMethod.GET) .consumes().produces("application/json"))); @@ -113,10 +114,10 @@ public class WebEndpointDiscovererTests { @Test public void getEndpointsWhenExtensionAddsOperationShouldHaveBothOperations() { load(AdditionalOperationWebEndpointConfiguration.class, (discoverer) -> { - Map endpoints = mapEndpoints( + Map endpoints = mapEndpoints( discoverer.getEndpoints()); - assertThat(endpoints).containsOnlyKeys("test"); - ExposableWebEndpoint endpoint = endpoints.get("test"); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test")); + ExposableWebEndpoint endpoint = endpoints.get(EndpointId.of("test")); assertThat(requestPredicates(endpoint)).has(requestPredicates( path("test").httpMethod(WebEndpointHttpMethod.GET).consumes() .produces("application/json"), @@ -128,10 +129,10 @@ public class WebEndpointDiscovererTests { @Test public void getEndpointsWhenPredicateForWriteOperationThatReturnsVoidShouldHaveNoProducedMediaTypes() { load(VoidWriteOperationEndpointConfiguration.class, (discoverer) -> { - Map endpoints = mapEndpoints( + Map endpoints = mapEndpoints( discoverer.getEndpoints()); - assertThat(endpoints).containsOnlyKeys("voidwrite"); - ExposableWebEndpoint endpoint = endpoints.get("voidwrite"); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("voidwrite")); + ExposableWebEndpoint endpoint = endpoints.get(EndpointId.of("voidwrite")); assertThat(requestPredicates(endpoint)).has(requestPredicates( path("voidwrite").httpMethod(WebEndpointHttpMethod.POST).produces() .consumes("application/json"))); @@ -191,10 +192,10 @@ public class WebEndpointDiscovererTests { @Test public void getEndpointsWhenHasCacheWithTtlShouldCacheReadOperationWithTtlValue() { load((id) -> 500L, (id) -> id, TestEndpointConfiguration.class, (discoverer) -> { - Map endpoints = mapEndpoints( + Map endpoints = mapEndpoints( discoverer.getEndpoints()); - assertThat(endpoints).containsOnlyKeys("test"); - ExposableWebEndpoint endpoint = endpoints.get("test"); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test")); + ExposableWebEndpoint endpoint = endpoints.get(EndpointId.of("test")); assertThat(endpoint.getOperations()).hasSize(1); WebOperation operation = endpoint.getOperations().iterator().next(); Object invoker = ReflectionTestUtils.getField(operation, "invoker"); @@ -207,10 +208,10 @@ public class WebEndpointDiscovererTests { @Test public void getEndpointsWhenOperationReturnsResourceShouldProduceApplicationOctetStream() { load(ResourceEndpointConfiguration.class, (discoverer) -> { - Map endpoints = mapEndpoints( + Map endpoints = mapEndpoints( discoverer.getEndpoints()); - assertThat(endpoints).containsOnlyKeys("resource"); - ExposableWebEndpoint endpoint = endpoints.get("resource"); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("resource")); + ExposableWebEndpoint endpoint = endpoints.get(EndpointId.of("resource")); assertThat(requestPredicates(endpoint)).has(requestPredicates( path("resource").httpMethod(WebEndpointHttpMethod.GET).consumes() .produces("application/octet-stream"))); @@ -220,10 +221,11 @@ public class WebEndpointDiscovererTests { @Test public void getEndpointsWhenHasCustomMediaTypeShouldProduceCustomMediaType() { load(CustomMediaTypesEndpointConfiguration.class, (discoverer) -> { - Map endpoints = mapEndpoints( + Map endpoints = mapEndpoints( discoverer.getEndpoints()); - assertThat(endpoints).containsOnlyKeys("custommediatypes"); - ExposableWebEndpoint endpoint = endpoints.get("custommediatypes"); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("custommediatypes")); + ExposableWebEndpoint endpoint = endpoints + .get(EndpointId.of("custommediatypes")); assertThat(requestPredicates(endpoint)).has(requestPredicates( path("custommediatypes").httpMethod(WebEndpointHttpMethod.GET) .consumes().produces("text/plain"), @@ -238,10 +240,10 @@ public class WebEndpointDiscovererTests { public void getEndpointsWhenHasCustomPathShouldReturnCustomPath() { load((id) -> null, (id) -> "custom/" + id, AdditionalOperationWebEndpointConfiguration.class, (discoverer) -> { - Map endpoints = mapEndpoints( + Map endpoints = mapEndpoints( discoverer.getEndpoints()); - assertThat(endpoints).containsOnlyKeys("test"); - ExposableWebEndpoint endpoint = endpoints.get("test"); + assertThat(endpoints).containsOnlyKeys(EndpointId.of("test")); + ExposableWebEndpoint endpoint = endpoints.get(EndpointId.of("test")); Condition> expected = requestPredicates( path("custom/test").httpMethod(WebEndpointHttpMethod.GET) .consumes().produces("application/json"), @@ -276,10 +278,11 @@ public class WebEndpointDiscovererTests { } } - private Map mapEndpoints( + private Map mapEndpoints( Collection endpoints) { - Map endpointById = new HashMap<>(); - endpoints.forEach((endpoint) -> endpointById.put(endpoint.getId(), endpoint)); + Map endpointById = new HashMap<>(); + endpoints.forEach( + (endpoint) -> endpointById.put(endpoint.getEndpointId(), endpoint)); return endpointById; } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/ControllerEndpointHandlerMappingTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/ControllerEndpointHandlerMappingTests.java index c789cb0b63..daf13f0681 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/ControllerEndpointHandlerMappingTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/reactive/ControllerEndpointHandlerMappingTests.java @@ -22,6 +22,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint; import org.springframework.boot.actuate.endpoint.web.annotation.ExposableControllerEndpoint; @@ -121,22 +122,22 @@ public class ControllerEndpointHandlerMappingTests { } private ExposableControllerEndpoint firstEndpoint() { - return mockEndpoint("first", new FirstTestMvcEndpoint()); + return mockEndpoint(EndpointId.of("first"), new FirstTestMvcEndpoint()); } private ExposableControllerEndpoint secondEndpoint() { - return mockEndpoint("second", new SecondTestMvcEndpoint()); + return mockEndpoint(EndpointId.of("second"), new SecondTestMvcEndpoint()); } private ExposableControllerEndpoint pathlessEndpoint() { - return mockEndpoint("pathless", new PathlessControllerEndpoint()); + return mockEndpoint(EndpointId.of("pathless"), new PathlessControllerEndpoint()); } - private ExposableControllerEndpoint mockEndpoint(String id, Object controller) { + private ExposableControllerEndpoint mockEndpoint(EndpointId id, Object controller) { ExposableControllerEndpoint endpoint = mock(ExposableControllerEndpoint.class); - given(endpoint.getId()).willReturn(id); + given(endpoint.getEndpointId()).willReturn(id); given(endpoint.getController()).willReturn(controller); - given(endpoint.getRootPath()).willReturn(id); + given(endpoint.getRootPath()).willReturn(id.toString()); return endpoint; } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/ControllerEndpointHandlerMappingTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/ControllerEndpointHandlerMappingTests.java index acdc57b70b..d159bacce3 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/ControllerEndpointHandlerMappingTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/servlet/ControllerEndpointHandlerMappingTests.java @@ -22,6 +22,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint; import org.springframework.boot.actuate.endpoint.web.annotation.ExposableControllerEndpoint; @@ -113,22 +114,22 @@ public class ControllerEndpointHandlerMappingTests { } private ExposableControllerEndpoint firstEndpoint() { - return mockEndpoint("first", new FirstTestMvcEndpoint()); + return mockEndpoint(EndpointId.of("first"), new FirstTestMvcEndpoint()); } private ExposableControllerEndpoint secondEndpoint() { - return mockEndpoint("second", new SecondTestMvcEndpoint()); + return mockEndpoint(EndpointId.of("second"), new SecondTestMvcEndpoint()); } private ExposableControllerEndpoint pathlessEndpoint() { - return mockEndpoint("pathless", new PathlessControllerEndpoint()); + return mockEndpoint(EndpointId.of("pathless"), new PathlessControllerEndpoint()); } - private ExposableControllerEndpoint mockEndpoint(String id, Object controller) { + private ExposableControllerEndpoint mockEndpoint(EndpointId id, Object controller) { ExposableControllerEndpoint endpoint = mock(ExposableControllerEndpoint.class); - given(endpoint.getId()).willReturn(id); + given(endpoint.getEndpointId()).willReturn(id); given(endpoint.getController()).willReturn(controller); - given(endpoint.getRootPath()).willReturn(id); + given(endpoint.getRootPath()).willReturn(id.toString()); return endpoint; } From 138d85477df69d8ced2d9fda7ce385b7ff14091f Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Sat, 13 Oct 2018 21:01:27 -0700 Subject: [PATCH 3/6] Support mixed case endpoint IDs with time-to-live Update the endpoint time-to-live binding logic so that mixed case endpoint IDs are supported. Prior to this commit an `InvalidConfigurationPropertyNameException` would be thrown when using a camel case endpoint ID. See gh-14773 --- .../EndpointIdTimeToLivePropertyFunction.java | 7 ++++--- ...CloudFoundryWebEndpointDiscovererTests.java | 4 ++-- ...pointIdTimeToLivePropertyFunctionTests.java | 18 ++++++++++++------ .../cache/CachingOperationInvokerAdvisor.java | 12 ++++++++++-- .../annotation/EndpointDiscovererTests.java | 4 ++-- .../CachingOperationInvokerAdvisorTests.java | 6 +++--- .../annotation/JmxEndpointDiscovererTests.java | 2 +- .../annotation/WebEndpointDiscovererTests.java | 5 +++-- 8 files changed, 37 insertions(+), 21 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointIdTimeToLivePropertyFunction.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointIdTimeToLivePropertyFunction.java index 494bea5b50..6db1fa6ab0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointIdTimeToLivePropertyFunction.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointIdTimeToLivePropertyFunction.java @@ -19,6 +19,7 @@ package org.springframework.boot.actuate.autoconfigure.endpoint; import java.time.Duration; import java.util.function.Function; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor; import org.springframework.boot.context.properties.bind.BindResult; import org.springframework.boot.context.properties.bind.Bindable; @@ -33,7 +34,7 @@ import org.springframework.core.env.PropertyResolver; * @author Stephane Nicoll * @author Phillip Webb */ -class EndpointIdTimeToLivePropertyFunction implements Function { +class EndpointIdTimeToLivePropertyFunction implements Function { private static final Bindable DURATION = Bindable.of(Duration.class); @@ -48,9 +49,9 @@ class EndpointIdTimeToLivePropertyFunction implements Function { } @Override - public Long apply(String endpointId) { + public Long apply(EndpointId endpointId) { String name = String.format("management.endpoint.%s.cache.time-to-live", - endpointId); + endpointId.toLowerCaseString()); BindResult duration = Binder.get(this.environment).bind(name, DURATION); return duration.map(Duration::toMillis).orElse(null); } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java index b40ea50a86..43bfff0f7a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/CloudFoundryWebEndpointDiscovererTests.java @@ -73,8 +73,8 @@ public class CloudFoundryWebEndpointDiscovererTests { this.load((id) -> null, (id) -> id, configuration, consumer); } - private void load(Function timeToLive, PathMapper endpointPathMapper, - Class configuration, + private void load(Function timeToLive, + PathMapper endpointPathMapper, Class configuration, Consumer consumer) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( configuration); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointIdTimeToLivePropertyFunctionTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointIdTimeToLivePropertyFunctionTests.java index 8e033bd102..5421cf78ab 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointIdTimeToLivePropertyFunctionTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/EndpointIdTimeToLivePropertyFunctionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.util.function.Function; import org.junit.Test; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.mock.env.MockEnvironment; import static org.assertj.core.api.Assertions.assertThat; @@ -34,21 +35,26 @@ public class EndpointIdTimeToLivePropertyFunctionTests { private final MockEnvironment environment = new MockEnvironment(); - private final Function timeToLive = new EndpointIdTimeToLivePropertyFunction( + private final Function timeToLive = new EndpointIdTimeToLivePropertyFunction( this.environment); @Test public void defaultConfiguration() { - Long result = this.timeToLive.apply("test"); + Long result = this.timeToLive.apply(EndpointId.of("test")); assertThat(result).isNull(); } @Test public void userConfiguration() { - this.environment.setProperty("management.endpoint.test.cache.time-to-live", - "500"); - Long result = this.timeToLive.apply("test"); + this.environment.setProperty( + "management.endpoint.another-test.cache.time-to-live", "500"); + Long result = this.timeToLive.apply(EndpointId.of("anotherTest")); assertThat(result).isEqualTo(500L); } + @Test + public void mixedCaseUserConfiguration() { + + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerAdvisor.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerAdvisor.java index 4e37f4e30a..ab97e2e745 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerAdvisor.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerAdvisor.java @@ -18,6 +18,7 @@ package org.springframework.boot.actuate.endpoint.invoker.cache; import java.util.function.Function; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; @@ -33,9 +34,10 @@ import org.springframework.boot.actuate.endpoint.invoke.OperationParameters; */ public class CachingOperationInvokerAdvisor implements OperationInvokerAdvisor { - private final Function endpointIdTimeToLive; + private final Function endpointIdTimeToLive; - public CachingOperationInvokerAdvisor(Function endpointIdTimeToLive) { + public CachingOperationInvokerAdvisor( + Function endpointIdTimeToLive) { this.endpointIdTimeToLive = endpointIdTimeToLive; } @@ -43,6 +45,12 @@ public class CachingOperationInvokerAdvisor implements OperationInvokerAdvisor { @Deprecated public OperationInvoker apply(String endpointId, OperationType operationType, OperationParameters parameters, OperationInvoker invoker) { + return apply(EndpointId.of(endpointId), operationType, parameters, invoker); + } + + @Override + public OperationInvoker apply(EndpointId endpointId, OperationType operationType, + OperationParameters parameters, OperationInvoker invoker) { if (operationType == OperationType.READ && !hasMandatoryParameter(parameters)) { Long timeToLive = this.endpointIdTimeToLive.apply(endpointId); if (timeToLive != null && timeToLive > 0) { diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscovererTests.java index 23dda7dabc..c5d508085f 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/annotation/EndpointDiscovererTests.java @@ -500,12 +500,12 @@ public class EndpointDiscovererTests { } TestEndpointDiscoverer(ApplicationContext applicationContext, - Function timeToLive) { + Function timeToLive) { this(applicationContext, timeToLive, Collections.emptyList()); } TestEndpointDiscoverer(ApplicationContext applicationContext, - Function timeToLive, + Function timeToLive, Collection> filters) { this(applicationContext, new ConversionServiceParameterValueMapper(), Collections.singleton(new CachingOperationInvokerAdvisor(timeToLive)), diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerAdvisorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerAdvisorTests.java index 95b3311640..b539c1d9e9 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerAdvisorTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerAdvisorTests.java @@ -51,7 +51,7 @@ public class CachingOperationInvokerAdvisorTests { private OperationInvoker invoker; @Mock - private Function timeToLive; + private Function timeToLive; private CachingOperationInvokerAdvisor advisor; @@ -85,7 +85,7 @@ public class CachingOperationInvokerAdvisorTests { OperationInvoker advised = this.advisor.apply(EndpointId.of("foo"), OperationType.READ, parameters, this.invoker); assertThat(advised).isSameAs(this.invoker); - verify(this.timeToLive).apply("foo"); + verify(this.timeToLive).apply(EndpointId.of("foo")); } @Test @@ -95,7 +95,7 @@ public class CachingOperationInvokerAdvisorTests { OperationInvoker advised = this.advisor.apply(EndpointId.of("foo"), OperationType.READ, parameters, this.invoker); assertThat(advised).isSameAs(this.invoker); - verify(this.timeToLive).apply("foo"); + verify(this.timeToLive).apply(EndpointId.of("foo")); } @Test diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscovererTests.java index f4085b7fbc..ff0b7d10c0 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxEndpointDiscovererTests.java @@ -306,7 +306,7 @@ public class JmxEndpointDiscovererTests { load(configuration, (id) -> null, consumer); } - private void load(Class configuration, Function timeToLive, + private void load(Class configuration, Function timeToLive, Consumer consumer) { try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( configuration)) { diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscovererTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscovererTests.java index 2eb59000f7..a2d64d0996 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscovererTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/WebEndpointDiscovererTests.java @@ -257,8 +257,9 @@ public class WebEndpointDiscovererTests { this.load((id) -> null, (id) -> id, configuration, consumer); } - private void load(Function timeToLive, PathMapper endpointPathMapper, - Class configuration, Consumer consumer) { + private void load(Function timeToLive, + PathMapper endpointPathMapper, Class configuration, + Consumer consumer) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( configuration); try { From 674a909bab96f760c1f529831a94ab491073816c Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Sat, 13 Oct 2018 22:50:35 -0700 Subject: [PATCH 4/6] Support mixed case endpoint IDs with enabled Update `OnEnabledEndpointCondition` so that mixed case endpoint IDs are supported. Prior to this commit an `InvalidConfigurationPropertyNameException` would be thrown when trying to enabled or disable an endpoint with a camel case ID. See gh-14773 --- .../condition/OnEnabledEndpointCondition.java | 5 ++-- .../ConditionalOnEnabledEndpointTests.java | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnEnabledEndpointCondition.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnEnabledEndpointCondition.java index ef25e29311..61bed1d9f0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnEnabledEndpointCondition.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/OnEnabledEndpointCondition.java @@ -19,6 +19,7 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.condition; import java.util.Map; import java.util.Optional; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.EndpointExtension; import org.springframework.boot.autoconfigure.condition.ConditionMessage; @@ -54,8 +55,8 @@ class OnEnabledEndpointCondition extends SpringBootCondition { AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); AnnotationAttributes attributes = getEndpointAttributes(context, metadata); - String id = attributes.getString("id"); - String key = "management.endpoint." + id + ".enabled"; + EndpointId id = EndpointId.of(attributes.getString("id")); + String key = "management.endpoint." + id.toLowerCaseString() + ".enabled"; Boolean userDefinedEnabled = environment.getProperty(key, Boolean.class); if (userDefinedEnabled != null) { return new ConditionOutcome(userDefinedEnabled, diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnEnabledEndpointTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnEnabledEndpointTests.java index a83516afc2..715fcdb43b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnEnabledEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnEnabledEndpointTests.java @@ -140,6 +140,14 @@ public class ConditionalOnEnabledEndpointTests { }); } + @Test + public void outcomeWhenEndpointEnabledPropertyIsTrueAndMixedCaseShouldMatch() { + this.contextRunner.withPropertyValues("management.endpoint.foo-bar.enabled=true") + .withUserConfiguration( + FooBarEndpointEnabledByDefaultFalseConfiguration.class) + .run((context) -> assertThat(context).hasBean("fooBar")); + } + @Endpoint(id = "foo", enableByDefault = true) static class FooEndpointEnabledByDefaultTrue { @@ -150,6 +158,11 @@ public class ConditionalOnEnabledEndpointTests { } + @Endpoint(id = "fooBar", enableByDefault = false) + static class FooBarEndpointEnabledByDefaultFalse { + + } + @EndpointExtension(endpoint = FooEndpointEnabledByDefaultTrue.class, filter = TestFilter.class) static class FooEndpointExtensionEnabledByDefaultTrue { @@ -191,6 +204,17 @@ public class ConditionalOnEnabledEndpointTests { } + @Configuration + static class FooBarEndpointEnabledByDefaultFalseConfiguration { + + @Bean + @ConditionalOnEnabledEndpoint + public FooBarEndpointEnabledByDefaultFalse fooBar() { + return new FooBarEndpointEnabledByDefaultFalse(); + } + + } + @Configuration static class FooEndpointAndExtensionEnabledByDefaultTrueConfiguration { From df5dfbf4be36b7c3f96f2c332db8bc2aa82dcbc7 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Sat, 13 Oct 2018 23:20:16 -0700 Subject: [PATCH 5/6] Support mixed case endpoint includes/excludes Update `ExposeExcludePropertyEndpointFilter` so that mixed case endpoint IDs are supported. Prior to this commit it was not easy for an endpoint to be missed by the filter due to the formatting of the property value. See gh-14773 --- .../ExposeExcludePropertyEndpointFilter.java | 13 ++++++++++++- .../ExposeExcludePropertyEndpointFilterTests.java | 6 ++++++ .../boot/actuate/endpoint/EndpointId.java | 10 ++++++++++ .../boot/actuate/endpoint/EndpointIdTests.java | 6 ++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilter.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilter.java index c435e84d94..0504d89031 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilter.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilter.java @@ -21,11 +21,13 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Set; import java.util.stream.Collectors; import org.springframework.boot.actuate.endpoint.EndpointFilter; +import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; @@ -74,10 +76,19 @@ public class ExposeExcludePropertyEndpointFilter> } private Set bind(Binder binder, String name) { - return asSet(binder.bind(name, Bindable.listOf(String.class)) + return asSet(binder.bind(name, Bindable.listOf(String.class)).map(this::cleanup) .orElseGet(ArrayList::new)); } + private List cleanup(List values) { + return values.stream().map(this::cleanup).collect(Collectors.toList()); + } + + private String cleanup(String value) { + return "*".equals(value) ? "*" + : EndpointId.fromPropertyValue(value).toLowerCaseString(); + } + private Set asSet(Collection items) { if (items == null) { return Collections.emptySet(); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilterTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilterTests.java index e386c989a2..98baeb8c59 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilterTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/ExposeExcludePropertyEndpointFilterTests.java @@ -147,6 +147,12 @@ public class ExposeExcludePropertyEndpointFilterTests { assertThat(match(EndpointId.of("buz"))).isFalse(); } + @Test + public void matchWhenMixedCaseShouldMatch() { + setupFilter("foo-bar", ""); + assertThat(match(EndpointId.of("fooBar"))).isTrue(); + } + private void setupFilter(String include, String exclude) { MockEnvironment environment = new MockEnvironment(); environment.setProperty("foo.include", include); diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java index 246ff3c29d..bb52d75bf9 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/EndpointId.java @@ -87,4 +87,14 @@ public final class EndpointId { return new EndpointId(value); } + /** + * Factory method to create a new {@link EndpointId} from a property value. Is more + * lenient that {@link #of(String)} to allow for common "relaxed" property variants. + * @param value the property value to convert + * @return an {@link EndpointId} instance + */ + public static EndpointId fromPropertyValue(String value) { + return new EndpointId(value.replace("-", "")); + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EndpointIdTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EndpointIdTests.java index 9c11e8f2ef..7f085211b0 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EndpointIdTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/EndpointIdTests.java @@ -93,4 +93,10 @@ public class EndpointIdTests { assertThat(EndpointId.of("fooBar").toString()).isEqualTo("fooBar"); } + @Test + public void fromPropertyValueStripsDashes() { + EndpointId fromPropertyValue = EndpointId.fromPropertyValue("foo-bar"); + assertThat(fromPropertyValue).isEqualTo(EndpointId.of("fooBar")); + } + } From a00ee15e16ca24ab4d56c6f876f9ab50b55ae217 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Sat, 13 Oct 2018 23:10:37 -0700 Subject: [PATCH 6/6] Use lowercase default endpoint paths Update `MappingWebEndpointPathMapper` to use the lowercase version of the endpoint ID when no explicit path mapping has been set. An endpoint with the ID 'myEndpoint' will now be mapped to the path 'myendpoint'. See gh-14773 --- .../web/MappingWebEndpointPathMapper.java | 7 +++++-- .../web/MappingWebEndpointPathMapperTests.java | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/MappingWebEndpointPathMapper.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/MappingWebEndpointPathMapper.java index 64bdbf39fd..9bed06f81b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/MappingWebEndpointPathMapper.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/MappingWebEndpointPathMapper.java @@ -16,6 +16,7 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web; +import java.util.HashMap; import java.util.Map; import org.springframework.boot.actuate.endpoint.EndpointId; @@ -29,10 +30,12 @@ import org.springframework.boot.actuate.endpoint.web.PathMapper; */ class MappingWebEndpointPathMapper implements PathMapper { - private final Map pathMapping; + private final Map pathMapping; MappingWebEndpointPathMapper(Map pathMapping) { - this.pathMapping = pathMapping; + this.pathMapping = new HashMap<>(); + pathMapping.forEach((id, path) -> this.pathMapping + .put(EndpointId.fromPropertyValue(id), path)); } @Override diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/MappingWebEndpointPathMapperTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/MappingWebEndpointPathMapperTests.java index a16acdddbd..d9d1d0aa92 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/MappingWebEndpointPathMapperTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/MappingWebEndpointPathMapperTests.java @@ -45,4 +45,19 @@ public class MappingWebEndpointPathMapperTests { assertThat(mapper.getRootPath(EndpointId.of("test"))).isEqualTo("custom"); } + @Test + public void mixedCaseDefaultConfiguration() { + MappingWebEndpointPathMapper mapper = new MappingWebEndpointPathMapper( + Collections.emptyMap()); + assertThat(mapper.getRootPath(EndpointId.of("testEndpoint"))) + .isEqualTo("testendpoint"); + } + + @Test + public void mixedCaseUserConfiguration() { + MappingWebEndpointPathMapper mapper = new MappingWebEndpointPathMapper( + Collections.singletonMap("test-endpoint", "custom")); + assertThat(mapper.getRootPath(EndpointId.of("testEndpoint"))).isEqualTo("custom"); + } + }