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
pull/14914/head
Phillip Webb 6 years ago
parent c5786c218d
commit 3105a38884

@ -40,19 +40,19 @@ public enum AccessLevel {
public static final String REQUEST_ATTRIBUTE = "cloudFoundryAccessLevel"; public static final String REQUEST_ATTRIBUTE = "cloudFoundryAccessLevel";
private final List<String> endpointIds; private final List<String> ids;
AccessLevel(String... endpointIds) { AccessLevel(String... ids) {
this.endpointIds = Arrays.asList(endpointIds); this.ids = Arrays.asList(ids);
} }
/** /**
* Returns if the access level should allow access to the specified endpoint path. * Returns if the access level should allow access to the specified ID.
* @param endpointId the endpoint ID to check * @param id the ID to check
* @return {@code true} if access is allowed * @return {@code true} if access is allowed
*/ */
public boolean isAccessAllowed(String endpointId) { public boolean isAccessAllowed(String id) {
return this.endpointIds.isEmpty() || this.endpointIds.contains(endpointId); return this.ids.isEmpty() || this.ids.contains(id);
} }
} }

@ -59,7 +59,7 @@ class CloudFoundrySecurityInterceptor {
this.applicationId = applicationId; this.applicationId = applicationId;
} }
Mono<SecurityResponse> preHandle(ServerWebExchange exchange, String endpointId) { Mono<SecurityResponse> preHandle(ServerWebExchange exchange, String id) {
ServerHttpRequest request = exchange.getRequest(); ServerHttpRequest request = exchange.getRequest();
if (CorsUtils.isPreFlightRequest(request)) { if (CorsUtils.isPreFlightRequest(request)) {
return SUCCESS; return SUCCESS;
@ -72,7 +72,7 @@ class CloudFoundrySecurityInterceptor {
return Mono.error(new CloudFoundryAuthorizationException( return Mono.error(new CloudFoundryAuthorizationException(
Reason.SERVICE_UNAVAILABLE, "Cloud controller URL is not available")); 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); .onErrorResume(this::getErrorResponse);
} }
@ -80,13 +80,13 @@ class CloudFoundrySecurityInterceptor {
logger.error(ex.getMessage(), ex); logger.error(ex.getMessage(), ex);
} }
private Mono<Void> check(ServerWebExchange exchange, String path) { private Mono<Void> check(ServerWebExchange exchange, String id) {
try { try {
Token token = getToken(exchange.getRequest()); Token token = getToken(exchange.getRequest());
return this.tokenValidator.validate(token) return this.tokenValidator.validate(token)
.then(this.cloudFoundrySecurityService .then(this.cloudFoundrySecurityService
.getAccessLevel(token.toString(), this.applicationId)) .getAccessLevel(token.toString(), this.applicationId))
.filter((accessLevel) -> accessLevel.isAccessAllowed(path)) .filter((accessLevel) -> accessLevel.isAccessAllowed(id))
.switchIfEmpty(Mono.error(new CloudFoundryAuthorizationException( .switchIfEmpty(Mono.error(new CloudFoundryAuthorizationException(
Reason.ACCESS_DENIED, "Access denied"))) Reason.ACCESS_DENIED, "Access denied")))
.doOnSuccess((accessLevel) -> exchange.getAttributes() .doOnSuccess((accessLevel) -> exchange.getAttributes()

@ -27,6 +27,7 @@ import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse; 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.EndpointLinksResolver;
import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
@ -70,7 +71,7 @@ class CloudFoundryWebFluxEndpointHandlerMapping
protected ReactiveWebOperation wrapReactiveWebOperation(ExposableWebEndpoint endpoint, protected ReactiveWebOperation wrapReactiveWebOperation(ExposableWebEndpoint endpoint,
WebOperation operation, ReactiveWebOperation reactiveWebOperation) { WebOperation operation, ReactiveWebOperation reactiveWebOperation) {
return new SecureReactiveWebOperation(reactiveWebOperation, return new SecureReactiveWebOperation(reactiveWebOperation,
this.securityInterceptor, endpoint.getId()); this.securityInterceptor, endpoint.getEndpointId());
} }
@Override @Override
@ -113,10 +114,11 @@ class CloudFoundryWebFluxEndpointHandlerMapping
private final CloudFoundrySecurityInterceptor securityInterceptor; private final CloudFoundrySecurityInterceptor securityInterceptor;
private final String endpointId; private final EndpointId endpointId;
SecureReactiveWebOperation(ReactiveWebOperation delegate, SecureReactiveWebOperation(ReactiveWebOperation delegate,
CloudFoundrySecurityInterceptor securityInterceptor, String endpointId) { CloudFoundrySecurityInterceptor securityInterceptor,
EndpointId endpointId) {
this.delegate = delegate; this.delegate = delegate;
this.securityInterceptor = securityInterceptor; this.securityInterceptor = securityInterceptor;
this.endpointId = endpointId; this.endpointId = endpointId;
@ -125,7 +127,8 @@ class CloudFoundryWebFluxEndpointHandlerMapping
@Override @Override
public Mono<ResponseEntity<Object>> handle(ServerWebExchange exchange, public Mono<ResponseEntity<Object>> handle(ServerWebExchange exchange,
Map<String, String> body) { Map<String, String> body) {
return this.securityInterceptor.preHandle(exchange, this.endpointId) return this.securityInterceptor
.preHandle(exchange, this.endpointId.toLowerCaseString())
.flatMap((securityResponse) -> flatMapResponse(exchange, body, .flatMap((securityResponse) -> flatMapResponse(exchange, body,
securityResponse)); securityResponse));
} }

@ -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.CloudFoundryAuthorizationException.Reason;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.Token; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.Token;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -59,7 +60,7 @@ class CloudFoundrySecurityInterceptor {
this.applicationId = applicationId; this.applicationId = applicationId;
} }
SecurityResponse preHandle(HttpServletRequest request, String endpointId) { SecurityResponse preHandle(HttpServletRequest request, EndpointId endpointId) {
if (CorsUtils.isPreFlightRequest(request)) { if (CorsUtils.isPreFlightRequest(request)) {
return SecurityResponse.success(); return SecurityResponse.success();
} }
@ -90,12 +91,14 @@ class CloudFoundrySecurityInterceptor {
return SecurityResponse.success(); 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); Token token = getToken(request);
this.tokenValidator.validate(token); this.tokenValidator.validate(token);
AccessLevel accessLevel = this.cloudFoundrySecurityService AccessLevel accessLevel = this.cloudFoundrySecurityService
.getAccessLevel(token.toString(), this.applicationId); .getAccessLevel(token.toString(), this.applicationId);
if (!accessLevel.isAccessAllowed(endpointId)) { if (!accessLevel.isAccessAllowed(
(endpointId != null) ? endpointId.toLowerCaseString() : "")) {
throw new CloudFoundryAuthorizationException(Reason.ACCESS_DENIED, throw new CloudFoundryAuthorizationException(Reason.ACCESS_DENIED,
"Access denied"); "Access denied");
} }

@ -27,6 +27,7 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.AccessLevel;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse; 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.EndpointLinksResolver;
import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
@ -68,7 +69,7 @@ class CloudFoundryWebEndpointServletHandlerMapping
protected ServletWebOperation wrapServletWebOperation(ExposableWebEndpoint endpoint, protected ServletWebOperation wrapServletWebOperation(ExposableWebEndpoint endpoint,
WebOperation operation, ServletWebOperation servletWebOperation) { WebOperation operation, ServletWebOperation servletWebOperation) {
return new SecureServletWebOperation(servletWebOperation, return new SecureServletWebOperation(servletWebOperation,
this.securityInterceptor, endpoint.getId()); this.securityInterceptor, endpoint.getEndpointId());
} }
@Override @Override
@ -76,7 +77,7 @@ class CloudFoundryWebEndpointServletHandlerMapping
protected Map<String, Map<String, Link>> links(HttpServletRequest request, protected Map<String, Map<String, Link>> links(HttpServletRequest request,
HttpServletResponse response) { HttpServletResponse response) {
SecurityResponse securityResponse = this.securityInterceptor.preHandle(request, SecurityResponse securityResponse = this.securityInterceptor.preHandle(request,
""); null);
if (!securityResponse.getStatus().equals(HttpStatus.OK)) { if (!securityResponse.getStatus().equals(HttpStatus.OK)) {
sendFailureResponse(response, securityResponse); sendFailureResponse(response, securityResponse);
} }
@ -115,10 +116,11 @@ class CloudFoundryWebEndpointServletHandlerMapping
private final CloudFoundrySecurityInterceptor securityInterceptor; private final CloudFoundrySecurityInterceptor securityInterceptor;
private final String endpointId; private final EndpointId endpointId;
SecureServletWebOperation(ServletWebOperation delegate, SecureServletWebOperation(ServletWebOperation delegate,
CloudFoundrySecurityInterceptor securityInterceptor, String endpointId) { CloudFoundrySecurityInterceptor securityInterceptor,
EndpointId endpointId) {
this.delegate = delegate; this.delegate = delegate;
this.securityInterceptor = securityInterceptor; this.securityInterceptor = securityInterceptor;
this.endpointId = endpointId; this.endpointId = endpointId;

@ -110,7 +110,7 @@ public class ExposeExcludePropertyEndpointFilter<E extends ExposableEndpoint<?>>
} }
private boolean contains(Set<String> items, ExposableEndpoint<?> endpoint) { private boolean contains(Set<String> items, ExposableEndpoint<?> endpoint) {
return items.contains(endpoint.getId().toLowerCase(Locale.ENGLISH)); return items.contains(endpoint.getEndpointId().toLowerCaseString());
} }
} }

@ -52,7 +52,8 @@ class DefaultEndpointObjectNameFactory implements EndpointObjectNameFactory {
throws MalformedObjectNameException { throws MalformedObjectNameException {
StringBuilder builder = new StringBuilder(this.properties.getDomain()); StringBuilder builder = new StringBuilder(this.properties.getDomain());
builder.append(":type=Endpoint"); builder.append(":type=Endpoint");
builder.append(",name=" + StringUtils.capitalize(endpoint.getId())); builder.append(
",name=" + StringUtils.capitalize(endpoint.getEndpointId().toString()));
String baseName = builder.toString(); String baseName = builder.toString();
if (this.mBeanServer != null && hasMBean(baseName)) { if (this.mBeanServer != null && hasMBean(baseName)) {
builder.append(",context=" + this.contextId); builder.append(",context=" + this.contextId);

@ -18,6 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web;
import java.util.Map; import java.util.Map;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.web.PathMapper; import org.springframework.boot.actuate.endpoint.web.PathMapper;
/** /**
@ -35,8 +36,14 @@ class MappingWebEndpointPathMapper implements PathMapper {
} }
@Override @Override
@Deprecated
public String getRootPath(String endpointId) { 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());
} }
} }

@ -31,6 +31,7 @@ import reactor.core.publisher.Mono;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; 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.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
import org.springframework.boot.security.reactive.ApplicationContextServerWebExchangeMatcher; import org.springframework.boot.security.reactive.ApplicationContextServerWebExchangeMatcher;
@ -208,9 +209,12 @@ public final class EndpointRequest {
.map(pathMappedEndpoints::getPath); .map(pathMappedEndpoints::getPath);
} }
private String getEndpointId(Object source) { private EndpointId getEndpointId(Object source) {
if (source instanceof EndpointId) {
return (EndpointId) source;
}
if (source instanceof String) { if (source instanceof String) {
return (String) source; return (EndpointId.of((String) source));
} }
if (source instanceof Class) { if (source instanceof Class) {
return getEndpointId((Class<?>) source); return getEndpointId((Class<?>) source);
@ -218,12 +222,12 @@ public final class EndpointRequest {
throw new IllegalStateException("Unsupported source " + source); throw new IllegalStateException("Unsupported source " + source);
} }
private String getEndpointId(Class<?> source) { private EndpointId getEndpointId(Class<?> source) {
Endpoint annotation = AnnotatedElementUtils.getMergedAnnotation(source, Endpoint annotation = AnnotatedElementUtils.getMergedAnnotation(source,
Endpoint.class); Endpoint.class);
Assert.state(annotation != null, Assert.state(annotation != null,
() -> "Class " + source + " is not annotated with @Endpoint"); () -> "Class " + source + " is not annotated with @Endpoint");
return annotation.id(); return EndpointId.of(annotation.id());
} }
private List<ServerWebExchangeMatcher> getDelegateMatchers(Set<String> paths) { private List<ServerWebExchangeMatcher> getDelegateMatchers(Set<String> paths) {

@ -31,6 +31,7 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; 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.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
import org.springframework.boot.autoconfigure.security.servlet.RequestMatcherProvider; import org.springframework.boot.autoconfigure.security.servlet.RequestMatcherProvider;
@ -256,9 +257,12 @@ public final class EndpointRequest {
.map(pathMappedEndpoints::getPath); .map(pathMappedEndpoints::getPath);
} }
private String getEndpointId(Object source) { private EndpointId getEndpointId(Object source) {
if (source instanceof EndpointId) {
return (EndpointId) source;
}
if (source instanceof String) { if (source instanceof String) {
return (String) source; return (EndpointId.of((String) source));
} }
if (source instanceof Class) { if (source instanceof Class) {
return getEndpointId((Class<?>) source); return getEndpointId((Class<?>) source);
@ -266,12 +270,12 @@ public final class EndpointRequest {
throw new IllegalStateException("Unsupported source " + source); throw new IllegalStateException("Unsupported source " + source);
} }
private String getEndpointId(Class<?> source) { private EndpointId getEndpointId(Class<?> source) {
Endpoint annotation = AnnotatedElementUtils.getMergedAnnotation(source, Endpoint annotation = AnnotatedElementUtils.getMergedAnnotation(source,
Endpoint.class); Endpoint.class);
Assert.state(annotation != null, Assert.state(annotation != null,
() -> "Class " + source + " is not annotated with @Endpoint"); () -> "Class " + source + " is not annotated with @Endpoint");
return annotation.id(); return EndpointId.of(annotation.id());
} }
private List<RequestMatcher> getDelegateMatchers( private List<RequestMatcher> getDelegateMatchers(

@ -23,6 +23,7 @@ import java.util.function.Function;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.InvocationContext; import org.springframework.boot.actuate.endpoint.InvocationContext;
import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
@ -57,7 +58,7 @@ public class CloudFoundryWebEndpointDiscovererTests {
Collection<ExposableWebEndpoint> endpoints = discoverer.getEndpoints(); Collection<ExposableWebEndpoint> endpoints = discoverer.getEndpoints();
assertThat(endpoints.size()).isEqualTo(2); assertThat(endpoints.size()).isEqualTo(2);
for (ExposableWebEndpoint endpoint : endpoints) { for (ExposableWebEndpoint endpoint : endpoints) {
if (endpoint.getId().equals("health")) { if (endpoint.getEndpointId().equals(EndpointId.of("health"))) {
WebOperation operation = endpoint.getOperations().iterator().next(); WebOperation operation = endpoint.getOperations().iterator().next();
assertThat(operation.invoke(new InvocationContext( assertThat(operation.invoke(new InvocationContext(
mock(SecurityContext.class), Collections.emptyMap()))) mock(SecurityContext.class), Collections.emptyMap())))

@ -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.endpoint.web.WebEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; 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.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
@ -243,9 +244,10 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
context); context);
Collection<ExposableWebEndpoint> endpoints = handlerMapping Collection<ExposableWebEndpoint> endpoints = handlerMapping
.getEndpoints(); .getEndpoints();
List<String> endpointIds = endpoints.stream() List<EndpointId> endpointIds = endpoints.stream()
.map(ExposableEndpoint::getId).collect(Collectors.toList()); .map(ExposableEndpoint::getEndpointId)
assertThat(endpointIds).contains("test"); .collect(Collectors.toList());
assertThat(endpointIds).contains(EndpointId.of("test"));
}); });
} }
@ -261,7 +263,8 @@ public class ReactiveCloudFoundryActuatorAutoConfigurationTests {
Collection<ExposableWebEndpoint> endpoints = handlerMapping Collection<ExposableWebEndpoint> endpoints = handlerMapping
.getEndpoints(); .getEndpoints();
ExposableWebEndpoint endpoint = endpoints.stream() ExposableWebEndpoint endpoint = endpoints.stream()
.filter((candidate) -> "test".equals(candidate.getId())) .filter((candidate) -> EndpointId.of("test")
.equals(candidate.getEndpointId()))
.findFirst().get(); .findFirst().get();
assertThat(endpoint.getOperations()).hasSize(1); assertThat(endpoint.getOperations()).hasSize(1);
WebOperation operation = endpoint.getOperations().iterator().next(); WebOperation operation = endpoint.getOperations().iterator().next();

@ -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.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration; 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.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType; import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
@ -235,7 +236,8 @@ public class CloudFoundryActuatorAutoConfigurationTests {
Collection<ExposableWebEndpoint> endpoints = handlerMapping Collection<ExposableWebEndpoint> endpoints = handlerMapping
.getEndpoints(); .getEndpoints();
assertThat(endpoints.stream() assertThat(endpoints.stream()
.filter((candidate) -> "test".equals(candidate.getId())) .filter((candidate) -> EndpointId.of("test")
.equals(candidate.getEndpointId()))
.findFirst()).isNotEmpty(); .findFirst()).isNotEmpty();
}); });
} }
@ -253,7 +255,8 @@ public class CloudFoundryActuatorAutoConfigurationTests {
Collection<ExposableWebEndpoint> endpoints = handlerMapping Collection<ExposableWebEndpoint> endpoints = handlerMapping
.getEndpoints(); .getEndpoints();
ExposableWebEndpoint endpoint = endpoints.stream() ExposableWebEndpoint endpoint = endpoints.stream()
.filter((candidate) -> "test".equals(candidate.getId())) .filter((candidate) -> EndpointId.of("test")
.equals(candidate.getEndpointId()))
.findFirst().get(); .findFirst().get();
Collection<WebOperation> operations = endpoint.getOperations(); Collection<WebOperation> operations = endpoint.getOperations();
assertThat(operations).hasSize(1); assertThat(operations).hasSize(1);

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.CloudFoundryAuthorizationException.Reason;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.SecurityResponse;
import org.springframework.boot.actuate.autoconfigure.cloudfoundry.Token; import org.springframework.boot.actuate.autoconfigure.cloudfoundry.Token;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
@ -65,13 +66,15 @@ public class CloudFoundrySecurityInterceptorTests {
this.request.setMethod("OPTIONS"); this.request.setMethod("OPTIONS");
this.request.addHeader(HttpHeaders.ORIGIN, "http://example.com"); this.request.addHeader(HttpHeaders.ORIGIN, "http://example.com");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"); 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); assertThat(response.getStatus()).isEqualTo(HttpStatus.OK);
} }
@Test @Test
public void preHandleWhenTokenIsMissingShouldReturnFalse() { public void preHandleWhenTokenIsMissingShouldReturnFalse() {
SecurityResponse response = this.interceptor.preHandle(this.request, "/a"); SecurityResponse response = this.interceptor.preHandle(this.request,
EndpointId.of("test"));
assertThat(response.getStatus()) assertThat(response.getStatus())
.isEqualTo(Reason.MISSING_AUTHORIZATION.getStatus()); .isEqualTo(Reason.MISSING_AUTHORIZATION.getStatus());
} }
@ -79,7 +82,8 @@ public class CloudFoundrySecurityInterceptorTests {
@Test @Test
public void preHandleWhenTokenIsNotBearerShouldReturnFalse() { public void preHandleWhenTokenIsNotBearerShouldReturnFalse() {
this.request.addHeader("Authorization", mockAccessToken()); 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()) assertThat(response.getStatus())
.isEqualTo(Reason.MISSING_AUTHORIZATION.getStatus()); .isEqualTo(Reason.MISSING_AUTHORIZATION.getStatus());
} }
@ -89,7 +93,8 @@ public class CloudFoundrySecurityInterceptorTests {
this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator, this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator,
this.securityService, null); this.securityService, null);
this.request.addHeader("Authorization", "bearer " + mockAccessToken()); 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()) assertThat(response.getStatus())
.isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus()); .isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus());
} }
@ -99,7 +104,8 @@ public class CloudFoundrySecurityInterceptorTests {
this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator, null, this.interceptor = new CloudFoundrySecurityInterceptor(this.tokenValidator, null,
"my-app-id"); "my-app-id");
this.request.addHeader("Authorization", "bearer " + mockAccessToken()); 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()) assertThat(response.getStatus())
.isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus()); .isEqualTo(Reason.SERVICE_UNAVAILABLE.getStatus());
} }
@ -110,7 +116,8 @@ public class CloudFoundrySecurityInterceptorTests {
this.request.addHeader("Authorization", "bearer " + accessToken); this.request.addHeader("Authorization", "bearer " + accessToken);
given(this.securityService.getAccessLevel(accessToken, "my-app-id")) given(this.securityService.getAccessLevel(accessToken, "my-app-id"))
.willReturn(AccessLevel.RESTRICTED); .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()); assertThat(response.getStatus()).isEqualTo(Reason.ACCESS_DENIED.getStatus());
} }
@ -120,7 +127,8 @@ public class CloudFoundrySecurityInterceptorTests {
this.request.addHeader("Authorization", "Bearer " + accessToken); this.request.addHeader("Authorization", "Bearer " + accessToken);
given(this.securityService.getAccessLevel(accessToken, "my-app-id")) given(this.securityService.getAccessLevel(accessToken, "my-app-id"))
.willReturn(AccessLevel.FULL); .willReturn(AccessLevel.FULL);
SecurityResponse response = this.interceptor.preHandle(this.request, "/a"); SecurityResponse response = this.interceptor.preHandle(this.request,
EndpointId.of("test"));
ArgumentCaptor<Token> tokenArgumentCaptor = ArgumentCaptor.forClass(Token.class); ArgumentCaptor<Token> tokenArgumentCaptor = ArgumentCaptor.forClass(Token.class);
verify(this.tokenValidator).validate(tokenArgumentCaptor.capture()); verify(this.tokenValidator).validate(tokenArgumentCaptor.capture());
Token token = tokenArgumentCaptor.getValue(); Token token = tokenArgumentCaptor.getValue();
@ -136,7 +144,8 @@ public class CloudFoundrySecurityInterceptorTests {
this.request.addHeader("Authorization", "Bearer " + accessToken); this.request.addHeader("Authorization", "Bearer " + accessToken);
given(this.securityService.getAccessLevel(accessToken, "my-app-id")) given(this.securityService.getAccessLevel(accessToken, "my-app-id"))
.willReturn(AccessLevel.RESTRICTED); .willReturn(AccessLevel.RESTRICTED);
SecurityResponse response = this.interceptor.preHandle(this.request, "info"); SecurityResponse response = this.interceptor.preHandle(this.request,
EndpointId.of("info"));
ArgumentCaptor<Token> tokenArgumentCaptor = ArgumentCaptor.forClass(Token.class); ArgumentCaptor<Token> tokenArgumentCaptor = ArgumentCaptor.forClass(Token.class);
verify(this.tokenValidator).validate(tokenArgumentCaptor.capture()); verify(this.tokenValidator).validate(tokenArgumentCaptor.capture());
Token token = tokenArgumentCaptor.getValue(); Token token = tokenArgumentCaptor.getValue();

@ -23,6 +23,7 @@ import org.junit.rules.ExpectedException;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.springframework.boot.actuate.endpoint.EndpointFilter; 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.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.mock.env.MockEnvironment; import org.springframework.mock.env.MockEnvironment;
@ -81,43 +82,43 @@ public class ExposeExcludePropertyEndpointFilterTests {
@Test @Test
public void matchWhenExposeIsEmptyAndExcludeIsEmptyAndInDefaultShouldMatch() { public void matchWhenExposeIsEmptyAndExcludeIsEmptyAndInDefaultShouldMatch() {
setupFilter("", ""); setupFilter("", "");
assertThat(match("def")).isTrue(); assertThat(match(EndpointId.of("def"))).isTrue();
} }
@Test @Test
public void matchWhenExposeIsEmptyAndExcludeIsEmptyAndNotInDefaultShouldNotMatch() { public void matchWhenExposeIsEmptyAndExcludeIsEmptyAndNotInDefaultShouldNotMatch() {
setupFilter("", ""); setupFilter("", "");
assertThat(match("bar")).isFalse(); assertThat(match(EndpointId.of("bar"))).isFalse();
} }
@Test @Test
public void matchWhenExposeMatchesAndExcludeIsEmptyShouldMatch() { public void matchWhenExposeMatchesAndExcludeIsEmptyShouldMatch() {
setupFilter("bar", ""); setupFilter("bar", "");
assertThat(match("bar")).isTrue(); assertThat(match(EndpointId.of("bar"))).isTrue();
} }
@Test @Test
public void matchWhenExposeDoesNotMatchAndExcludeIsEmptyShouldNotMatch() { public void matchWhenExposeDoesNotMatchAndExcludeIsEmptyShouldNotMatch() {
setupFilter("bar", ""); setupFilter("bar", "");
assertThat(match("baz")).isFalse(); assertThat(match(EndpointId.of("baz"))).isFalse();
} }
@Test @Test
public void matchWhenExposeMatchesAndExcludeMatchesShouldNotMatch() { public void matchWhenExposeMatchesAndExcludeMatchesShouldNotMatch() {
setupFilter("bar,baz", "baz"); setupFilter("bar,baz", "baz");
assertThat(match("baz")).isFalse(); assertThat(match(EndpointId.of("baz"))).isFalse();
} }
@Test @Test
public void matchWhenExposeMatchesAndExcludeDoesNotMatchShouldMatch() { public void matchWhenExposeMatchesAndExcludeDoesNotMatchShouldMatch() {
setupFilter("bar,baz", "buz"); setupFilter("bar,baz", "buz");
assertThat(match("baz")).isTrue(); assertThat(match(EndpointId.of("baz"))).isTrue();
} }
@Test @Test
public void matchWhenExposeMatchesWithDifferentCaseShouldMatch() { public void matchWhenExposeMatchesWithDifferentCaseShouldMatch() {
setupFilter("bar", ""); setupFilter("bar", "");
assertThat(match("bAr")).isTrue(); assertThat(match(EndpointId.of("bAr"))).isTrue();
} }
@Test @Test
@ -127,23 +128,23 @@ public class ExposeExcludePropertyEndpointFilterTests {
environment.setProperty("foo.exclude", ""); environment.setProperty("foo.exclude", "");
this.filter = new ExposeExcludePropertyEndpointFilter<>( this.filter = new ExposeExcludePropertyEndpointFilter<>(
DifferentTestExposableWebEndpoint.class, environment, "foo"); DifferentTestExposableWebEndpoint.class, environment, "foo");
assertThat(match("baz")).isTrue(); assertThat(match(EndpointId.of("baz"))).isTrue();
} }
@Test @Test
public void matchWhenIncludeIsAsteriskShouldMatchAll() { public void matchWhenIncludeIsAsteriskShouldMatchAll() {
setupFilter("*", "buz"); setupFilter("*", "buz");
assertThat(match("bar")).isTrue(); assertThat(match(EndpointId.of("bar"))).isTrue();
assertThat(match("baz")).isTrue(); assertThat(match(EndpointId.of("baz"))).isTrue();
assertThat(match("buz")).isFalse(); assertThat(match(EndpointId.of("buz"))).isFalse();
} }
@Test @Test
public void matchWhenExcludeIsAsteriskShouldMatchNone() { public void matchWhenExcludeIsAsteriskShouldMatchNone() {
setupFilter("bar,baz,buz", "*"); setupFilter("bar,baz,buz", "*");
assertThat(match("bar")).isFalse(); assertThat(match(EndpointId.of("bar"))).isFalse();
assertThat(match("baz")).isFalse(); assertThat(match(EndpointId.of("baz"))).isFalse();
assertThat(match("buz")).isFalse(); assertThat(match(EndpointId.of("buz"))).isFalse();
} }
private void setupFilter(String include, String exclude) { private void setupFilter(String include, String exclude) {
@ -155,9 +156,9 @@ public class ExposeExcludePropertyEndpointFilterTests {
} }
@SuppressWarnings({ "rawtypes", "unchecked" }) @SuppressWarnings({ "rawtypes", "unchecked" })
private boolean match(String id) { private boolean match(EndpointId id) {
ExposableEndpoint<?> endpoint = mock(TestExposableWebEndpoint.class); ExposableEndpoint<?> endpoint = mock(TestExposableWebEndpoint.class);
given(endpoint.getId()).willReturn(id); given(endpoint.getEndpointId()).willReturn(id);
return ((EndpointFilter) this.filter).match(endpoint); return ((EndpointFilter) this.filter).match(endpoint);
} }

@ -24,6 +24,7 @@ import javax.management.ObjectName;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint; import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint;
import org.springframework.mock.env.MockEnvironment; import org.springframework.mock.env.MockEnvironment;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
@ -50,22 +51,23 @@ public class DefaultEndpointObjectNameFactoryTests {
@Test @Test
public void generateObjectName() { public void generateObjectName() {
ObjectName objectName = generateObjectName(endpoint("Test")); ObjectName objectName = generateObjectName(endpoint(EndpointId.of("test")));
assertThat(objectName.toString()) assertThat(objectName.toString())
.isEqualTo("org.springframework.boot:type=Endpoint,name=Test"); .isEqualTo("org.springframework.boot:type=Endpoint,name=Test");
} }
@Test @Test
public void generateObjectNameWithCapitalizedId() { public void generateObjectNameWithCapitalizedId() {
ObjectName objectName = generateObjectName(endpoint("test")); ObjectName objectName = generateObjectName(
endpoint(EndpointId.of("testEndpoint")));
assertThat(objectName.toString()) assertThat(objectName.toString())
.isEqualTo("org.springframework.boot:type=Endpoint,name=Test"); .isEqualTo("org.springframework.boot:type=Endpoint,name=TestEndpoint");
} }
@Test @Test
public void generateObjectNameWithCustomDomain() { public void generateObjectNameWithCustomDomain() {
this.properties.setDomain("com.example.acme"); this.properties.setDomain("com.example.acme");
ObjectName objectName = generateObjectName(endpoint("test")); ObjectName objectName = generateObjectName(endpoint(EndpointId.of("test")));
assertThat(objectName.toString()) assertThat(objectName.toString())
.isEqualTo("com.example.acme:type=Endpoint,name=Test"); .isEqualTo("com.example.acme:type=Endpoint,name=Test");
} }
@ -73,7 +75,7 @@ public class DefaultEndpointObjectNameFactoryTests {
@Test @Test
public void generateObjectNameWithUniqueNames() { public void generateObjectNameWithUniqueNames() {
this.properties.setUniqueNames(true); this.properties.setUniqueNames(true);
ExposableJmxEndpoint endpoint = endpoint("test"); ExposableJmxEndpoint endpoint = endpoint(EndpointId.of("test"));
String id = ObjectUtils.getIdentityHexString(endpoint); String id = ObjectUtils.getIdentityHexString(endpoint);
ObjectName objectName = generateObjectName(endpoint); ObjectName objectName = generateObjectName(endpoint);
assertThat(objectName.toString()).isEqualTo( assertThat(objectName.toString()).isEqualTo(
@ -84,7 +86,7 @@ public class DefaultEndpointObjectNameFactoryTests {
public void generateObjectNameWithStaticNames() { public void generateObjectNameWithStaticNames() {
this.properties.getStaticNames().setProperty("counter", "42"); this.properties.getStaticNames().setProperty("counter", "42");
this.properties.getStaticNames().setProperty("foo", "bar"); 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("counter")).isEqualTo("42");
assertThat(objectName.getKeyProperty("foo")).isEqualTo("bar"); assertThat(objectName.getKeyProperty("foo")).isEqualTo("bar");
assertThat(objectName.toString()) assertThat(objectName.toString())
@ -99,7 +101,7 @@ public class DefaultEndpointObjectNameFactoryTests {
null)).willReturn( null)).willReturn(
Collections.singleton(new ObjectName( Collections.singleton(new ObjectName(
"org.springframework.boot:type=Endpoint,name=Test"))); "org.springframework.boot:type=Endpoint,name=Test")));
ObjectName objectName = generateObjectName(endpoint("test")); ObjectName objectName = generateObjectName(endpoint(EndpointId.of("test")));
assertThat(objectName.toString()).isEqualTo( assertThat(objectName.toString()).isEqualTo(
"org.springframework.boot:type=Endpoint,name=Test,context=testContext"); "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); ExposableJmxEndpoint endpoint = mock(ExposableJmxEndpoint.class);
given(endpoint.getId()).willReturn(id); given(endpoint.getEndpointId()).willReturn(id);
return endpoint; return endpoint;
} }

@ -20,6 +20,8 @@ import java.util.Collections;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.endpoint.EndpointId;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
@ -33,14 +35,14 @@ public class MappingWebEndpointPathMapperTests {
public void defaultConfiguration() { public void defaultConfiguration() {
MappingWebEndpointPathMapper mapper = new MappingWebEndpointPathMapper( MappingWebEndpointPathMapper mapper = new MappingWebEndpointPathMapper(
Collections.emptyMap()); Collections.emptyMap());
assertThat(mapper.getRootPath("test")).isEqualTo("test"); assertThat(mapper.getRootPath(EndpointId.of("test"))).isEqualTo("test");
} }
@Test @Test
public void userConfiguration() { public void userConfiguration() {
MappingWebEndpointPathMapper mapper = new MappingWebEndpointPathMapper( MappingWebEndpointPathMapper mapper = new MappingWebEndpointPathMapper(
Collections.singletonMap("test", "custom")); Collections.singletonMap("test", "custom"));
assertThat(mapper.getRootPath("test")).isEqualTo("custom"); assertThat(mapper.getRootPath(EndpointId.of("test"))).isEqualTo("custom");
} }
} }

@ -20,6 +20,7 @@ import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter; 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.http.ActuatorMediaType;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.PathMapper; import org.springframework.boot.actuate.endpoint.web.PathMapper;
@ -65,7 +66,7 @@ public class WebEndpointAutoConfigurationTests {
.run((context) -> { .run((context) -> {
assertThat(context).hasSingleBean(PathMapper.class); assertThat(context).hasSingleBean(PathMapper.class);
String pathMapping = context.getBean(PathMapper.class) String pathMapping = context.getBean(PathMapper.class)
.getRootPath("health"); .getRootPath(EndpointId.of("health"));
assertThat(pathMapping).isEqualTo("healthcheck"); assertThat(pathMapping).isEqualTo("healthcheck");
}); });
} }

@ -23,6 +23,7 @@ import org.assertj.core.api.AssertDelegateTarget;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; 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.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
@ -129,9 +130,9 @@ public class EndpointRequestTests {
ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint() ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint()
.excluding(FooEndpoint.class, BazServletEndpoint.class); .excluding(FooEndpoint.class, BazServletEndpoint.class);
List<ExposableEndpoint<?>> endpoints = new ArrayList<>(); List<ExposableEndpoint<?>> endpoints = new ArrayList<>();
endpoints.add(mockEndpoint("foo", "foo")); endpoints.add(mockEndpoint(EndpointId.of("foo"), "foo"));
endpoints.add(mockEndpoint("bar", "bar")); endpoints.add(mockEndpoint(EndpointId.of("bar"), "bar"));
endpoints.add(mockEndpoint("baz", "baz")); endpoints.add(mockEndpoint(EndpointId.of("baz"), "baz"));
PathMappedEndpoints pathMappedEndpoints = new PathMappedEndpoints("/actuator", PathMappedEndpoints pathMappedEndpoints = new PathMappedEndpoints("/actuator",
() -> endpoints); () -> endpoints);
assertMatcher(matcher, pathMappedEndpoints).doesNotMatch("/actuator/foo"); assertMatcher(matcher, pathMappedEndpoints).doesNotMatch("/actuator/foo");
@ -202,14 +203,14 @@ public class EndpointRequestTests {
private PathMappedEndpoints mockPathMappedEndpoints(String basePath) { private PathMappedEndpoints mockPathMappedEndpoints(String basePath) {
List<ExposableEndpoint<?>> endpoints = new ArrayList<>(); List<ExposableEndpoint<?>> endpoints = new ArrayList<>();
endpoints.add(mockEndpoint("foo", "foo")); endpoints.add(mockEndpoint(EndpointId.of("foo"), "foo"));
endpoints.add(mockEndpoint("bar", "bar")); endpoints.add(mockEndpoint(EndpointId.of("bar"), "bar"));
return new PathMappedEndpoints(basePath, () -> endpoints); return new PathMappedEndpoints(basePath, () -> endpoints);
} }
private TestEndpoint mockEndpoint(String id, String rootPath) { private TestEndpoint mockEndpoint(EndpointId id, String rootPath) {
TestEndpoint endpoint = mock(TestEndpoint.class); TestEndpoint endpoint = mock(TestEndpoint.class);
given(endpoint.getId()).willReturn(id); given(endpoint.getEndpointId()).willReturn(id);
given(endpoint.getRootPath()).willReturn(rootPath); given(endpoint.getRootPath()).willReturn(rootPath);
return endpoint; return endpoint;
} }

@ -25,6 +25,7 @@ import org.assertj.core.api.AssertDelegateTarget;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; 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.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
@ -163,9 +164,9 @@ public class EndpointRequestTests {
RequestMatcher matcher = EndpointRequest.toAnyEndpoint() RequestMatcher matcher = EndpointRequest.toAnyEndpoint()
.excluding(FooEndpoint.class, BazServletEndpoint.class); .excluding(FooEndpoint.class, BazServletEndpoint.class);
List<ExposableEndpoint<?>> endpoints = new ArrayList<>(); List<ExposableEndpoint<?>> endpoints = new ArrayList<>();
endpoints.add(mockEndpoint("foo", "foo")); endpoints.add(mockEndpoint(EndpointId.of("foo"), "foo"));
endpoints.add(mockEndpoint("bar", "bar")); endpoints.add(mockEndpoint(EndpointId.of("bar"), "bar"));
endpoints.add(mockEndpoint("baz", "baz")); endpoints.add(mockEndpoint(EndpointId.of("baz"), "baz"));
PathMappedEndpoints pathMappedEndpoints = new PathMappedEndpoints("/actuator", PathMappedEndpoints pathMappedEndpoints = new PathMappedEndpoints("/actuator",
() -> endpoints); () -> endpoints);
assertMatcher(matcher, pathMappedEndpoints).doesNotMatch("/actuator/foo"); assertMatcher(matcher, pathMappedEndpoints).doesNotMatch("/actuator/foo");
@ -258,14 +259,14 @@ public class EndpointRequestTests {
private PathMappedEndpoints mockPathMappedEndpoints(String basePath) { private PathMappedEndpoints mockPathMappedEndpoints(String basePath) {
List<ExposableEndpoint<?>> endpoints = new ArrayList<>(); List<ExposableEndpoint<?>> endpoints = new ArrayList<>();
endpoints.add(mockEndpoint("foo", "foo")); endpoints.add(mockEndpoint(EndpointId.of("foo"), "foo"));
endpoints.add(mockEndpoint("bar", "bar")); endpoints.add(mockEndpoint(EndpointId.of("bar"), "bar"));
return new PathMappedEndpoints(basePath, () -> endpoints); return new PathMappedEndpoints(basePath, () -> endpoints);
} }
private TestEndpoint mockEndpoint(String id, String rootPath) { private TestEndpoint mockEndpoint(EndpointId id, String rootPath) {
TestEndpoint endpoint = mock(TestEndpoint.class); TestEndpoint endpoint = mock(TestEndpoint.class);
given(endpoint.getId()).willReturn(id); given(endpoint.getEndpointId()).willReturn(id);
given(endpoint.getRootPath()).willReturn(rootPath); given(endpoint.getRootPath()).willReturn(rootPath);
return endpoint; return endpoint;
} }

@ -33,7 +33,7 @@ import org.springframework.util.Assert;
public abstract class AbstractExposableEndpoint<O extends Operation> public abstract class AbstractExposableEndpoint<O extends Operation>
implements ExposableEndpoint<O> { implements ExposableEndpoint<O> {
private final String id; private final EndpointId id;
private boolean enabledByDefault; private boolean enabledByDefault;
@ -44,9 +44,24 @@ public abstract class AbstractExposableEndpoint<O extends Operation>
* @param id the endpoint id * @param id the endpoint id
* @param enabledByDefault if the endpoint is enabled by default * @param enabledByDefault if the endpoint is enabled by default
* @param operations the endpoint operations * @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, public AbstractExposableEndpoint(String id, boolean enabledByDefault,
Collection<? extends O> operations) { Collection<? extends O> 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<? extends O> operations) {
Assert.notNull(id, "ID must not be null"); Assert.notNull(id, "ID must not be null");
Assert.notNull(operations, "Operations must not be null"); Assert.notNull(operations, "Operations must not be null");
this.id = id; this.id = id;
@ -56,7 +71,7 @@ public abstract class AbstractExposableEndpoint<O extends Operation>
@Override @Override
public String getId() { public String getId() {
return this.id; return this.id.toString();
} }
@Override @Override

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

@ -31,9 +31,20 @@ public interface ExposableEndpoint<O extends Operation> {
/** /**
* Returns the id of the endpoint. * Returns the id of the endpoint.
* @return the id * @return the id
* @deprecated since 2.0.6 in favor of {@link #getEndpointId()}
*/ */
@Deprecated
String getId(); 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. * Returns if the endpoint is enabled by default.
* @return if the endpoint is enabled by default * @return if the endpoint is enabled by default

@ -19,6 +19,7 @@ package org.springframework.boot.actuate.endpoint.annotation;
import java.util.Collection; import java.util.Collection;
import org.springframework.boot.actuate.endpoint.AbstractExposableEndpoint; 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.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
@ -46,10 +47,28 @@ public abstract class AbstractDiscoveredEndpoint<O extends Operation>
* @param id the ID of the endpoint * @param id the ID of the endpoint
* @param enabledByDefault if the endpoint is enabled by default * @param enabledByDefault if the endpoint is enabled by default
* @param operations the endpoint operations * @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, public AbstractDiscoveredEndpoint(EndpointDiscoverer<?, ?> discoverer,
Object endpointBean, String id, boolean enabledByDefault, Object endpointBean, String id, boolean enabledByDefault,
Collection<? extends O> operations) { Collection<? extends O> 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<? extends O> operations) {
super(id, enabledByDefault, operations); super(id, enabledByDefault, operations);
Assert.notNull(discoverer, "Discoverer must not be null"); Assert.notNull(discoverer, "Discoverer must not be null");
Assert.notNull(endpointBean, "EndpointBean must not be null"); Assert.notNull(endpointBean, "EndpointBean must not be null");

@ -24,6 +24,7 @@ import java.util.EnumMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
@ -68,20 +69,20 @@ abstract class DiscoveredOperationsFactory<O extends Operation> {
this.invokerAdvisors = invokerAdvisors; this.invokerAdvisors = invokerAdvisors;
} }
public Collection<O> createOperations(String id, Object target) { public Collection<O> createOperations(EndpointId id, Object target) {
return MethodIntrospector.selectMethods(target.getClass(), return MethodIntrospector.selectMethods(target.getClass(),
(MetadataLookup<O>) (method) -> createOperation(id, target, method)) (MetadataLookup<O>) (method) -> createOperation(id, target, method))
.values(); .values();
} }
private O createOperation(String endpointId, Object target, Method method) { private O createOperation(EndpointId endpointId, Object target, Method method) {
return OPERATION_TYPES.entrySet().stream() return OPERATION_TYPES.entrySet().stream()
.map((entry) -> createOperation(endpointId, target, method, .map((entry) -> createOperation(endpointId, target, method,
entry.getKey(), entry.getValue())) entry.getKey(), entry.getValue()))
.filter(Objects::nonNull).findFirst().orElse(null); .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<? extends Annotation> annotationType) { OperationType operationType, Class<? extends Annotation> annotationType) {
AnnotationAttributes annotationAttributes = AnnotatedElementUtils AnnotationAttributes annotationAttributes = AnnotatedElementUtils
.getMergedAnnotationAttributes(method, annotationType); .getMergedAnnotationAttributes(method, annotationType);
@ -96,7 +97,7 @@ abstract class DiscoveredOperationsFactory<O extends Operation> {
return createOperation(endpointId, operationMethod, invoker); return createOperation(endpointId, operationMethod, invoker);
} }
private OperationInvoker applyAdvisors(String endpointId, private OperationInvoker applyAdvisors(EndpointId endpointId,
OperationMethod operationMethod, OperationInvoker invoker) { OperationMethod operationMethod, OperationInvoker invoker) {
if (this.invokerAdvisors != null) { if (this.invokerAdvisors != null) {
for (OperationInvokerAdvisor advisor : this.invokerAdvisors) { for (OperationInvokerAdvisor advisor : this.invokerAdvisors) {
@ -107,7 +108,7 @@ abstract class DiscoveredOperationsFactory<O extends Operation> {
return invoker; return invoker;
} }
protected abstract O createOperation(String endpointId, protected abstract O createOperation(EndpointId endpointId,
DiscoveredOperationMethod operationMethod, OperationInvoker invoker); DiscoveredOperationMethod operationMethod, OperationInvoker invoker);
} }

@ -22,6 +22,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; 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 * 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 * running application. Endpoints can be exposed over a variety of technologies including
@ -52,8 +54,9 @@ import java.lang.annotation.Target;
public @interface Endpoint { public @interface Endpoint {
/** /**
* The id of the endpoint. * The id of the endpoint (must follow {@link EndpointId} rules).
* @return the id * @return the id
* @see EndpointId
*/ */
String id() default ""; String id() default "";

@ -32,6 +32,7 @@ import java.util.stream.Collectors;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.boot.actuate.endpoint.EndpointFilter; 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.EndpointsSupplier;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.Operation;
@ -101,7 +102,7 @@ public abstract class EndpointDiscoverer<E extends ExposableEndpoint<O>, O exten
return new DiscoveredOperationsFactory<O>(parameterValueMapper, invokerAdvisors) { return new DiscoveredOperationsFactory<O>(parameterValueMapper, invokerAdvisors) {
@Override @Override
protected O createOperation(String endpointId, protected O createOperation(EndpointId endpointId,
DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { DiscoveredOperationMethod operationMethod, OperationInvoker invoker) {
return EndpointDiscoverer.this.createOperation(endpointId, return EndpointDiscoverer.this.createOperation(endpointId,
operationMethod, invoker); operationMethod, invoker);
@ -125,7 +126,7 @@ public abstract class EndpointDiscoverer<E extends ExposableEndpoint<O>, O exten
} }
private Collection<EndpointBean> createEndpointBeans() { private Collection<EndpointBean> createEndpointBeans() {
Map<String, EndpointBean> byId = new LinkedHashMap<>(); Map<EndpointId, EndpointBean> byId = new LinkedHashMap<>();
String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors( String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(
this.applicationContext, Endpoint.class); this.applicationContext, Endpoint.class);
for (String beanName : beanNames) { for (String beanName : beanNames) {
@ -145,7 +146,7 @@ public abstract class EndpointDiscoverer<E extends ExposableEndpoint<O>, O exten
} }
private void addExtensionBeans(Collection<EndpointBean> endpointBeans) { private void addExtensionBeans(Collection<EndpointBean> endpointBeans) {
Map<String, EndpointBean> byId = endpointBeans.stream() Map<EndpointId, EndpointBean> byId = endpointBeans.stream()
.collect(Collectors.toMap(EndpointBean::getId, (bean) -> bean)); .collect(Collectors.toMap(EndpointBean::getId, (bean) -> bean));
String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors( String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(
this.applicationContext, EndpointExtension.class); this.applicationContext, EndpointExtension.class);
@ -189,7 +190,7 @@ public abstract class EndpointDiscoverer<E extends ExposableEndpoint<O>, O exten
private E convertToEndpoint(EndpointBean endpointBean) { private E convertToEndpoint(EndpointBean endpointBean) {
MultiValueMap<OperationKey, O> indexed = new LinkedMultiValueMap<>(); MultiValueMap<OperationKey, O> indexed = new LinkedMultiValueMap<>();
String id = endpointBean.getId(); EndpointId id = endpointBean.getId();
addOperations(indexed, id, endpointBean.getBean(), false); addOperations(indexed, id, endpointBean.getBean(), false);
if (endpointBean.getExtensions().size() > 1) { if (endpointBean.getExtensions().size() > 1) {
String extensionBeans = endpointBean.getExtensions().stream() String extensionBeans = endpointBean.getExtensions().stream()
@ -209,7 +210,7 @@ public abstract class EndpointDiscoverer<E extends ExposableEndpoint<O>, O exten
endpointBean.isEnabledByDefault(), operations); endpointBean.isEnabledByDefault(), operations);
} }
private void addOperations(MultiValueMap<OperationKey, O> indexed, String id, private void addOperations(MultiValueMap<OperationKey, O> indexed, EndpointId id,
Object target, boolean replaceLast) { Object target, boolean replaceLast) {
Set<OperationKey> replacedLast = new HashSet<>(); Set<OperationKey> replacedLast = new HashSet<>();
Collection<O> operations = this.operationsFactory.createOperations(id, target); Collection<O> operations = this.operationsFactory.createOperations(id, target);
@ -339,7 +340,25 @@ public abstract class EndpointDiscoverer<E extends ExposableEndpoint<O>, O exten
* @param enabledByDefault if the endpoint is enabled by default * @param enabledByDefault if the endpoint is enabled by default
* @param operations the endpoint operations * @param operations the endpoint operations
* @return a created endpoint (a {@link DiscoveredEndpoint} is recommended) * @return a created endpoint (a {@link DiscoveredEndpoint} is recommended)
* @since 2.0.6
*/ */
protected E createEndpoint(Object endpointBean, EndpointId id,
boolean enabledByDefault, Collection<O> 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, protected abstract E createEndpoint(Object endpointBean, String id,
boolean enabledByDefault, Collection<O> operations); boolean enabledByDefault, Collection<O> operations);
@ -349,7 +368,24 @@ public abstract class EndpointDiscoverer<E extends ExposableEndpoint<O>, O exten
* @param operationMethod the operation method * @param operationMethod the operation method
* @param invoker the invoker to use * @param invoker the invoker to use
* @return a created operation * @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, protected abstract O createOperation(String endpointId,
DiscoveredOperationMethod operationMethod, OperationInvoker invoker); DiscoveredOperationMethod operationMethod, OperationInvoker invoker);
@ -414,7 +450,7 @@ public abstract class EndpointDiscoverer<E extends ExposableEndpoint<O>, O exten
private final Object bean; private final Object bean;
private final String id; private final EndpointId id;
private boolean enabledByDefault; private boolean enabledByDefault;
@ -426,14 +462,15 @@ public abstract class EndpointDiscoverer<E extends ExposableEndpoint<O>, O exten
AnnotationAttributes attributes = AnnotatedElementUtils AnnotationAttributes attributes = AnnotatedElementUtils
.findMergedAnnotationAttributes(bean.getClass(), Endpoint.class, true, .findMergedAnnotationAttributes(bean.getClass(), Endpoint.class, true,
true); true);
String id = attributes.getString("id");
Assert.state(StringUtils.hasText(id),
() -> "No @Endpoint id attribute specified for "
+ bean.getClass().getName());
this.beanName = beanName; this.beanName = beanName;
this.bean = bean; this.bean = bean;
this.id = attributes.getString("id"); this.id = EndpointId.of(id);
this.enabledByDefault = (Boolean) attributes.get("enableByDefault"); this.enabledByDefault = (Boolean) attributes.get("enableByDefault");
this.filter = getFilter(this.bean.getClass()); this.filter = getFilter(this.bean.getClass());
Assert.state(StringUtils.hasText(this.id),
() -> "No @Endpoint id attribute specified for "
+ bean.getClass().getName());
} }
public void addExtension(ExtensionBean extensionBean) { public void addExtension(ExtensionBean extensionBean) {
@ -461,7 +498,7 @@ public abstract class EndpointDiscoverer<E extends ExposableEndpoint<O>, O exten
return this.bean; return this.bean;
} }
public String getId() { public EndpointId getId() {
return this.id; return this.id;
} }
@ -484,7 +521,7 @@ public abstract class EndpointDiscoverer<E extends ExposableEndpoint<O>, O exten
private final Object bean; private final Object bean;
private final String endpointId; private final EndpointId endpointId;
private final Class<?> filter; private final Class<?> filter;
@ -500,7 +537,7 @@ public abstract class EndpointDiscoverer<E extends ExposableEndpoint<O>, O exten
true); true);
Assert.state(endpointAttributes != null, () -> "Extension " Assert.state(endpointAttributes != null, () -> "Extension "
+ endpointType.getName() + " does not specify an endpoint"); + endpointType.getName() + " does not specify an endpoint");
this.endpointId = endpointAttributes.getString("id"); this.endpointId = EndpointId.of(endpointAttributes.getString("id"));
this.filter = attributes.getClass("filter"); this.filter = attributes.getClass("filter");
} }
@ -512,7 +549,7 @@ public abstract class EndpointDiscoverer<E extends ExposableEndpoint<O>, O exten
return this.bean; return this.bean;
} }
public String getEndpointId() { public EndpointId getEndpointId() {
return this.endpointId; return this.endpointId;
} }

@ -16,6 +16,7 @@
package org.springframework.boot.actuate.endpoint.invoke; package org.springframework.boot.actuate.endpoint.invoke;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.OperationType;
/** /**
@ -34,7 +35,25 @@ public interface OperationInvokerAdvisor {
* @param parameters the operation parameters * @param parameters the operation parameters
* @param invoker the invoker to advise * @param invoker the invoker to advise
* @return an potentially new operation invoker with support for additional features * @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, OperationInvoker apply(String endpointId, OperationType operationType,
OperationParameters parameters, OperationInvoker invoker); OperationParameters parameters, OperationInvoker invoker);

@ -40,6 +40,7 @@ public class CachingOperationInvokerAdvisor implements OperationInvokerAdvisor {
} }
@Override @Override
@Deprecated
public OperationInvoker apply(String endpointId, OperationType operationType, public OperationInvoker apply(String endpointId, OperationType operationType,
OperationParameters parameters, OperationInvoker invoker) { OperationParameters parameters, OperationInvoker invoker) {
if (operationType == OperationType.READ && !hasMandatoryParameter(parameters)) { if (operationType == OperationType.READ && !hasMandatoryParameter(parameters)) {

@ -89,7 +89,7 @@ public class EndpointMBean implements DynamicMBean {
throws MBeanException, ReflectionException { throws MBeanException, ReflectionException {
JmxOperation operation = this.operations.get(actionName); JmxOperation operation = this.operations.get(actionName);
if (operation == null) { if (operation == null) {
String message = "Endpoint with id '" + this.endpoint.getId() String message = "Endpoint with id '" + this.endpoint.getEndpointId()
+ "' has no operation named " + actionName; + "' has no operation named " + actionName;
throw new ReflectionException(new IllegalArgumentException(message), message); throw new ReflectionException(new IllegalArgumentException(message), message);
} }

@ -136,7 +136,7 @@ public class JmxEndpointExporter
} }
private String getEndpointDescription(ExposableJmxEndpoint endpoint) { private String getEndpointDescription(ExposableJmxEndpoint endpoint) {
return "endpoint '" + endpoint.getId() + "'"; return "endpoint '" + endpoint.getEndpointId() + "'";
} }
} }

@ -58,7 +58,7 @@ class MBeanInfoFactory {
} }
private String getDescription(ExposableJmxEndpoint endpoint) { private String getDescription(ExposableJmxEndpoint endpoint) {
return "MBean operations for endpoint " + endpoint.getId(); return "MBean operations for endpoint " + endpoint.getEndpointId();
} }
private ModelMBeanOperationInfo[] getMBeanOperations(ExposableJmxEndpoint endpoint) { private ModelMBeanOperationInfo[] getMBeanOperations(ExposableJmxEndpoint endpoint) {

@ -18,6 +18,7 @@ package org.springframework.boot.actuate.endpoint.jmx.annotation;
import java.util.Collection; 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.AbstractDiscoveredEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint; import org.springframework.boot.actuate.endpoint.jmx.ExposableJmxEndpoint;
@ -32,7 +33,8 @@ class DiscoveredJmxEndpoint extends AbstractDiscoveredEndpoint<JmxOperation>
implements ExposableJmxEndpoint { implements ExposableJmxEndpoint {
DiscoveredJmxEndpoint(EndpointDiscoverer<?, ?> discoverer, Object endpointBean, DiscoveredJmxEndpoint(EndpointDiscoverer<?, ?> discoverer, Object endpointBean,
String id, boolean enabledByDefault, Collection<JmxOperation> operations) { EndpointId id, boolean enabledByDefault,
Collection<JmxOperation> operations) {
super(discoverer, endpointBean, id, enabledByDefault, operations); super(discoverer, endpointBean, id, enabledByDefault, operations);
} }

@ -26,6 +26,7 @@ import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; 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.AbstractDiscoveredOperation;
import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
@ -59,8 +60,8 @@ class DiscoveredJmxOperation extends AbstractDiscoveredOperation implements JmxO
private final List<JmxOperationParameter> parameters; private final List<JmxOperationParameter> parameters;
DiscoveredJmxOperation(String endpointId, DiscoveredOperationMethod operationMethod, DiscoveredJmxOperation(EndpointId endpointId,
OperationInvoker invoker) { DiscoveredOperationMethod operationMethod, OperationInvoker invoker) {
super(operationMethod, invoker); super(operationMethod, invoker);
Method method = operationMethod.getMethod(); Method method = operationMethod.getMethod();
this.name = method.getName(); this.name = method.getName();

@ -19,6 +19,7 @@ package org.springframework.boot.actuate.endpoint.jmx.annotation;
import java.util.Collection; import java.util.Collection;
import org.springframework.boot.actuate.endpoint.EndpointFilter; 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.DiscoveredOperationMethod;
import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
@ -54,15 +55,30 @@ public class JmxEndpointDiscoverer
} }
@Override @Override
@Deprecated
protected ExposableJmxEndpoint createEndpoint(Object endpointBean, String id, protected ExposableJmxEndpoint createEndpoint(Object endpointBean, String id,
boolean enabledByDefault, Collection<JmxOperation> operations) { boolean enabledByDefault, Collection<JmxOperation> operations) {
return createEndpoint(endpointBean, EndpointId.of(id), enabledByDefault,
operations);
}
@Override
protected ExposableJmxEndpoint createEndpoint(Object endpointBean, EndpointId id,
boolean enabledByDefault, Collection<JmxOperation> operations) {
return new DiscoveredJmxEndpoint(this, endpointBean, id, enabledByDefault, return new DiscoveredJmxEndpoint(this, endpointBean, id, enabledByDefault,
operations); operations);
} }
@Override @Override
@Deprecated
protected JmxOperation createOperation(String endpointId, protected JmxOperation createOperation(String endpointId,
DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { 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); return new DiscoveredJmxOperation(endpointId, operationMethod, invoker);
} }

@ -76,8 +76,9 @@ public class EndpointLinksResolver {
collectLinks(links, (ExposableWebEndpoint) endpoint, normalizedUrl); collectLinks(links, (ExposableWebEndpoint) endpoint, normalizedUrl);
} }
else if (endpoint instanceof PathMappedEndpoint) { else if (endpoint instanceof PathMappedEndpoint) {
links.put(endpoint.getId(), createLink(normalizedUrl, String rootPath = ((PathMappedEndpoint) endpoint).getRootPath();
((PathMappedEndpoint) endpoint).getRootPath())); Link link = createLink(normalizedUrl, rootPath);
links.put(endpoint.getEndpointId().toLowerCaseString(), link);
} }
} }
return links; return links;

@ -25,6 +25,7 @@ import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.EndpointsSupplier; import org.springframework.boot.actuate.endpoint.EndpointsSupplier;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -37,7 +38,7 @@ public class PathMappedEndpoints implements Iterable<PathMappedEndpoint> {
private final String basePath; private final String basePath;
private final Map<String, PathMappedEndpoint> endpoints; private final Map<EndpointId, PathMappedEndpoint> endpoints;
/** /**
* Create a new {@link PathMappedEndpoints} instance for the given supplier. * Create a new {@link PathMappedEndpoints} instance for the given supplier.
@ -62,13 +63,14 @@ public class PathMappedEndpoints implements Iterable<PathMappedEndpoint> {
this.endpoints = getEndpoints(suppliers); this.endpoints = getEndpoints(suppliers);
} }
private Map<String, PathMappedEndpoint> getEndpoints( private Map<EndpointId, PathMappedEndpoint> getEndpoints(
Collection<EndpointsSupplier<?>> suppliers) { Collection<EndpointsSupplier<?>> suppliers) {
Map<String, PathMappedEndpoint> endpoints = new LinkedHashMap<>(); Map<EndpointId, PathMappedEndpoint> endpoints = new LinkedHashMap<>();
suppliers.forEach((supplier) -> { suppliers.forEach((supplier) -> {
supplier.getEndpoints().forEach((endpoint) -> { supplier.getEndpoints().forEach((endpoint) -> {
if (endpoint instanceof PathMappedEndpoint) { 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<PathMappedEndpoint> {
* endpoint cannot be found. * endpoint cannot be found.
* @param endpointId the endpoint ID * @param endpointId the endpoint ID
* @return the root path or {@code null} * @return the root path or {@code null}
* @deprecated since 2.0.6 in favor of {@link #getRootPath(EndpointId)}
*/ */
@Deprecated
public String getRootPath(String endpointId) { 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); PathMappedEndpoint endpoint = getEndpoint(endpointId);
return (endpoint != null) ? endpoint.getRootPath() : null; return (endpoint != null) ? endpoint.getRootPath() : null;
} }
@ -99,8 +114,21 @@ public class PathMappedEndpoints implements Iterable<PathMappedEndpoint> {
* endpoint cannot be found. * endpoint cannot be found.
* @param endpointId the endpoint ID * @param endpointId the endpoint ID
* @return the full path or {@code null} * @return the full path or {@code null}
* @deprecated since 2.0.6 in favor of {@link #getPath(EndpointId)}
*/ */
@Deprecated
public String getPath(String endpointId) { 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)); return getPath(getEndpoint(endpointId));
} }
@ -125,8 +153,21 @@ public class PathMappedEndpoints implements Iterable<PathMappedEndpoint> {
* endpoint cannot be found. * endpoint cannot be found.
* @param endpointId the endpoint ID * @param endpointId the endpoint ID
* @return the path mapped endpoint or {@code null} * @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) { 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); return this.endpoints.get(endpointId);
} }

@ -16,6 +16,8 @@
package org.springframework.boot.actuate.endpoint.web; 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 * Strategy interface used to provide a mapping between an endpoint ID and the root path
* where it will be exposed. * where it will be exposed.
@ -31,15 +33,41 @@ public interface PathMapper {
* Resolve the root path for the endpoint with the specified {@code endpointId}. * Resolve the root path for the endpoint with the specified {@code endpointId}.
* @param endpointId the id of an endpoint * @param endpointId the id of an endpoint
* @return the path of the 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); String getRootPath(String endpointId);
/** /**
* Returns an {@link PathMapper} that uses the endpoint ID as the path. * 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() { 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();
}
};
} }
} }

@ -67,7 +67,7 @@ public class ServletEndpointRegistrar implements ServletContextInitializer {
private void register(ServletContext servletContext, private void register(ServletContext servletContext,
ExposableServletEndpoint endpoint) { ExposableServletEndpoint endpoint) {
String name = endpoint.getId() + "-actuator-endpoint"; String name = endpoint.getEndpointId().toLowerCaseString() + "-actuator-endpoint";
String path = this.basePath + "/" + endpoint.getRootPath(); String path = this.basePath + "/" + endpoint.getRootPath();
String urlMapping = path.endsWith("/") ? path + "*" : path + "/*"; String urlMapping = path.endsWith("/") ? path + "*" : path + "/*";
EndpointServlet endpointServlet = endpoint.getEndpointServlet(); EndpointServlet endpointServlet = endpoint.getEndpointServlet();

@ -20,6 +20,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import org.springframework.boot.actuate.endpoint.EndpointFilter; 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.Operation;
import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod;
import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer;
@ -67,16 +68,31 @@ public class ControllerEndpointDiscoverer
} }
@Override @Override
@Deprecated
protected ExposableControllerEndpoint createEndpoint(Object endpointBean, String id, protected ExposableControllerEndpoint createEndpoint(Object endpointBean, String id,
boolean enabledByDefault, Collection<Operation> operations) { boolean enabledByDefault, Collection<Operation> operations) {
return createEndpoint(endpointBean, (id != null) ? EndpointId.of(id) : null,
enabledByDefault, operations);
}
@Override
protected ExposableControllerEndpoint createEndpoint(Object endpointBean,
EndpointId id, boolean enabledByDefault, Collection<Operation> operations) {
String rootPath = this.endpointPathMapper.getRootPath(id); String rootPath = this.endpointPathMapper.getRootPath(id);
return new DiscoveredControllerEndpoint(this, endpointBean, id, rootPath, return new DiscoveredControllerEndpoint(this, endpointBean, id, rootPath,
enabledByDefault); enabledByDefault);
} }
@Override @Override
@Deprecated
protected Operation createOperation(String endpointId, protected Operation createOperation(String endpointId,
DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { DiscoveredOperationMethod operationMethod, OperationInvoker invoker) {
return createOperation(EndpointId.of(endpointId), operationMethod, invoker);
}
@Override
protected Operation createOperation(EndpointId endpointId,
DiscoveredOperationMethod operationMethod, OperationInvoker invoker) {
throw new IllegalStateException( throw new IllegalStateException(
"ControllerEndpoints must not declare operations"); "ControllerEndpoints must not declare operations");
} }

@ -18,6 +18,7 @@ package org.springframework.boot.actuate.endpoint.web.annotation;
import java.util.Collections; import java.util.Collections;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredEndpoint; import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer;
@ -33,7 +34,7 @@ class DiscoveredControllerEndpoint extends AbstractDiscoveredEndpoint<Operation>
private final String rootPath; private final String rootPath;
DiscoveredControllerEndpoint(EndpointDiscoverer<?, ?> discoverer, Object endpointBean, DiscoveredControllerEndpoint(EndpointDiscoverer<?, ?> discoverer, Object endpointBean,
String id, String rootPath, boolean enabledByDefault) { EndpointId id, String rootPath, boolean enabledByDefault) {
super(discoverer, endpointBean, id, enabledByDefault, Collections.emptyList()); super(discoverer, endpointBean, id, enabledByDefault, Collections.emptyList());
this.rootPath = rootPath; this.rootPath = rootPath;
} }

@ -19,6 +19,7 @@ package org.springframework.boot.actuate.endpoint.web.annotation;
import java.util.Collections; import java.util.Collections;
import java.util.function.Supplier; 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.Operation;
import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredEndpoint; import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer;
@ -39,7 +40,7 @@ class DiscoveredServletEndpoint extends AbstractDiscoveredEndpoint<Operation>
private final EndpointServlet endpointServlet; private final EndpointServlet endpointServlet;
DiscoveredServletEndpoint(EndpointDiscoverer<?, ?> discoverer, Object endpointBean, DiscoveredServletEndpoint(EndpointDiscoverer<?, ?> discoverer, Object endpointBean,
String id, String rootPath, boolean enabledByDefault) { EndpointId id, String rootPath, boolean enabledByDefault) {
super(discoverer, endpointBean, id, enabledByDefault, Collections.emptyList()); super(discoverer, endpointBean, id, enabledByDefault, Collections.emptyList());
String beanType = endpointBean.getClass().getName(); String beanType = endpointBean.getClass().getName();
Assert.state(endpointBean instanceof Supplier, Assert.state(endpointBean instanceof Supplier,

@ -18,6 +18,7 @@ package org.springframework.boot.actuate.endpoint.web.annotation;
import java.util.Collection; 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.AbstractDiscoveredEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint; import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
@ -34,7 +35,7 @@ class DiscoveredWebEndpoint extends AbstractDiscoveredEndpoint<WebOperation>
private final String rootPath; private final String rootPath;
DiscoveredWebEndpoint(EndpointDiscoverer<?, ?> discoverer, Object endpointBean, DiscoveredWebEndpoint(EndpointDiscoverer<?, ?> discoverer, Object endpointBean,
String id, String rootPath, boolean enabledByDefault, EndpointId id, String rootPath, boolean enabledByDefault,
Collection<WebOperation> operations) { Collection<WebOperation> operations) {
super(discoverer, endpointBean, id, enabledByDefault, operations); super(discoverer, endpointBean, id, enabledByDefault, operations);
this.rootPath = rootPath; this.rootPath = rootPath;

@ -23,6 +23,7 @@ import java.util.stream.Stream;
import org.reactivestreams.Publisher; 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.AbstractDiscoveredOperation;
import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod;
import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector;
@ -51,8 +52,9 @@ class DiscoveredWebOperation extends AbstractDiscoveredOperation implements WebO
private final WebOperationRequestPredicate requestPredicate; private final WebOperationRequestPredicate requestPredicate;
DiscoveredWebOperation(String endpointId, DiscoveredOperationMethod operationMethod, DiscoveredWebOperation(EndpointId endpointId,
OperationInvoker invoker, WebOperationRequestPredicate requestPredicate) { DiscoveredOperationMethod operationMethod, OperationInvoker invoker,
WebOperationRequestPredicate requestPredicate) {
super(operationMethod, invoker); super(operationMethod, invoker);
Method method = operationMethod.getMethod(); Method method = operationMethod.getMethod();
this.id = getId(endpointId, method); this.id = getId(endpointId, method);
@ -60,7 +62,7 @@ class DiscoveredWebOperation extends AbstractDiscoveredOperation implements WebO
this.requestPredicate = requestPredicate; 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) return endpointId + Stream.of(method.getParameters()).filter(this::hasSelector)
.map(this::dashName).collect(Collectors.joining()); .map(this::dashName).collect(Collectors.joining());
} }

@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; 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.OperationType;
import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod;
import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector;
@ -50,7 +51,7 @@ class RequestPredicateFactory {
this.endpointMediaTypes = endpointMediaTypes; this.endpointMediaTypes = endpointMediaTypes;
} }
public WebOperationRequestPredicate getRequestPredicate(String endpointId, public WebOperationRequestPredicate getRequestPredicate(EndpointId endpointId,
String rootPath, DiscoveredOperationMethod operationMethod) { String rootPath, DiscoveredOperationMethod operationMethod) {
Method method = operationMethod.getMethod(); Method method = operationMethod.getMethod();
String path = getPath(rootPath, method); String path = getPath(rootPath, method);

@ -20,6 +20,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import org.springframework.boot.actuate.endpoint.EndpointFilter; 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.Operation;
import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod;
import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer;
@ -66,16 +67,31 @@ public class ServletEndpointDiscoverer
} }
@Override @Override
@Deprecated
protected ExposableServletEndpoint createEndpoint(Object endpointBean, String id, protected ExposableServletEndpoint createEndpoint(Object endpointBean, String id,
boolean enabledByDefault, Collection<Operation> operations) { boolean enabledByDefault, Collection<Operation> operations) {
return createEndpoint(endpointBean, EndpointId.of(id), enabledByDefault,
operations);
}
@Override
protected ExposableServletEndpoint createEndpoint(Object endpointBean, EndpointId id,
boolean enabledByDefault, Collection<Operation> operations) {
String rootPath = this.endpointPathMapper.getRootPath(id); String rootPath = this.endpointPathMapper.getRootPath(id);
return new DiscoveredServletEndpoint(this, endpointBean, id, rootPath, return new DiscoveredServletEndpoint(this, endpointBean, id, rootPath,
enabledByDefault); enabledByDefault);
} }
@Override @Override
@Deprecated
protected Operation createOperation(String endpointId, protected Operation createOperation(String endpointId,
DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { 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"); throw new IllegalStateException("ServletEndpoints must not declare operations");
} }

@ -19,6 +19,7 @@ package org.springframework.boot.actuate.endpoint.web.annotation;
import java.util.Collection; import java.util.Collection;
import org.springframework.boot.actuate.endpoint.EndpointFilter; 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.DiscoveredOperationMethod;
import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer; import org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
@ -68,16 +69,31 @@ public class WebEndpointDiscoverer
} }
@Override @Override
@Deprecated
protected ExposableWebEndpoint createEndpoint(Object endpointBean, String id, protected ExposableWebEndpoint createEndpoint(Object endpointBean, String id,
boolean enabledByDefault, Collection<WebOperation> operations) { boolean enabledByDefault, Collection<WebOperation> operations) {
return createEndpoint(endpointBean, EndpointId.of(id), enabledByDefault,
operations);
}
@Override
protected ExposableWebEndpoint createEndpoint(Object endpointBean, EndpointId id,
boolean enabledByDefault, Collection<WebOperation> operations) {
String rootPath = this.endpointPathMapper.getRootPath(id); String rootPath = this.endpointPathMapper.getRootPath(id);
return new DiscoveredWebEndpoint(this, endpointBean, id, rootPath, return new DiscoveredWebEndpoint(this, endpointBean, id, rootPath,
enabledByDefault, operations); enabledByDefault, operations);
} }
@Override @Override
@Deprecated
protected WebOperation createOperation(String endpointId, protected WebOperation createOperation(String endpointId,
DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { 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); String rootPath = this.endpointPathMapper.getRootPath(endpointId);
WebOperationRequestPredicate requestPredicate = this.requestPredicateFactory WebOperationRequestPredicate requestPredicate = this.requestPredicateFactory
.getRequestPredicate(endpointId, rootPath, operationMethod); .getRequestPredicate(endpointId, rootPath, operationMethod);

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

@ -25,6 +25,7 @@ import java.util.Map;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.InvocationContext; import org.springframework.boot.actuate.endpoint.InvocationContext;
import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.SecurityContext;
@ -60,8 +61,8 @@ public class DiscoveredOperationsFactoryTests {
@Test @Test
public void createOperationsWhenHasReadMethodShouldCreateOperation() { public void createOperationsWhenHasReadMethodShouldCreateOperation() {
Collection<TestOperation> operations = this.factory.createOperations("test", Collection<TestOperation> operations = this.factory
new ExampleRead()); .createOperations(EndpointId.of("test"), new ExampleRead());
assertThat(operations).hasSize(1); assertThat(operations).hasSize(1);
TestOperation operation = getFirst(operations); TestOperation operation = getFirst(operations);
assertThat(operation.getType()).isEqualTo(OperationType.READ); assertThat(operation.getType()).isEqualTo(OperationType.READ);
@ -69,8 +70,8 @@ public class DiscoveredOperationsFactoryTests {
@Test @Test
public void createOperationsWhenHasWriteMethodShouldCreateOperation() { public void createOperationsWhenHasWriteMethodShouldCreateOperation() {
Collection<TestOperation> operations = this.factory.createOperations("test", Collection<TestOperation> operations = this.factory
new ExampleWrite()); .createOperations(EndpointId.of("test"), new ExampleWrite());
assertThat(operations).hasSize(1); assertThat(operations).hasSize(1);
TestOperation operation = getFirst(operations); TestOperation operation = getFirst(operations);
assertThat(operation.getType()).isEqualTo(OperationType.WRITE); assertThat(operation.getType()).isEqualTo(OperationType.WRITE);
@ -78,8 +79,8 @@ public class DiscoveredOperationsFactoryTests {
@Test @Test
public void createOperationsWhenHasDeleteMethodShouldCreateOperation() { public void createOperationsWhenHasDeleteMethodShouldCreateOperation() {
Collection<TestOperation> operations = this.factory.createOperations("test", Collection<TestOperation> operations = this.factory
new ExampleDelete()); .createOperations(EndpointId.of("test"), new ExampleDelete());
assertThat(operations).hasSize(1); assertThat(operations).hasSize(1);
TestOperation operation = getFirst(operations); TestOperation operation = getFirst(operations);
assertThat(operation.getType()).isEqualTo(OperationType.DELETE); assertThat(operation.getType()).isEqualTo(OperationType.DELETE);
@ -87,8 +88,8 @@ public class DiscoveredOperationsFactoryTests {
@Test @Test
public void createOperationsWhenMultipleShouldReturnMultiple() { public void createOperationsWhenMultipleShouldReturnMultiple() {
Collection<TestOperation> operations = this.factory.createOperations("test", Collection<TestOperation> operations = this.factory
new ExampleMultiple()); .createOperations(EndpointId.of("test"), new ExampleMultiple());
assertThat(operations).hasSize(2); assertThat(operations).hasSize(2);
assertThat(operations.stream().map(TestOperation::getType)) assertThat(operations.stream().map(TestOperation::getType))
.containsOnly(OperationType.READ, OperationType.WRITE); .containsOnly(OperationType.READ, OperationType.WRITE);
@ -96,8 +97,8 @@ public class DiscoveredOperationsFactoryTests {
@Test @Test
public void createOperationsShouldProvideOperationMethod() { public void createOperationsShouldProvideOperationMethod() {
TestOperation operation = getFirst( TestOperation operation = getFirst(this.factory
this.factory.createOperations("test", new ExampleWithParams())); .createOperations(EndpointId.of("test"), new ExampleWithParams()));
OperationMethod operationMethod = operation.getOperationMethod(); OperationMethod operationMethod = operation.getOperationMethod();
assertThat(operationMethod.getMethod().getName()).isEqualTo("read"); assertThat(operationMethod.getMethod().getName()).isEqualTo("read");
assertThat(operationMethod.getParameters().hasParameters()).isTrue(); assertThat(operationMethod.getParameters().hasParameters()).isTrue();
@ -105,8 +106,8 @@ public class DiscoveredOperationsFactoryTests {
@Test @Test
public void createOperationsShouldProviderInvoker() { public void createOperationsShouldProviderInvoker() {
TestOperation operation = getFirst( TestOperation operation = getFirst(this.factory
this.factory.createOperations("test", new ExampleWithParams())); .createOperations(EndpointId.of("test"), new ExampleWithParams()));
Map<String, Object> params = Collections.singletonMap("name", 123); Map<String, Object> params = Collections.singletonMap("name", 123);
Object result = operation Object result = operation
.invoke(new InvocationContext(mock(SecurityContext.class), params)); .invoke(new InvocationContext(mock(SecurityContext.class), params));
@ -118,10 +119,10 @@ public class DiscoveredOperationsFactoryTests {
TestOperationInvokerAdvisor advisor = new TestOperationInvokerAdvisor(); TestOperationInvokerAdvisor advisor = new TestOperationInvokerAdvisor();
this.invokerAdvisors.add(advisor); this.invokerAdvisors.add(advisor);
TestOperation operation = getFirst( TestOperation operation = getFirst(
this.factory.createOperations("test", new ExampleRead())); this.factory.createOperations(EndpointId.of("test"), new ExampleRead()));
operation.invoke(new InvocationContext(mock(SecurityContext.class), operation.invoke(new InvocationContext(mock(SecurityContext.class),
Collections.emptyMap())); Collections.emptyMap()));
assertThat(advisor.getEndpointId()).isEqualTo("test"); assertThat(advisor.getEndpointId()).isEqualTo(EndpointId.of("test"));
assertThat(advisor.getOperationType()).isEqualTo(OperationType.READ); assertThat(advisor.getOperationType()).isEqualTo(OperationType.READ);
assertThat(advisor.getParameters()).isEmpty(); assertThat(advisor.getParameters()).isEmpty();
} }
@ -189,7 +190,7 @@ public class DiscoveredOperationsFactoryTests {
} }
@Override @Override
protected TestOperation createOperation(String endpointId, protected TestOperation createOperation(EndpointId endpointId,
DiscoveredOperationMethod operationMethod, OperationInvoker invoker) { DiscoveredOperationMethod operationMethod, OperationInvoker invoker) {
return new TestOperation(endpointId, operationMethod, invoker); return new TestOperation(endpointId, operationMethod, invoker);
} }
@ -198,7 +199,7 @@ public class DiscoveredOperationsFactoryTests {
static class TestOperation extends AbstractDiscoveredOperation { static class TestOperation extends AbstractDiscoveredOperation {
TestOperation(String endpointId, DiscoveredOperationMethod operationMethod, TestOperation(EndpointId endpointId, DiscoveredOperationMethod operationMethod,
OperationInvoker invoker) { OperationInvoker invoker) {
super(operationMethod, invoker); super(operationMethod, invoker);
} }
@ -207,14 +208,14 @@ public class DiscoveredOperationsFactoryTests {
static class TestOperationInvokerAdvisor implements OperationInvokerAdvisor { static class TestOperationInvokerAdvisor implements OperationInvokerAdvisor {
private String endpointId; private EndpointId endpointId;
private OperationType operationType; private OperationType operationType;
private OperationParameters parameters; private OperationParameters parameters;
@Override @Override
public OperationInvoker apply(String endpointId, OperationType operationType, public OperationInvoker apply(EndpointId endpointId, OperationType operationType,
OperationParameters parameters, OperationInvoker invoker) { OperationParameters parameters, OperationInvoker invoker) {
this.endpointId = endpointId; this.endpointId = endpointId;
this.operationType = operationType; this.operationType = operationType;
@ -222,7 +223,14 @@ public class DiscoveredOperationsFactoryTests {
return invoker; 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; return this.endpointId;
} }

@ -37,6 +37,7 @@ import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.boot.actuate.endpoint.EndpointFilter; 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.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
@ -125,10 +126,11 @@ public class EndpointDiscovererTests {
public void getEndpointsWhenHasSubclassedEndpointShouldReturnEndpoint() { public void getEndpointsWhenHasSubclassedEndpointShouldReturnEndpoint() {
load(TestEndpointSubclassConfiguration.class, (context) -> { load(TestEndpointSubclassConfiguration.class, (context) -> {
TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context); TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context);
Map<String, TestExposableEndpoint> endpoints = mapEndpoints( Map<EndpointId, TestExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints()); discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"));
Map<Method, TestOperation> operations = mapOperations(endpoints.get("test")); Map<Method, TestOperation> operations = mapOperations(
endpoints.get(EndpointId.of("test")));
assertThat(operations).hasSize(5); assertThat(operations).hasSize(5);
assertThat(operations).containsKeys(testEndpointMethods()); assertThat(operations).containsKeys(testEndpointMethods());
assertThat(operations).containsKeys(ReflectionUtils.findMethod( assertThat(operations).containsKeys(ReflectionUtils.findMethod(
@ -151,10 +153,11 @@ public class EndpointDiscovererTests {
load(TestEndpointConfiguration.class, (context) -> { load(TestEndpointConfiguration.class, (context) -> {
TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context, TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context,
(endpointId) -> 0L); (endpointId) -> 0L);
Map<String, TestExposableEndpoint> endpoints = mapEndpoints( Map<EndpointId, TestExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints()); discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"));
Map<Method, TestOperation> operations = mapOperations(endpoints.get("test")); Map<Method, TestOperation> operations = mapOperations(
endpoints.get(EndpointId.of("test")));
operations.values().forEach((operation) -> assertThat(operation.getInvoker()) operations.values().forEach((operation) -> assertThat(operation.getInvoker())
.isNotInstanceOf(CachingOperationInvoker.class)); .isNotInstanceOf(CachingOperationInvoker.class));
}); });
@ -165,10 +168,11 @@ public class EndpointDiscovererTests {
load(TestEndpointConfiguration.class, (context) -> { load(TestEndpointConfiguration.class, (context) -> {
TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context, TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context,
(endpointId) -> (endpointId.equals("foo") ? 500L : 0L)); (endpointId) -> (endpointId.equals("foo") ? 500L : 0L));
Map<String, TestExposableEndpoint> endpoints = mapEndpoints( Map<EndpointId, TestExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints()); discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"));
Map<Method, TestOperation> operations = mapOperations(endpoints.get("test")); Map<Method, TestOperation> operations = mapOperations(
endpoints.get(EndpointId.of("test")));
operations.values().forEach((operation) -> assertThat(operation.getInvoker()) operations.values().forEach((operation) -> assertThat(operation.getInvoker())
.isNotInstanceOf(CachingOperationInvoker.class)); .isNotInstanceOf(CachingOperationInvoker.class));
}); });
@ -178,11 +182,13 @@ public class EndpointDiscovererTests {
public void getEndpointsWhenTtlSetByIdAndIdMatchesShouldCacheInvokeCalls() { public void getEndpointsWhenTtlSetByIdAndIdMatchesShouldCacheInvokeCalls() {
load(TestEndpointConfiguration.class, (context) -> { load(TestEndpointConfiguration.class, (context) -> {
TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context, TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context,
(endpointId) -> (endpointId.equals("test") ? 500L : 0L)); (endpointId) -> (endpointId.equals(EndpointId.of("test")) ? 500L
Map<String, TestExposableEndpoint> endpoints = mapEndpoints( : 0L));
Map<EndpointId, TestExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints()); discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"));
Map<Method, TestOperation> operations = mapOperations(endpoints.get("test")); Map<Method, TestOperation> operations = mapOperations(
endpoints.get(EndpointId.of("test")));
TestOperation getAll = operations.get(findTestEndpointMethod("getAll")); TestOperation getAll = operations.get(findTestEndpointMethod("getAll"));
TestOperation getOne = operations TestOperation getOne = operations
.get(findTestEndpointMethod("getOne", String.class)); .get(findTestEndpointMethod("getOne", String.class));
@ -201,9 +207,9 @@ public class EndpointDiscovererTests {
public void getEndpointsWhenHasSpecializedFiltersInNonSpecializedDiscovererShouldFilterEndpoints() { public void getEndpointsWhenHasSpecializedFiltersInNonSpecializedDiscovererShouldFilterEndpoints() {
load(SpecializedEndpointsConfiguration.class, (context) -> { load(SpecializedEndpointsConfiguration.class, (context) -> {
TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context); TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context);
Map<String, TestExposableEndpoint> endpoints = mapEndpoints( Map<EndpointId, TestExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints()); discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"));
}); });
} }
@ -212,9 +218,10 @@ public class EndpointDiscovererTests {
load(SpecializedEndpointsConfiguration.class, (context) -> { load(SpecializedEndpointsConfiguration.class, (context) -> {
SpecializedEndpointDiscoverer discoverer = new SpecializedEndpointDiscoverer( SpecializedEndpointDiscoverer discoverer = new SpecializedEndpointDiscoverer(
context); context);
Map<String, SpecializedExposableEndpoint> endpoints = mapEndpoints( Map<EndpointId, SpecializedExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints()); 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) -> { load(SpecializedEndpointsConfiguration.class, (context) -> {
SpecializedEndpointDiscoverer discoverer = new SpecializedEndpointDiscoverer( SpecializedEndpointDiscoverer discoverer = new SpecializedEndpointDiscoverer(
context); context);
Map<String, SpecializedExposableEndpoint> endpoints = mapEndpoints( Map<EndpointId, SpecializedExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints()); discoverer.getEndpoints());
Map<Method, SpecializedOperation> operations = mapOperations( Map<Method, SpecializedOperation> operations = mapOperations(
endpoints.get("specialized")); endpoints.get(EndpointId.of("specialized")));
assertThat(operations).containsKeys( assertThat(operations).containsKeys(
ReflectionUtils.findMethod(SpecializedExtension.class, "getSpecial")); ReflectionUtils.findMethod(SpecializedExtension.class, "getSpecial"));
@ -238,10 +245,10 @@ public class EndpointDiscovererTests {
load(SubSpecializedEndpointsConfiguration.class, (context) -> { load(SubSpecializedEndpointsConfiguration.class, (context) -> {
SpecializedEndpointDiscoverer discoverer = new SpecializedEndpointDiscoverer( SpecializedEndpointDiscoverer discoverer = new SpecializedEndpointDiscoverer(
context); context);
Map<String, SpecializedExposableEndpoint> endpoints = mapEndpoints( Map<EndpointId, SpecializedExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints()); discoverer.getEndpoints());
Map<Method, SpecializedOperation> operations = mapOperations( Map<Method, SpecializedOperation> operations = mapOperations(
endpoints.get("specialized")); endpoints.get(EndpointId.of("specialized")));
assertThat(operations).containsKeys( assertThat(operations).containsKeys(
ReflectionUtils.findMethod(SpecializedTestEndpoint.class, "getAll")); ReflectionUtils.findMethod(SpecializedTestEndpoint.class, "getAll"));
assertThat(operations).containsKeys(ReflectionUtils.findMethod( assertThat(operations).containsKeys(ReflectionUtils.findMethod(
@ -261,18 +268,19 @@ public class EndpointDiscovererTests {
}; };
SpecializedEndpointDiscoverer discoverer = new SpecializedEndpointDiscoverer( SpecializedEndpointDiscoverer discoverer = new SpecializedEndpointDiscoverer(
context, Collections.singleton(filter)); context, Collections.singleton(filter));
Map<String, SpecializedExposableEndpoint> endpoints = mapEndpoints( Map<EndpointId, SpecializedExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints()); discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"));
}); });
} }
private void hasTestEndpoint(AnnotationConfigApplicationContext context) { private void hasTestEndpoint(AnnotationConfigApplicationContext context) {
TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context); TestEndpointDiscoverer discoverer = new TestEndpointDiscoverer(context);
Map<String, TestExposableEndpoint> endpoints = mapEndpoints( Map<EndpointId, TestExposableEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints()); discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"));
Map<Method, TestOperation> operations = mapOperations(endpoints.get("test")); Map<Method, TestOperation> operations = mapOperations(
endpoints.get(EndpointId.of("test")));
assertThat(operations).hasSize(4); assertThat(operations).hasSize(4);
assertThat(operations).containsKeys(); assertThat(operations).containsKeys();
} }
@ -290,14 +298,15 @@ public class EndpointDiscovererTests {
return ReflectionUtils.findMethod(TestEndpoint.class, name, paramTypes); return ReflectionUtils.findMethod(TestEndpoint.class, name, paramTypes);
} }
private <E extends ExposableEndpoint<?>> Map<String, E> mapEndpoints( private <E extends ExposableEndpoint<?>> Map<EndpointId, E> mapEndpoints(
Collection<E> endpoints) { Collection<E> endpoints) {
Map<String, E> byId = new LinkedHashMap<>(); Map<EndpointId, E> byId = new LinkedHashMap<>();
endpoints.forEach((endpoint) -> { endpoints.forEach((endpoint) -> {
E existing = byId.put(endpoint.getId(), endpoint); E existing = byId.put(endpoint.getEndpointId(), endpoint);
if (existing != null) { if (existing != null) {
throw new AssertionError(String.format( throw new AssertionError(
"Found endpoints with duplicate id '%s'", endpoint.getId())); String.format("Found endpoints with duplicate id '%s'",
endpoint.getEndpointId()));
} }
}); });
return byId; return byId;
@ -513,6 +522,12 @@ public class EndpointDiscovererTests {
@Override @Override
protected TestExposableEndpoint createEndpoint(Object endpointBean, String id, protected TestExposableEndpoint createEndpoint(Object endpointBean, String id,
boolean enabledByDefault, Collection<TestOperation> operations) { boolean enabledByDefault, Collection<TestOperation> operations) {
throw new IllegalStateException();
}
@Override
protected TestExposableEndpoint createEndpoint(Object endpointBean, EndpointId id,
boolean enabledByDefault, Collection<TestOperation> operations) {
return new TestExposableEndpoint(this, endpointBean, id, enabledByDefault, return new TestExposableEndpoint(this, endpointBean, id, enabledByDefault,
operations); operations);
} }
@ -548,6 +563,13 @@ public class EndpointDiscovererTests {
protected SpecializedExposableEndpoint createEndpoint(Object endpointBean, protected SpecializedExposableEndpoint createEndpoint(Object endpointBean,
String id, boolean enabledByDefault, String id, boolean enabledByDefault,
Collection<SpecializedOperation> operations) { Collection<SpecializedOperation> operations) {
throw new IllegalStateException();
}
@Override
protected SpecializedExposableEndpoint createEndpoint(Object endpointBean,
EndpointId id, boolean enabledByDefault,
Collection<SpecializedOperation> operations) {
return new SpecializedExposableEndpoint(this, endpointBean, id, return new SpecializedExposableEndpoint(this, endpointBean, id,
enabledByDefault, operations); enabledByDefault, operations);
} }
@ -569,7 +591,7 @@ public class EndpointDiscovererTests {
static class TestExposableEndpoint extends AbstractDiscoveredEndpoint<TestOperation> { static class TestExposableEndpoint extends AbstractDiscoveredEndpoint<TestOperation> {
TestExposableEndpoint(EndpointDiscoverer<?, ?> discoverer, Object endpointBean, TestExposableEndpoint(EndpointDiscoverer<?, ?> discoverer, Object endpointBean,
String id, boolean enabledByDefault, EndpointId id, boolean enabledByDefault,
Collection<? extends TestOperation> operations) { Collection<? extends TestOperation> operations) {
super(discoverer, endpointBean, id, enabledByDefault, operations); super(discoverer, endpointBean, id, enabledByDefault, operations);
} }
@ -580,7 +602,7 @@ public class EndpointDiscovererTests {
extends AbstractDiscoveredEndpoint<SpecializedOperation> { extends AbstractDiscoveredEndpoint<SpecializedOperation> {
SpecializedExposableEndpoint(EndpointDiscoverer<?, ?> discoverer, SpecializedExposableEndpoint(EndpointDiscoverer<?, ?> discoverer,
Object endpointBean, String id, boolean enabledByDefault, Object endpointBean, EndpointId id, boolean enabledByDefault,
Collection<? extends SpecializedOperation> operations) { Collection<? extends SpecializedOperation> operations) {
super(discoverer, endpointBean, id, enabledByDefault, operations); super(discoverer, endpointBean, id, enabledByDefault, operations);
} }

@ -24,6 +24,7 @@ import org.junit.Test;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
@ -63,8 +64,8 @@ public class CachingOperationInvokerAdvisorTests {
@Test @Test
public void applyWhenOperationIsNotReadShouldNotAddAdvise() { public void applyWhenOperationIsNotReadShouldNotAddAdvise() {
OperationParameters parameters = getParameters("get"); OperationParameters parameters = getParameters("get");
OperationInvoker advised = this.advisor.apply("foo", OperationType.WRITE, OperationInvoker advised = this.advisor.apply(EndpointId.of("foo"),
parameters, this.invoker); OperationType.WRITE, parameters, this.invoker);
assertThat(advised).isSameAs(this.invoker); assertThat(advised).isSameAs(this.invoker);
} }
@ -72,8 +73,8 @@ public class CachingOperationInvokerAdvisorTests {
public void applyWhenHasAtLeaseOneMandatoryParameterShouldNotAddAdvise() { public void applyWhenHasAtLeaseOneMandatoryParameterShouldNotAddAdvise() {
OperationParameters parameters = getParameters("getWithParameters", String.class, OperationParameters parameters = getParameters("getWithParameters", String.class,
String.class); String.class);
OperationInvoker advised = this.advisor.apply("foo", OperationType.READ, OperationInvoker advised = this.advisor.apply(EndpointId.of("foo"),
parameters, this.invoker); OperationType.READ, parameters, this.invoker);
assertThat(advised).isSameAs(this.invoker); assertThat(advised).isSameAs(this.invoker);
} }
@ -81,8 +82,8 @@ public class CachingOperationInvokerAdvisorTests {
public void applyWhenTimeToLiveReturnsNullShouldNotAddAdvise() { public void applyWhenTimeToLiveReturnsNullShouldNotAddAdvise() {
OperationParameters parameters = getParameters("get"); OperationParameters parameters = getParameters("get");
given(this.timeToLive.apply(any())).willReturn(null); given(this.timeToLive.apply(any())).willReturn(null);
OperationInvoker advised = this.advisor.apply("foo", OperationType.READ, OperationInvoker advised = this.advisor.apply(EndpointId.of("foo"),
parameters, this.invoker); OperationType.READ, parameters, this.invoker);
assertThat(advised).isSameAs(this.invoker); assertThat(advised).isSameAs(this.invoker);
verify(this.timeToLive).apply("foo"); verify(this.timeToLive).apply("foo");
} }
@ -91,8 +92,8 @@ public class CachingOperationInvokerAdvisorTests {
public void applyWhenTimeToLiveIsZeroShouldNotAddAdvise() { public void applyWhenTimeToLiveIsZeroShouldNotAddAdvise() {
OperationParameters parameters = getParameters("get"); OperationParameters parameters = getParameters("get");
given(this.timeToLive.apply(any())).willReturn(0L); given(this.timeToLive.apply(any())).willReturn(0L);
OperationInvoker advised = this.advisor.apply("foo", OperationType.READ, OperationInvoker advised = this.advisor.apply(EndpointId.of("foo"),
parameters, this.invoker); OperationType.READ, parameters, this.invoker);
assertThat(advised).isSameAs(this.invoker); assertThat(advised).isSameAs(this.invoker);
verify(this.timeToLive).apply("foo"); verify(this.timeToLive).apply("foo");
} }
@ -121,8 +122,8 @@ public class CachingOperationInvokerAdvisorTests {
} }
private void assertAdviseIsApplied(OperationParameters parameters) { private void assertAdviseIsApplied(OperationParameters parameters) {
OperationInvoker advised = this.advisor.apply("foo", OperationType.READ, OperationInvoker advised = this.advisor.apply(EndpointId.of("foo"),
parameters, this.invoker); OperationType.READ, parameters, this.invoker);
assertThat(advised).isInstanceOf(CachingOperationInvoker.class); assertThat(advised).isInstanceOf(CachingOperationInvoker.class);
assertThat(ReflectionTestUtils.getField(advised, "invoker")) assertThat(ReflectionTestUtils.getField(advised, "invoker"))
.isEqualTo(this.invoker); .isEqualTo(this.invoker);

@ -190,9 +190,8 @@ public class JmxEndpointExporterTests {
@Override @Override
public ObjectName getObjectName(ExposableJmxEndpoint endpoint) public ObjectName getObjectName(ExposableJmxEndpoint endpoint)
throws MalformedObjectNameException { throws MalformedObjectNameException {
return (endpoint != null) return (endpoint != null) ? new ObjectName(
? new ObjectName("boot:type=Endpoint,name=" + endpoint.getId()) "boot:type=Endpoint,name=" + endpoint.getEndpointId()) : null;
: null;
} }
} }

@ -25,6 +25,7 @@ import java.util.Map;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
@ -126,8 +127,8 @@ public class DiscoveredJmxOperationTests {
annotationAttributes.put("produces", "application/xml"); annotationAttributes.put("produces", "application/xml");
DiscoveredOperationMethod operationMethod = new DiscoveredOperationMethod(method, DiscoveredOperationMethod operationMethod = new DiscoveredOperationMethod(method,
OperationType.READ, annotationAttributes); OperationType.READ, annotationAttributes);
DiscoveredJmxOperation operation = new DiscoveredJmxOperation("test", DiscoveredJmxOperation operation = new DiscoveredJmxOperation(
operationMethod, mock(OperationInvoker.class)); EndpointId.of("test"), operationMethod, mock(OperationInvoker.class));
return operation; return operation;
} }

@ -28,6 +28,7 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; 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.DeleteOperation;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
@ -70,10 +71,10 @@ public class JmxEndpointDiscovererTests {
@Test @Test
public void getEndpointsShouldDiscoverStandardEndpoints() { public void getEndpointsShouldDiscoverStandardEndpoints() {
load(TestEndpoint.class, (discoverer) -> { load(TestEndpoint.class, (discoverer) -> {
Map<String, ExposableJmxEndpoint> endpoints = discover(discoverer); Map<EndpointId, ExposableJmxEndpoint> endpoints = discover(discoverer);
assertThat(endpoints).containsOnlyKeys("test"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"));
Map<String, JmxOperation> operationByName = mapOperations( Map<String, JmxOperation> operationByName = mapOperations(
endpoints.get("test").getOperations()); endpoints.get(EndpointId.of("test")).getOperations());
assertThat(operationByName).containsOnlyKeys("getAll", "getSomething", assertThat(operationByName).containsOnlyKeys("getAll", "getSomething",
"update", "deleteSomething"); "update", "deleteSomething");
JmxOperation getAll = operationByName.get("getAll"); JmxOperation getAll = operationByName.get("getAll");
@ -106,8 +107,9 @@ public class JmxEndpointDiscovererTests {
@Test @Test
public void getEndpointsWhenHasFilteredEndpointShouldOnlyDiscoverJmxEndpoints() { public void getEndpointsWhenHasFilteredEndpointShouldOnlyDiscoverJmxEndpoints() {
load(MultipleEndpointsConfiguration.class, (discoverer) -> { load(MultipleEndpointsConfiguration.class, (discoverer) -> {
Map<String, ExposableJmxEndpoint> endpoints = discover(discoverer); Map<EndpointId, ExposableJmxEndpoint> endpoints = discover(discoverer);
assertThat(endpoints).containsOnlyKeys("test", "jmx"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"),
EndpointId.of("jmx"));
}); });
} }
@ -125,19 +127,19 @@ public class JmxEndpointDiscovererTests {
@Test @Test
public void getEndpointsWhenHasJmxExtensionShouldOverrideStandardEndpoint() { public void getEndpointsWhenHasJmxExtensionShouldOverrideStandardEndpoint() {
load(OverriddenOperationJmxEndpointConfiguration.class, (discoverer) -> { load(OverriddenOperationJmxEndpointConfiguration.class, (discoverer) -> {
Map<String, ExposableJmxEndpoint> endpoints = discover(discoverer); Map<EndpointId, ExposableJmxEndpoint> endpoints = discover(discoverer);
assertThat(endpoints).containsOnlyKeys("test"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"));
assertJmxTestEndpoint(endpoints.get("test")); assertJmxTestEndpoint(endpoints.get(EndpointId.of("test")));
}); });
} }
@Test @Test
public void getEndpointsWhenHasJmxExtensionWithNewOperationAddsExtraOperation() { public void getEndpointsWhenHasJmxExtensionWithNewOperationAddsExtraOperation() {
load(AdditionalOperationJmxEndpointConfiguration.class, (discoverer) -> { load(AdditionalOperationJmxEndpointConfiguration.class, (discoverer) -> {
Map<String, ExposableJmxEndpoint> endpoints = discover(discoverer); Map<EndpointId, ExposableJmxEndpoint> endpoints = discover(discoverer);
assertThat(endpoints).containsOnlyKeys("test"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"));
Map<String, JmxOperation> operationByName = mapOperations( Map<String, JmxOperation> operationByName = mapOperations(
endpoints.get("test").getOperations()); endpoints.get(EndpointId.of("test")).getOperations());
assertThat(operationByName).containsOnlyKeys("getAll", "getSomething", assertThat(operationByName).containsOnlyKeys("getAll", "getSomething",
"update", "deleteSomething", "getAnother"); "update", "deleteSomething", "getAnother");
JmxOperation getAnother = operationByName.get("getAnother"); JmxOperation getAnother = operationByName.get("getAnother");
@ -150,10 +152,10 @@ public class JmxEndpointDiscovererTests {
@Test @Test
public void getEndpointsWhenHasCacheWithTtlShouldCacheReadOperationWithTtlValue() { public void getEndpointsWhenHasCacheWithTtlShouldCacheReadOperationWithTtlValue() {
load(TestEndpoint.class, (id) -> 500L, (discoverer) -> { load(TestEndpoint.class, (id) -> 500L, (discoverer) -> {
Map<String, ExposableJmxEndpoint> endpoints = discover(discoverer); Map<EndpointId, ExposableJmxEndpoint> endpoints = discover(discoverer);
assertThat(endpoints).containsOnlyKeys("test"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"));
Map<String, JmxOperation> operationByName = mapOperations( Map<String, JmxOperation> operationByName = mapOperations(
endpoints.get("test").getOperations()); endpoints.get(EndpointId.of("test")).getOperations());
assertThat(operationByName).containsOnlyKeys("getAll", "getSomething", assertThat(operationByName).containsOnlyKeys("getAll", "getSomething",
"update", "deleteSomething"); "update", "deleteSomething");
JmxOperation getAll = operationByName.get("getAll"); JmxOperation getAll = operationByName.get("getAll");
@ -167,10 +169,11 @@ public class JmxEndpointDiscovererTests {
public void getEndpointsShouldCacheReadOperations() { public void getEndpointsShouldCacheReadOperations() {
load(AdditionalOperationJmxEndpointConfiguration.class, (id) -> 500L, load(AdditionalOperationJmxEndpointConfiguration.class, (id) -> 500L,
(discoverer) -> { (discoverer) -> {
Map<String, ExposableJmxEndpoint> endpoints = discover(discoverer); Map<EndpointId, ExposableJmxEndpoint> endpoints = discover(
assertThat(endpoints).containsOnlyKeys("test"); discoverer);
assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"));
Map<String, JmxOperation> operationByName = mapOperations( Map<String, JmxOperation> operationByName = mapOperations(
endpoints.get("test").getOperations()); endpoints.get(EndpointId.of("test")).getOperations());
assertThat(operationByName).containsOnlyKeys("getAll", "getSomething", assertThat(operationByName).containsOnlyKeys("getAll", "getSomething",
"update", "deleteSomething", "getAnother"); "update", "deleteSomething", "getAnother");
JmxOperation getAll = operationByName.get("getAll"); JmxOperation getAll = operationByName.get("getAll");
@ -285,10 +288,11 @@ public class JmxEndpointDiscovererTests {
assertThat(parameter.getType()).isEqualTo(type); assertThat(parameter.getType()).isEqualTo(type);
} }
private Map<String, ExposableJmxEndpoint> discover(JmxEndpointDiscoverer discoverer) { private Map<EndpointId, ExposableJmxEndpoint> discover(
Map<String, ExposableJmxEndpoint> byId = new HashMap<>(); JmxEndpointDiscoverer discoverer) {
Map<EndpointId, ExposableJmxEndpoint> byId = new HashMap<>();
discoverer.getEndpoints() discoverer.getEndpoints()
.forEach((endpoint) -> byId.put(endpoint.getId(), endpoint)); .forEach((endpoint) -> byId.put(endpoint.getEndpointId(), endpoint));
return byId; return byId;
} }

@ -24,6 +24,7 @@ import java.util.Map;
import org.assertj.core.api.Condition; import org.assertj.core.api.Condition;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.web.annotation.ExposableControllerEndpoint; 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", "alpha"));
operations.add(operationWithPath("/alpha/{name}", "alpha-name")); operations.add(operationWithPath("/alpha/{name}", "alpha-name"));
ExposableWebEndpoint endpoint = mock(ExposableWebEndpoint.class); ExposableWebEndpoint endpoint = mock(ExposableWebEndpoint.class);
given(endpoint.getId()).willReturn("alpha"); given(endpoint.getEndpointId()).willReturn(EndpointId.of("alpha"));
given(endpoint.isEnableByDefault()).willReturn(true); given(endpoint.isEnableByDefault()).willReturn(true);
given(endpoint.getOperations()).willReturn(operations); given(endpoint.getOperations()).willReturn(operations);
String requestUrl = "https://api.example.com/actuator"; String requestUrl = "https://api.example.com/actuator";
@ -80,7 +81,7 @@ public class EndpointLinksResolverTests {
@Test @Test
public void resolvedLinksContainsALinkForServletEndpoint() { public void resolvedLinksContainsALinkForServletEndpoint() {
ExposableServletEndpoint servletEndpoint = mock(ExposableServletEndpoint.class); ExposableServletEndpoint servletEndpoint = mock(ExposableServletEndpoint.class);
given(servletEndpoint.getId()).willReturn("alpha"); given(servletEndpoint.getEndpointId()).willReturn(EndpointId.of("alpha"));
given(servletEndpoint.isEnableByDefault()).willReturn(true); given(servletEndpoint.isEnableByDefault()).willReturn(true);
given(servletEndpoint.getRootPath()).willReturn("alpha"); given(servletEndpoint.getRootPath()).willReturn("alpha");
String requestUrl = "https://api.example.com/actuator"; String requestUrl = "https://api.example.com/actuator";
@ -97,7 +98,7 @@ public class EndpointLinksResolverTests {
public void resolvedLinksContainsALinkForControllerEndpoint() { public void resolvedLinksContainsALinkForControllerEndpoint() {
ExposableControllerEndpoint controllerEndpoint = mock( ExposableControllerEndpoint controllerEndpoint = mock(
ExposableControllerEndpoint.class); ExposableControllerEndpoint.class);
given(controllerEndpoint.getId()).willReturn("alpha"); given(controllerEndpoint.getEndpointId()).willReturn(EndpointId.of("alpha"));
given(controllerEndpoint.isEnableByDefault()).willReturn(true); given(controllerEndpoint.isEnableByDefault()).willReturn(true);
given(controllerEndpoint.getRootPath()).willReturn("alpha"); given(controllerEndpoint.getRootPath()).willReturn("alpha");
String requestUrl = "https://api.example.com/actuator"; String requestUrl = "https://api.example.com/actuator";

@ -24,6 +24,7 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; 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.EndpointsSupplier;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.Operation; import org.springframework.boot.actuate.endpoint.Operation;
@ -60,38 +61,41 @@ public class PathMappedEndpointsTests {
public void iteratorShouldReturnPathMappedEndpoints() { public void iteratorShouldReturnPathMappedEndpoints() {
PathMappedEndpoints mapped = createTestMapped(null); PathMappedEndpoints mapped = createTestMapped(null);
assertThat(mapped).hasSize(2); assertThat(mapped).hasSize(2);
assertThat(mapped).extracting("id").containsExactly("e2", "e3"); assertThat(mapped).extracting("endpointId").containsExactly(EndpointId.of("e2"),
EndpointId.of("e3"));
} }
@Test @Test
public void streamShouldReturnPathMappedEndpoints() { public void streamShouldReturnPathMappedEndpoints() {
PathMappedEndpoints mapped = createTestMapped(null); PathMappedEndpoints mapped = createTestMapped(null);
assertThat(mapped.stream()).hasSize(2); 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 @Test
public void getRootPathWhenContainsIdShouldReturnRootPath() { public void getRootPathWhenContainsIdShouldReturnRootPath() {
PathMappedEndpoints mapped = createTestMapped(null); PathMappedEndpoints mapped = createTestMapped(null);
assertThat(mapped.getRootPath("e2")).isEqualTo("p2"); assertThat(mapped.getRootPath(EndpointId.of("e2"))).isEqualTo("p2");
} }
@Test @Test
public void getRootPathWhenMissingIdShouldReturnNull() { public void getRootPathWhenMissingIdShouldReturnNull() {
PathMappedEndpoints mapped = createTestMapped(null); PathMappedEndpoints mapped = createTestMapped(null);
assertThat(mapped.getRootPath("xx")).isNull(); assertThat(mapped.getRootPath(EndpointId.of("xx"))).isNull();
} }
@Test @Test
public void getPathWhenContainsIdShouldReturnRootPath() { public void getPathWhenContainsIdShouldReturnRootPath() {
assertThat(createTestMapped(null).getPath("e2")).isEqualTo("/p2"); assertThat(createTestMapped(null).getPath(EndpointId.of("e2"))).isEqualTo("/p2");
assertThat(createTestMapped("/x").getPath("e2")).isEqualTo("/x/p2"); assertThat(createTestMapped("/x").getPath(EndpointId.of("e2")))
.isEqualTo("/x/p2");
} }
@Test @Test
public void getPathWhenMissingIdShouldReturnNull() { public void getPathWhenMissingIdShouldReturnNull() {
PathMappedEndpoints mapped = createTestMapped(null); PathMappedEndpoints mapped = createTestMapped(null);
assertThat(mapped.getPath("xx")).isNull(); assertThat(mapped.getPath(EndpointId.of("xx"))).isNull();
} }
@Test @Test
@ -110,34 +114,34 @@ public class PathMappedEndpointsTests {
@Test @Test
public void getEndpointWhenContainsIdShouldReturnPathMappedEndpoint() { public void getEndpointWhenContainsIdShouldReturnPathMappedEndpoint() {
PathMappedEndpoints mapped = createTestMapped(null); PathMappedEndpoints mapped = createTestMapped(null);
assertThat(mapped.getEndpoint("e2").getRootPath()).isEqualTo("p2"); assertThat(mapped.getEndpoint(EndpointId.of("e2")).getRootPath()).isEqualTo("p2");
} }
@Test @Test
public void getEndpointWhenMissingIdShouldReturnNull() { public void getEndpointWhenMissingIdShouldReturnNull() {
PathMappedEndpoints mapped = createTestMapped(null); PathMappedEndpoints mapped = createTestMapped(null);
assertThat(mapped.getEndpoint("xx")).isNull(); assertThat(mapped.getEndpoint(EndpointId.of("xx"))).isNull();
} }
private PathMappedEndpoints createTestMapped(String basePath) { private PathMappedEndpoints createTestMapped(String basePath) {
List<ExposableEndpoint<?>> endpoints = new ArrayList<>(); List<ExposableEndpoint<?>> endpoints = new ArrayList<>();
endpoints.add(mockEndpoint("e1")); endpoints.add(mockEndpoint(EndpointId.of("e1")));
endpoints.add(mockEndpoint("e2", "p2")); endpoints.add(mockEndpoint(EndpointId.of("e2"), "p2"));
endpoints.add(mockEndpoint("e3", "p3")); endpoints.add(mockEndpoint(EndpointId.of("e3"), "p3"));
endpoints.add(mockEndpoint("e4")); endpoints.add(mockEndpoint(EndpointId.of("e4")));
return new PathMappedEndpoints(basePath, () -> endpoints); return new PathMappedEndpoints(basePath, () -> endpoints);
} }
private TestPathMappedEndpoint mockEndpoint(String id, String rootPath) { private TestPathMappedEndpoint mockEndpoint(EndpointId id, String rootPath) {
TestPathMappedEndpoint endpoint = mock(TestPathMappedEndpoint.class); TestPathMappedEndpoint endpoint = mock(TestPathMappedEndpoint.class);
given(endpoint.getId()).willReturn(id); given(endpoint.getEndpointId()).willReturn(id);
given(endpoint.getRootPath()).willReturn(rootPath); given(endpoint.getRootPath()).willReturn(rootPath);
return endpoint; return endpoint;
} }
private TestEndpoint mockEndpoint(String id) { private TestEndpoint mockEndpoint(EndpointId id) {
TestEndpoint endpoint = mock(TestEndpoint.class); TestEndpoint endpoint = mock(TestEndpoint.class);
given(endpoint.getId()).willReturn(id); given(endpoint.getEndpointId()).willReturn(id);
return endpoint; return endpoint;
} }

@ -35,6 +35,8 @@ import org.mockito.Captor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.springframework.boot.actuate.endpoint.EndpointId;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
@ -124,7 +126,7 @@ public class ServletEndpointRegistrarTests {
private ExposableServletEndpoint mockEndpoint(EndpointServlet endpointServlet) { private ExposableServletEndpoint mockEndpoint(EndpointServlet endpointServlet) {
ExposableServletEndpoint endpoint = mock(ExposableServletEndpoint.class); ExposableServletEndpoint endpoint = mock(ExposableServletEndpoint.class);
given(endpoint.getId()).willReturn("test"); given(endpoint.getEndpointId()).willReturn(EndpointId.of("test"));
given(endpoint.getEndpointServlet()).willReturn(endpointServlet); given(endpoint.getEndpointServlet()).willReturn(endpointServlet);
given(endpoint.getRootPath()).willReturn("test"); given(endpoint.getRootPath()).willReturn("test");
return endpoint; return endpoint;

@ -26,6 +26,7 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; 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.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.DiscoveredEndpoint; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
@ -70,7 +71,8 @@ public class ControllerEndpointDiscovererTests {
.getEndpoints(); .getEndpoints();
assertThat(endpoints).hasSize(1); assertThat(endpoints).hasSize(1);
ExposableControllerEndpoint endpoint = endpoints.iterator().next(); ExposableControllerEndpoint endpoint = endpoints.iterator().next();
assertThat(endpoint.getId()).isEqualTo("testcontroller"); assertThat(endpoint.getEndpointId())
.isEqualTo(EndpointId.of("testcontroller"));
assertThat(endpoint.getController()) assertThat(endpoint.getController())
.isInstanceOf(TestControllerEndpoint.class); .isInstanceOf(TestControllerEndpoint.class);
assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class); assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class);
@ -87,7 +89,8 @@ public class ControllerEndpointDiscovererTests {
.getEndpoints(); .getEndpoints();
assertThat(endpoints).hasSize(1); assertThat(endpoints).hasSize(1);
ExposableControllerEndpoint endpoint = endpoints.iterator().next(); ExposableControllerEndpoint endpoint = endpoints.iterator().next();
assertThat(endpoint.getId()).isEqualTo("testcontroller"); assertThat(endpoint.getEndpointId())
.isEqualTo(EndpointId.of("testcontroller"));
assertThat(endpoint.getController()) assertThat(endpoint.getController())
.isInstanceOf(TestProxyControllerEndpoint.class); .isInstanceOf(TestProxyControllerEndpoint.class);
assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class); assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class);
@ -102,7 +105,8 @@ public class ControllerEndpointDiscovererTests {
.getEndpoints(); .getEndpoints();
assertThat(endpoints).hasSize(1); assertThat(endpoints).hasSize(1);
ExposableControllerEndpoint endpoint = endpoints.iterator().next(); ExposableControllerEndpoint endpoint = endpoints.iterator().next();
assertThat(endpoint.getId()).isEqualTo("testrestcontroller"); assertThat(endpoint.getEndpointId())
.isEqualTo(EndpointId.of("testrestcontroller"));
assertThat(endpoint.getController()) assertThat(endpoint.getController())
.isInstanceOf(TestRestControllerEndpoint.class); .isInstanceOf(TestRestControllerEndpoint.class);
})); }));
@ -118,7 +122,8 @@ public class ControllerEndpointDiscovererTests {
.getEndpoints(); .getEndpoints();
assertThat(endpoints).hasSize(1); assertThat(endpoints).hasSize(1);
ExposableControllerEndpoint endpoint = endpoints.iterator().next(); ExposableControllerEndpoint endpoint = endpoints.iterator().next();
assertThat(endpoint.getId()).isEqualTo("testrestcontroller"); assertThat(endpoint.getEndpointId())
.isEqualTo(EndpointId.of("testrestcontroller"));
assertThat(endpoint.getController()) assertThat(endpoint.getController())
.isInstanceOf(TestProxyRestControllerEndpoint.class); .isInstanceOf(TestProxyRestControllerEndpoint.class);
assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class); assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class);
@ -131,9 +136,11 @@ public class ControllerEndpointDiscovererTests {
.run(assertDiscoverer((discoverer) -> { .run(assertDiscoverer((discoverer) -> {
Collection<ExposableControllerEndpoint> endpoints = discoverer Collection<ExposableControllerEndpoint> endpoints = discoverer
.getEndpoints(); .getEndpoints();
List<String> ids = endpoints.stream().map(ExposableEndpoint::getId) List<EndpointId> ids = endpoints.stream()
.map(ExposableEndpoint::getEndpointId)
.collect(Collectors.toList()); .collect(Collectors.toList());
assertThat(ids).containsOnly("testcontroller", "testrestcontroller"); assertThat(ids).containsOnly(EndpointId.of("testcontroller"),
EndpointId.of("testrestcontroller"));
})); }));
} }

@ -33,6 +33,7 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; 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.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.DiscoveredEndpoint; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
@ -79,7 +80,8 @@ public class ServletEndpointDiscovererTests {
.getEndpoints(); .getEndpoints();
assertThat(endpoints).hasSize(1); assertThat(endpoints).hasSize(1);
ExposableServletEndpoint endpoint = endpoints.iterator().next(); ExposableServletEndpoint endpoint = endpoints.iterator().next();
assertThat(endpoint.getId()).isEqualTo("testservlet"); assertThat(endpoint.getEndpointId())
.isEqualTo(EndpointId.of("testservlet"));
assertThat(endpoint.getEndpointServlet()).isNotNull(); assertThat(endpoint.getEndpointServlet()).isNotNull();
assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class); assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class);
})); }));
@ -95,7 +97,8 @@ public class ServletEndpointDiscovererTests {
.getEndpoints(); .getEndpoints();
assertThat(endpoints).hasSize(1); assertThat(endpoints).hasSize(1);
ExposableServletEndpoint endpoint = endpoints.iterator().next(); ExposableServletEndpoint endpoint = endpoints.iterator().next();
assertThat(endpoint.getId()).isEqualTo("testservlet"); assertThat(endpoint.getEndpointId())
.isEqualTo(EndpointId.of("testservlet"));
assertThat(endpoint.getEndpointServlet()).isNotNull(); assertThat(endpoint.getEndpointServlet()).isNotNull();
assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class); assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class);
})); }));
@ -107,9 +110,10 @@ public class ServletEndpointDiscovererTests {
.run(assertDiscoverer((discoverer) -> { .run(assertDiscoverer((discoverer) -> {
Collection<ExposableServletEndpoint> endpoints = discoverer Collection<ExposableServletEndpoint> endpoints = discoverer
.getEndpoints(); .getEndpoints();
List<String> ids = endpoints.stream().map(ExposableEndpoint::getId) List<EndpointId> ids = endpoints.stream()
.map(ExposableEndpoint::getEndpointId)
.collect(Collectors.toList()); .collect(Collectors.toList());
assertThat(ids).containsOnly("testservlet"); assertThat(ids).containsOnly(EndpointId.of("testservlet"));
})); }));
} }

@ -33,6 +33,7 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; 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.DeleteOperation;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
@ -91,19 +92,19 @@ public class WebEndpointDiscovererTests {
@Test @Test
public void getEndpointsWhenHasFilteredEndpointShouldOnlyDiscoverWebEndpoints() { public void getEndpointsWhenHasFilteredEndpointShouldOnlyDiscoverWebEndpoints() {
load(MultipleEndpointsConfiguration.class, (discoverer) -> { load(MultipleEndpointsConfiguration.class, (discoverer) -> {
Map<String, ExposableWebEndpoint> endpoints = mapEndpoints( Map<EndpointId, ExposableWebEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints()); discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"));
}); });
} }
@Test @Test
public void getEndpointsWhenHasWebExtensionShouldOverrideStandardEndpoint() { public void getEndpointsWhenHasWebExtensionShouldOverrideStandardEndpoint() {
load(OverriddenOperationWebEndpointExtensionConfiguration.class, (discoverer) -> { load(OverriddenOperationWebEndpointExtensionConfiguration.class, (discoverer) -> {
Map<String, ExposableWebEndpoint> endpoints = mapEndpoints( Map<EndpointId, ExposableWebEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints()); discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"));
ExposableWebEndpoint endpoint = endpoints.get("test"); ExposableWebEndpoint endpoint = endpoints.get(EndpointId.of("test"));
assertThat(requestPredicates(endpoint)).has( assertThat(requestPredicates(endpoint)).has(
requestPredicates(path("test").httpMethod(WebEndpointHttpMethod.GET) requestPredicates(path("test").httpMethod(WebEndpointHttpMethod.GET)
.consumes().produces("application/json"))); .consumes().produces("application/json")));
@ -113,10 +114,10 @@ public class WebEndpointDiscovererTests {
@Test @Test
public void getEndpointsWhenExtensionAddsOperationShouldHaveBothOperations() { public void getEndpointsWhenExtensionAddsOperationShouldHaveBothOperations() {
load(AdditionalOperationWebEndpointConfiguration.class, (discoverer) -> { load(AdditionalOperationWebEndpointConfiguration.class, (discoverer) -> {
Map<String, ExposableWebEndpoint> endpoints = mapEndpoints( Map<EndpointId, ExposableWebEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints()); discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"));
ExposableWebEndpoint endpoint = endpoints.get("test"); ExposableWebEndpoint endpoint = endpoints.get(EndpointId.of("test"));
assertThat(requestPredicates(endpoint)).has(requestPredicates( assertThat(requestPredicates(endpoint)).has(requestPredicates(
path("test").httpMethod(WebEndpointHttpMethod.GET).consumes() path("test").httpMethod(WebEndpointHttpMethod.GET).consumes()
.produces("application/json"), .produces("application/json"),
@ -128,10 +129,10 @@ public class WebEndpointDiscovererTests {
@Test @Test
public void getEndpointsWhenPredicateForWriteOperationThatReturnsVoidShouldHaveNoProducedMediaTypes() { public void getEndpointsWhenPredicateForWriteOperationThatReturnsVoidShouldHaveNoProducedMediaTypes() {
load(VoidWriteOperationEndpointConfiguration.class, (discoverer) -> { load(VoidWriteOperationEndpointConfiguration.class, (discoverer) -> {
Map<String, ExposableWebEndpoint> endpoints = mapEndpoints( Map<EndpointId, ExposableWebEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints()); discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("voidwrite"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("voidwrite"));
ExposableWebEndpoint endpoint = endpoints.get("voidwrite"); ExposableWebEndpoint endpoint = endpoints.get(EndpointId.of("voidwrite"));
assertThat(requestPredicates(endpoint)).has(requestPredicates( assertThat(requestPredicates(endpoint)).has(requestPredicates(
path("voidwrite").httpMethod(WebEndpointHttpMethod.POST).produces() path("voidwrite").httpMethod(WebEndpointHttpMethod.POST).produces()
.consumes("application/json"))); .consumes("application/json")));
@ -191,10 +192,10 @@ public class WebEndpointDiscovererTests {
@Test @Test
public void getEndpointsWhenHasCacheWithTtlShouldCacheReadOperationWithTtlValue() { public void getEndpointsWhenHasCacheWithTtlShouldCacheReadOperationWithTtlValue() {
load((id) -> 500L, (id) -> id, TestEndpointConfiguration.class, (discoverer) -> { load((id) -> 500L, (id) -> id, TestEndpointConfiguration.class, (discoverer) -> {
Map<String, ExposableWebEndpoint> endpoints = mapEndpoints( Map<EndpointId, ExposableWebEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints()); discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"));
ExposableWebEndpoint endpoint = endpoints.get("test"); ExposableWebEndpoint endpoint = endpoints.get(EndpointId.of("test"));
assertThat(endpoint.getOperations()).hasSize(1); assertThat(endpoint.getOperations()).hasSize(1);
WebOperation operation = endpoint.getOperations().iterator().next(); WebOperation operation = endpoint.getOperations().iterator().next();
Object invoker = ReflectionTestUtils.getField(operation, "invoker"); Object invoker = ReflectionTestUtils.getField(operation, "invoker");
@ -207,10 +208,10 @@ public class WebEndpointDiscovererTests {
@Test @Test
public void getEndpointsWhenOperationReturnsResourceShouldProduceApplicationOctetStream() { public void getEndpointsWhenOperationReturnsResourceShouldProduceApplicationOctetStream() {
load(ResourceEndpointConfiguration.class, (discoverer) -> { load(ResourceEndpointConfiguration.class, (discoverer) -> {
Map<String, ExposableWebEndpoint> endpoints = mapEndpoints( Map<EndpointId, ExposableWebEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints()); discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("resource"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("resource"));
ExposableWebEndpoint endpoint = endpoints.get("resource"); ExposableWebEndpoint endpoint = endpoints.get(EndpointId.of("resource"));
assertThat(requestPredicates(endpoint)).has(requestPredicates( assertThat(requestPredicates(endpoint)).has(requestPredicates(
path("resource").httpMethod(WebEndpointHttpMethod.GET).consumes() path("resource").httpMethod(WebEndpointHttpMethod.GET).consumes()
.produces("application/octet-stream"))); .produces("application/octet-stream")));
@ -220,10 +221,11 @@ public class WebEndpointDiscovererTests {
@Test @Test
public void getEndpointsWhenHasCustomMediaTypeShouldProduceCustomMediaType() { public void getEndpointsWhenHasCustomMediaTypeShouldProduceCustomMediaType() {
load(CustomMediaTypesEndpointConfiguration.class, (discoverer) -> { load(CustomMediaTypesEndpointConfiguration.class, (discoverer) -> {
Map<String, ExposableWebEndpoint> endpoints = mapEndpoints( Map<EndpointId, ExposableWebEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints()); discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("custommediatypes"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("custommediatypes"));
ExposableWebEndpoint endpoint = endpoints.get("custommediatypes"); ExposableWebEndpoint endpoint = endpoints
.get(EndpointId.of("custommediatypes"));
assertThat(requestPredicates(endpoint)).has(requestPredicates( assertThat(requestPredicates(endpoint)).has(requestPredicates(
path("custommediatypes").httpMethod(WebEndpointHttpMethod.GET) path("custommediatypes").httpMethod(WebEndpointHttpMethod.GET)
.consumes().produces("text/plain"), .consumes().produces("text/plain"),
@ -238,10 +240,10 @@ public class WebEndpointDiscovererTests {
public void getEndpointsWhenHasCustomPathShouldReturnCustomPath() { public void getEndpointsWhenHasCustomPathShouldReturnCustomPath() {
load((id) -> null, (id) -> "custom/" + id, load((id) -> null, (id) -> "custom/" + id,
AdditionalOperationWebEndpointConfiguration.class, (discoverer) -> { AdditionalOperationWebEndpointConfiguration.class, (discoverer) -> {
Map<String, ExposableWebEndpoint> endpoints = mapEndpoints( Map<EndpointId, ExposableWebEndpoint> endpoints = mapEndpoints(
discoverer.getEndpoints()); discoverer.getEndpoints());
assertThat(endpoints).containsOnlyKeys("test"); assertThat(endpoints).containsOnlyKeys(EndpointId.of("test"));
ExposableWebEndpoint endpoint = endpoints.get("test"); ExposableWebEndpoint endpoint = endpoints.get(EndpointId.of("test"));
Condition<List<? extends WebOperationRequestPredicate>> expected = requestPredicates( Condition<List<? extends WebOperationRequestPredicate>> expected = requestPredicates(
path("custom/test").httpMethod(WebEndpointHttpMethod.GET) path("custom/test").httpMethod(WebEndpointHttpMethod.GET)
.consumes().produces("application/json"), .consumes().produces("application/json"),
@ -276,10 +278,11 @@ public class WebEndpointDiscovererTests {
} }
} }
private Map<String, ExposableWebEndpoint> mapEndpoints( private Map<EndpointId, ExposableWebEndpoint> mapEndpoints(
Collection<ExposableWebEndpoint> endpoints) { Collection<ExposableWebEndpoint> endpoints) {
Map<String, ExposableWebEndpoint> endpointById = new HashMap<>(); Map<EndpointId, ExposableWebEndpoint> endpointById = new HashMap<>();
endpoints.forEach((endpoint) -> endpointById.put(endpoint.getId(), endpoint)); endpoints.forEach(
(endpoint) -> endpointById.put(endpoint.getEndpointId(), endpoint));
return endpointById; return endpointById;
} }

@ -22,6 +22,7 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; 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.EndpointMapping;
import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint; import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint;
import org.springframework.boot.actuate.endpoint.web.annotation.ExposableControllerEndpoint; import org.springframework.boot.actuate.endpoint.web.annotation.ExposableControllerEndpoint;
@ -121,22 +122,22 @@ public class ControllerEndpointHandlerMappingTests {
} }
private ExposableControllerEndpoint firstEndpoint() { private ExposableControllerEndpoint firstEndpoint() {
return mockEndpoint("first", new FirstTestMvcEndpoint()); return mockEndpoint(EndpointId.of("first"), new FirstTestMvcEndpoint());
} }
private ExposableControllerEndpoint secondEndpoint() { private ExposableControllerEndpoint secondEndpoint() {
return mockEndpoint("second", new SecondTestMvcEndpoint()); return mockEndpoint(EndpointId.of("second"), new SecondTestMvcEndpoint());
} }
private ExposableControllerEndpoint pathlessEndpoint() { 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); ExposableControllerEndpoint endpoint = mock(ExposableControllerEndpoint.class);
given(endpoint.getId()).willReturn(id); given(endpoint.getEndpointId()).willReturn(id);
given(endpoint.getController()).willReturn(controller); given(endpoint.getController()).willReturn(controller);
given(endpoint.getRootPath()).willReturn(id); given(endpoint.getRootPath()).willReturn(id.toString());
return endpoint; return endpoint;
} }

@ -22,6 +22,7 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; 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.EndpointMapping;
import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint; import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint;
import org.springframework.boot.actuate.endpoint.web.annotation.ExposableControllerEndpoint; import org.springframework.boot.actuate.endpoint.web.annotation.ExposableControllerEndpoint;
@ -113,22 +114,22 @@ public class ControllerEndpointHandlerMappingTests {
} }
private ExposableControllerEndpoint firstEndpoint() { private ExposableControllerEndpoint firstEndpoint() {
return mockEndpoint("first", new FirstTestMvcEndpoint()); return mockEndpoint(EndpointId.of("first"), new FirstTestMvcEndpoint());
} }
private ExposableControllerEndpoint secondEndpoint() { private ExposableControllerEndpoint secondEndpoint() {
return mockEndpoint("second", new SecondTestMvcEndpoint()); return mockEndpoint(EndpointId.of("second"), new SecondTestMvcEndpoint());
} }
private ExposableControllerEndpoint pathlessEndpoint() { 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); ExposableControllerEndpoint endpoint = mock(ExposableControllerEndpoint.class);
given(endpoint.getId()).willReturn(id); given(endpoint.getEndpointId()).willReturn(id);
given(endpoint.getController()).willReturn(controller); given(endpoint.getController()).willReturn(controller);
given(endpoint.getRootPath()).willReturn(id); given(endpoint.getRootPath()).willReturn(id.toString());
return endpoint; return endpoint;
} }

Loading…
Cancel
Save