Refactor endpoint path concerns

Create a `PathMappedEndpoint` interface that allows any `ExposedEndpoint`
to provide root path details. The `EndpointPathResolver` interface has
been renamed to `PathMapper` and is now only used during endpoint
discovery.

`EndpointPathProvider` has been replaced with `PathMappedEndpoints`
which simply finds relevant path mapped endpoints.

Fixes gh-10985
This commit is contained in:
Phillip Webb 2018-01-19 16:01:50 -08:00
parent 1d39feffea
commit 340ef52f78
29 changed files with 502 additions and 332 deletions

View File

@ -22,8 +22,8 @@ import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.PathMapper;
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
import org.springframework.boot.actuate.health.HealthEndpoint;
@ -45,18 +45,18 @@ public class CloudFoundryWebEndpointDiscoverer extends WebEndpointDiscoverer {
* @param applicationContext the source application context
* @param parameterValueMapper the parameter value mapper
* @param endpointMediaTypes the endpoint media types
* @param endpointPathResolver the endpoint path resolver
* @param endpointPathMapper the endpoint path mapper
* @param invokerAdvisors invoker advisors to apply
* @param filters filters to apply
*/
public CloudFoundryWebEndpointDiscoverer(ApplicationContext applicationContext,
ParameterValueMapper parameterValueMapper,
EndpointMediaTypes endpointMediaTypes,
EndpointPathResolver endpointPathResolver,
PathMapper endpointPathMapper,
Collection<OperationInvokerAdvisor> invokerAdvisors,
Collection<EndpointFilter<ExposableWebEndpoint>> filters) {
super(applicationContext, parameterValueMapper, endpointMediaTypes,
endpointPathResolver, invokerAdvisors, filters);
endpointPathMapper, invokerAdvisors, filters);
}
@Override

View File

@ -26,7 +26,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.condition.Conditi
import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoConfiguration;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.PathMapper;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.ReactiveHealthEndpointWebExtension;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@ -88,7 +88,7 @@ public class ReactiveCloudFoundryActuatorAutoConfiguration {
WebClient.Builder webClientBuilder) {
CloudFoundryWebEndpointDiscoverer endpointDiscoverer = new CloudFoundryWebEndpointDiscoverer(
this.applicationContext, parameterMapper, endpointMediaTypes,
EndpointPathResolver.useEndpointId(), Collections.emptyList(),
PathMapper.useEndpointId(), Collections.emptyList(),
Collections.emptyList());
CloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor(
webClientBuilder, this.applicationContext.getEnvironment());

View File

@ -25,7 +25,7 @@ import org.springframework.boot.actuate.autoconfigure.health.HealthEndpointAutoC
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.PathMapper;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.health.HealthEndpointWebExtension;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@ -90,7 +90,7 @@ public class CloudFoundryActuatorAutoConfiguration {
RestTemplateBuilder restTemplateBuilder) {
CloudFoundryWebEndpointDiscoverer discoverer = new CloudFoundryWebEndpointDiscoverer(
this.applicationContext, parameterMapper, endpointMediaTypes,
EndpointPathResolver.useEndpointId(), Collections.emptyList(),
PathMapper.useEndpointId(), Collections.emptyList(),
Collections.emptyList());
CloudFoundrySecurityInterceptor securityInterceptor = getSecurityInterceptor(
restTemplateBuilder, this.applicationContext.getEnvironment());

View File

@ -1,61 +0,0 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.autoconfigure.endpoint.web;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.util.Assert;
/**
* Default {@link EndpointPathProvider} implementation.
*
* @author Phillip Webb
* @since 2.0.0
*/
public class DefaultEndpointPathProvider implements EndpointPathProvider {
private final String basePath;
private final Collection<ExposableWebEndpoint> endpoints;
public DefaultEndpointPathProvider(WebEndpointProperties webEndpointProperties,
Collection<? extends ExposableWebEndpoint> endpoints) {
this.basePath = webEndpointProperties.getBasePath();
this.endpoints = Collections.unmodifiableCollection(endpoints);
}
@Override
public List<String> getPaths() {
return this.endpoints.stream().map(this::getPath).collect(Collectors.toList());
}
@Override
public String getPath(String id) {
Assert.notNull(id, "ID must not be null");
return this.endpoints.stream().filter((info) -> id.equals(info.getId()))
.findFirst().map(this::getPath).orElse(null);
}
private String getPath(ExposableWebEndpoint endpoint) {
return this.basePath + "/" + endpoint.getId();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,25 +18,24 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.web;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.core.env.Environment;
import org.springframework.boot.actuate.endpoint.web.PathMapper;
/**
* Default {@link EndpointPathResolver} implementation that uses the {@link Environment}
* to determine if an endpoint has a custom path.
* A {@link PathMapper} implementation that uses a simple {@link Map} to
* determine the endpoint path.
*
* @author Stephane Nicoll
*/
class DefaultEndpointPathResolver implements EndpointPathResolver {
class MappingWebEndpointPathMapper implements PathMapper {
private final Map<String, String> pathMapping;
DefaultEndpointPathResolver(Map<String, String> pathMapping) {
MappingWebEndpointPathMapper(Map<String, String> pathMapping) {
this.pathMapping = pathMapping;
}
@Override
public String resolvePath(String endpointId) {
public String getRootPath(String endpointId) {
return this.pathMapping.getOrDefault(endpointId, endpointId);
}

View File

@ -26,13 +26,15 @@ import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointFilter;
import org.springframework.boot.actuate.endpoint.EndpointsSupplier;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
import org.springframework.boot.actuate.endpoint.web.PathMapper;
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@ -75,8 +77,8 @@ public class WebEndpointAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public EndpointPathResolver endpointPathResolver() {
return new DefaultEndpointPathResolver(this.properties.getPathMapping());
public PathMapper webEndpointPathMapper() {
return new MappingWebEndpointPathMapper(this.properties.getPathMapping());
}
@Bean
@ -89,23 +91,22 @@ public class WebEndpointAutoConfiguration {
@ConditionalOnMissingBean(WebEndpointsSupplier.class)
public WebEndpointDiscoverer webEndpointDiscoverer(
ParameterValueMapper parameterValueMapper,
EndpointMediaTypes endpointMediaTypes,
EndpointPathResolver endpointPathResolver,
EndpointMediaTypes endpointMediaTypes, PathMapper webEndpointPathMapper,
ObjectProvider<Collection<OperationInvokerAdvisor>> invokerAdvisors,
ObjectProvider<Collection<EndpointFilter<ExposableWebEndpoint>>> filters) {
return new WebEndpointDiscoverer(this.applicationContext, parameterValueMapper,
endpointMediaTypes, endpointPathResolver,
endpointMediaTypes, webEndpointPathMapper,
invokerAdvisors.getIfAvailable(Collections::emptyList),
filters.getIfAvailable(Collections::emptyList));
}
@Bean
@ConditionalOnMissingBean
public EndpointPathProvider endpointPathProvider(
WebEndpointsSupplier webEndpointsSupplier,
public PathMappedEndpoints pathMappedEndpoints(
Collection<EndpointsSupplier<?>> endpointSuppliers,
WebEndpointProperties webEndpointProperties) {
return new DefaultEndpointPathProvider(webEndpointProperties,
webEndpointsSupplier.getEndpoints());
return new PathMappedEndpoints(webEndpointProperties.getBasePath(),
endpointSuppliers);
}
@Bean

View File

@ -28,8 +28,8 @@ import java.util.stream.Stream;
import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
import org.springframework.boot.security.reactive.ApplicationContextServerWebExchangeMatcher;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.web.server.util.matcher.OrServerWebExchangeMatcher;
@ -91,8 +91,8 @@ public final class EndpointRequest {
* The {@link ServerWebExchangeMatcher} used to match against {@link Endpoint actuator
* endpoints}.
*/
public static final class EndpointServerWebExchangeMatcher
extends ApplicationContextServerWebExchangeMatcher<EndpointPathProvider> {
public final static class EndpointServerWebExchangeMatcher
extends ApplicationContextServerWebExchangeMatcher<PathMappedEndpoints> {
private final List<Object> includes;
@ -114,7 +114,7 @@ public final class EndpointRequest {
private EndpointServerWebExchangeMatcher(List<Object> includes,
List<Object> excludes) {
super(EndpointPathProvider.class);
super(PathMappedEndpoints.class);
this.includes = includes;
this.excludes = excludes;
}
@ -132,31 +132,33 @@ public final class EndpointRequest {
}
@Override
protected void initialized(EndpointPathProvider endpointPathProvider) {
Set<String> paths = new LinkedHashSet<>(this.includes.isEmpty()
? endpointPathProvider.getPaths() : Collections.emptyList());
streamPaths(this.includes, endpointPathProvider).forEach(paths::add);
streamPaths(this.excludes, endpointPathProvider).forEach(paths::remove);
protected void initialized(PathMappedEndpoints pathMappedEndpoints) {
Set<String> paths = new LinkedHashSet<>();
if (this.includes.isEmpty()) {
paths.addAll(pathMappedEndpoints.getAllPaths());
}
streamPaths(this.includes, pathMappedEndpoints).forEach(paths::add);
streamPaths(this.excludes, pathMappedEndpoints).forEach(paths::remove);
this.delegate = new OrServerWebExchangeMatcher(getDelegateMatchers(paths));
}
private Stream<String> streamPaths(List<Object> source,
EndpointPathProvider endpointPathProvider) {
return source.stream().filter(Objects::nonNull).map(this::getPathId)
.map(endpointPathProvider::getPath);
PathMappedEndpoints pathMappedEndpoints) {
return source.stream().filter(Objects::nonNull).map(this::getEndpointId)
.map(pathMappedEndpoints::getPath);
}
private String getPathId(Object source) {
private String getEndpointId(Object source) {
if (source instanceof String) {
return (String) source;
}
if (source instanceof Class) {
return getPathId((Class<?>) source);
return getEndpointId((Class<?>) source);
}
throw new IllegalStateException("Unsupported source " + source);
}
private String getPathId(Class<?> source) {
private String getEndpointId(Class<?> source) {
Endpoint annotation = AnnotationUtils.findAnnotation(source, Endpoint.class);
Assert.state(annotation != null,
() -> "Class " + source + " is not annotated with @Endpoint");
@ -171,7 +173,7 @@ public final class EndpointRequest {
@Override
protected Mono<MatchResult> matches(ServerWebExchange exchange,
EndpointPathProvider context) {
PathMappedEndpoints context) {
return this.delegate.matches(exchange);
}

View File

@ -28,8 +28,8 @@ import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@ -89,8 +89,8 @@ public final class EndpointRequest {
/**
* The request matcher used to match against {@link Endpoint actuator endpoints}.
*/
public static final class EndpointRequestMatcher
extends ApplicationContextRequestMatcher<EndpointPathProvider> {
public final static class EndpointRequestMatcher
extends ApplicationContextRequestMatcher<PathMappedEndpoints> {
private final List<Object> includes;
@ -111,7 +111,7 @@ public final class EndpointRequest {
}
private EndpointRequestMatcher(List<Object> includes, List<Object> excludes) {
super(EndpointPathProvider.class);
super(PathMappedEndpoints.class);
this.includes = includes;
this.excludes = excludes;
}
@ -129,31 +129,33 @@ public final class EndpointRequest {
}
@Override
protected void initialized(EndpointPathProvider endpointPathProvider) {
Set<String> paths = new LinkedHashSet<>(this.includes.isEmpty()
? endpointPathProvider.getPaths() : Collections.emptyList());
streamPaths(this.includes, endpointPathProvider).forEach(paths::add);
streamPaths(this.excludes, endpointPathProvider).forEach(paths::remove);
protected void initialized(PathMappedEndpoints pathMappedEndpoints) {
Set<String> paths = new LinkedHashSet<>();
if (this.includes.isEmpty()) {
paths.addAll(pathMappedEndpoints.getAllPaths());
}
streamPaths(this.includes, pathMappedEndpoints).forEach(paths::add);
streamPaths(this.excludes, pathMappedEndpoints).forEach(paths::remove);
this.delegate = new OrRequestMatcher(getDelegateMatchers(paths));
}
private Stream<String> streamPaths(List<Object> source,
EndpointPathProvider endpointPathProvider) {
return source.stream().filter(Objects::nonNull).map(this::getPathId)
.map(endpointPathProvider::getPath);
PathMappedEndpoints pathMappedEndpoints) {
return source.stream().filter(Objects::nonNull).map(this::getEndpointId)
.map(pathMappedEndpoints::getPath);
}
private String getPathId(Object source) {
private String getEndpointId(Object source) {
if (source instanceof String) {
return (String) source;
}
if (source instanceof Class) {
return getPathId((Class<?>) source);
return getEndpointId((Class<?>) source);
}
throw new IllegalStateException("Unsupported source " + source);
}
private String getPathId(Class<?> source) {
private String getEndpointId(Class<?> source) {
Endpoint annotation = AnnotationUtils.findAnnotation(source, Endpoint.class);
Assert.state(annotation != null,
() -> "Class " + source + " is not annotated with @Endpoint");
@ -167,7 +169,7 @@ public final class EndpointRequest {
@Override
protected boolean matches(HttpServletRequest request,
EndpointPathProvider context) {
PathMappedEndpoints context) {
return this.delegate.matches(request);
}

View File

@ -28,8 +28,8 @@ import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.PathMapper;
import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
import org.springframework.boot.actuate.health.HealthEndpoint;
@ -69,7 +69,7 @@ public class CloudFoundryWebEndpointDiscovererTests {
}
private void load(Function<String, Long> timeToLive,
EndpointPathResolver endpointPathResolver, Class<?> configuration,
PathMapper endpointPathMapper, Class<?> configuration,
Consumer<CloudFoundryWebEndpointDiscoverer> consumer) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
configuration);
@ -80,7 +80,7 @@ public class CloudFoundryWebEndpointDiscovererTests {
Collections.singletonList("application/json"),
Collections.singletonList("application/json"));
CloudFoundryWebEndpointDiscoverer discoverer = new CloudFoundryWebEndpointDiscoverer(
context, parameterMapper, mediaTypes, endpointPathResolver,
context, parameterMapper, mediaTypes, endpointPathMapper,
Collections.singleton(new CachingOperationInvokerAdvisor(timeToLive)),
Collections.emptyList());
consumer.accept(discoverer);

View File

@ -35,7 +35,7 @@ import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.PathMapper;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
import org.springframework.boot.endpoint.web.EndpointMapping;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
@ -235,7 +235,7 @@ public class CloudFoundryWebFluxEndpointIntegrationTests {
ParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper(
DefaultConversionService.getSharedInstance());
return new WebEndpointDiscoverer(applicationContext, parameterMapper,
endpointMediaTypes, EndpointPathResolver.useEndpointId(),
endpointMediaTypes, PathMapper.useEndpointId(),
Collections.emptyList(), Collections.emptyList());
}

View File

@ -34,7 +34,7 @@ import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.PathMapper;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
import org.springframework.boot.endpoint.web.EndpointMapping;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
@ -221,7 +221,7 @@ public class CloudFoundryMvcWebEndpointIntegrationTests {
ParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper(
DefaultConversionService.getSharedInstance());
return new WebEndpointDiscoverer(applicationContext, parameterMapper,
endpointMediaTypes, EndpointPathResolver.useEndpointId(),
endpointMediaTypes, PathMapper.useEndpointId(),
Collections.emptyList(), Collections.emptyList());
}

View File

@ -1,89 +0,0 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.autoconfigure.endpoint.web;
import java.util.ArrayList;
import java.util.Collection;
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link DefaultEndpointPathProvider}.
*
* @author Phillip Webb
*/
public class DefaultEndpointPathProviderTests {
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
public void getPathsShouldReturnAllPaths() {
DefaultEndpointPathProvider provider = createProvider("");
assertThat(provider.getPaths()).containsOnly("/foo", "/bar");
}
@Test
public void getPathsWhenHasContextPathShouldReturnAllPathsWithContext() {
DefaultEndpointPathProvider provider = createProvider("/actuator");
assertThat(provider.getPaths()).containsOnly("/actuator/foo", "/actuator/bar");
}
@Test
public void getPathWhenEndpointIdIsKnownShouldReturnPath() {
DefaultEndpointPathProvider provider = createProvider("");
assertThat(provider.getPath("foo")).isEqualTo("/foo");
}
@Test
public void getPathWhenEndpointIdIsUnknownShouldReturnNull() {
DefaultEndpointPathProvider provider = createProvider("");
assertThat(provider.getPath("baz")).isNull();
}
@Test
public void getPathWhenHasContextPathReturnPath() {
DefaultEndpointPathProvider provider = createProvider("/actuator");
assertThat(provider.getPath("foo")).isEqualTo("/actuator/foo");
}
private DefaultEndpointPathProvider createProvider(String contextPath) {
Collection<ExposableWebEndpoint> endpoints = new ArrayList<>();
endpoints.add(mockEndpoint("foo"));
endpoints.add(mockEndpoint("bar"));
WebEndpointProperties properties = new WebEndpointProperties();
properties.setBasePath(contextPath);
return new DefaultEndpointPathProvider(properties, endpoints);
}
private ExposableWebEndpoint mockEndpoint(String id) {
ExposableWebEndpoint endpoint = mock(ExposableWebEndpoint.class);
given(endpoint.getId()).willReturn(id);
return endpoint;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,29 +20,27 @@ import java.util.Collections;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DefaultEndpointPathResolver}.
* Tests for {@link MappingWebEndpointPathMapper}.
*
* @author Stephane Nicoll
*/
public class DefaultEndpointPathResolverTests {
public class MappingWebEndpointPathMapperTests {
@Test
public void defaultConfiguration() {
EndpointPathResolver resolver = new DefaultEndpointPathResolver(
MappingWebEndpointPathMapper mapper = new MappingWebEndpointPathMapper(
Collections.emptyMap());
assertThat(resolver.resolvePath("test")).isEqualTo("test");
assertThat(mapper.getRootPath("test")).isEqualTo("test");
}
@Test
public void userConfiguration() {
EndpointPathResolver resolver = new DefaultEndpointPathResolver(
MappingWebEndpointPathMapper mapper = new MappingWebEndpointPathMapper(
Collections.singletonMap("test", "custom"));
assertThat(resolver.resolvePath("test")).isEqualTo("custom");
assertThat(mapper.getRootPath("test")).isEqualTo("custom");
}
}

View File

@ -16,14 +16,17 @@
package org.springframework.boot.actuate.autoconfigure.security.reactive;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import org.assertj.core.api.AssertDelegateTarget;
import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoint;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
@ -35,12 +38,14 @@ import org.springframework.web.server.WebHandler;
import org.springframework.web.server.adapter.HttpWebHandlerAdapter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link EndpointRequest}.
*
* @author Madhura Bhave
* @author Phillip Webb
*/
public class EndpointRequestTests {
@ -98,13 +103,27 @@ public class EndpointRequestTests {
}
private RequestMatcherAssert assertMatcher(ServerWebExchangeMatcher matcher) {
return assertMatcher(matcher, new MockEndpointPathProvider());
return assertMatcher(matcher, mockPathMappedEndpoints());
}
private PathMappedEndpoints mockPathMappedEndpoints() {
List<ExposableEndpoint<?>> endpoints = new ArrayList<>();
endpoints.add(mockEndpoint("foo", "foo"));
endpoints.add(mockEndpoint("bar", "bar"));
return new PathMappedEndpoints("/actuator", () -> endpoints);
}
private TestEndpoint mockEndpoint(String id, String rootPath) {
TestEndpoint endpoint = mock(TestEndpoint.class);
given(endpoint.getId()).willReturn(id);
given(endpoint.getRootPath()).willReturn(rootPath);
return endpoint;
}
private RequestMatcherAssert assertMatcher(ServerWebExchangeMatcher matcher,
EndpointPathProvider endpointPathProvider) {
PathMappedEndpoints pathMappedEndpoints) {
StaticApplicationContext context = new StaticApplicationContext();
context.registerBean(EndpointPathProvider.class, () -> endpointPathProvider);
context.registerBean(PathMappedEndpoints.class, () -> pathMappedEndpoints);
return assertThat(new RequestMatcherAssert(context, matcher));
}
@ -171,28 +190,13 @@ public class EndpointRequestTests {
}
private static class MockEndpointPathProvider implements EndpointPathProvider {
@Override
public List<String> getPaths() {
return Arrays.asList("/actuator/foo", "/actuator/bar");
}
@Override
public String getPath(String id) {
if ("foo".equals(id)) {
return "/actuator/foo";
}
if ("bar".equals(id)) {
return "/actuator/bar";
}
return null;
}
}
@Endpoint(id = "foo")
private static class FooEndpoint {
}
interface TestEndpoint extends ExposableEndpoint<Operation>, PathMappedEndpoint {
}
}

View File

@ -16,7 +16,7 @@
package org.springframework.boot.actuate.autoconfigure.security.servlet;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
@ -24,8 +24,11 @@ import javax.servlet.http.HttpServletRequest;
import org.assertj.core.api.AssertDelegateTarget;
import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.EndpointPathProvider;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.Operation;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoint;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.web.util.matcher.RequestMatcher;
@ -33,6 +36,8 @@ import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.StaticWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link EndpointRequest}.
@ -94,13 +99,27 @@ public class EndpointRequestTests {
}
private RequestMatcherAssert assertMatcher(RequestMatcher matcher) {
return assertMatcher(matcher, new MockEndpointPathProvider());
return assertMatcher(matcher, mockPathMappedEndpoints());
}
private PathMappedEndpoints mockPathMappedEndpoints() {
List<ExposableEndpoint<?>> endpoints = new ArrayList<>();
endpoints.add(mockEndpoint("foo", "foo"));
endpoints.add(mockEndpoint("bar", "bar"));
return new PathMappedEndpoints("/actuator", () -> endpoints);
}
private TestEndpoint mockEndpoint(String id, String rootPath) {
TestEndpoint endpoint = mock(TestEndpoint.class);
given(endpoint.getId()).willReturn(id);
given(endpoint.getRootPath()).willReturn(rootPath);
return endpoint;
}
private RequestMatcherAssert assertMatcher(RequestMatcher matcher,
EndpointPathProvider endpointPathProvider) {
PathMappedEndpoints pathMappedEndpoints) {
StaticWebApplicationContext context = new StaticWebApplicationContext();
context.registerBean(EndpointPathProvider.class, () -> endpointPathProvider);
context.registerBean(PathMappedEndpoints.class, () -> pathMappedEndpoints);
return assertThat(new RequestMatcherAssert(context, matcher));
}
@ -160,28 +179,13 @@ public class EndpointRequestTests {
}
private static class MockEndpointPathProvider implements EndpointPathProvider {
@Override
public List<String> getPaths() {
return Arrays.asList("/actuator/foo", "/actuator/bar");
}
@Override
public String getPath(String id) {
if ("foo".equals(id)) {
return "/actuator/foo";
}
if ("bar".equals(id)) {
return "/actuator/bar";
}
return null;
}
}
@Endpoint(id = "foo")
private static class FooEndpoint {
}
interface TestEndpoint extends ExposableEndpoint<Operation>, PathMappedEndpoint {
}
}

View File

@ -27,6 +27,7 @@ import java.util.Collection;
* @author Phillip Webb
* @since 2.0.0
*/
@FunctionalInterface
public interface EndpointsSupplier<E extends ExposableEndpoint<?>> {
/**

View File

@ -24,6 +24,7 @@ import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
* @author Phillip Webb
* @since 2.0.0
*/
public interface ExposableWebEndpoint extends ExposableEndpoint<WebOperation> {
public interface ExposableWebEndpoint
extends ExposableEndpoint<WebOperation>, PathMappedEndpoint {
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,29 +14,26 @@
* limitations under the License.
*/
package org.springframework.boot.actuate.autoconfigure.endpoint.web;
package org.springframework.boot.actuate.endpoint.web;
import java.util.List;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
/**
* Interface that provides path information for web mapped endpoints.
* Interface that can be implemented by an {@link ExposableEndpoint} that is mapped to a
* root web path.
*
* @author Phillip Webb
* @since 2.0.0
* @see PathMapper
*/
public interface EndpointPathProvider {
public interface PathMappedEndpoint {
/**
* Return all mapped endpoint paths.
* @return all paths
* Return the root path of the endpoint, relative to the context that exposes it. For
* example, a root path of {@code example} would be exposed under the URL
* "/{actuator-context}/example".
* @return the root path for the endpoint
*/
List<String> getPaths();
/**
* Return the path for the endpoint with the specified ID.
* @param id the endpoint ID
* @return the path of the endpoint or {@code null}
*/
String getPath(String id);
String getRootPath();
}

View File

@ -0,0 +1,147 @@
/*
* 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.web;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.boot.actuate.endpoint.EndpointsSupplier;
import org.springframework.util.Assert;
/**
* A collection of {@link PathMappedEndpoint path mapped endpoints}.
*
* @author Phillip Webb
*/
public class PathMappedEndpoints implements Iterable<PathMappedEndpoint> {
private final String basePath;
private final Map<String, PathMappedEndpoint> endpoints;
/**
* Create a new {@link PathMappedEndpoints} instance for the given supplier.
* @param basePath the base path of the endpoints
* @param supplier the endpoint supplier
*/
public PathMappedEndpoints(String basePath, EndpointsSupplier<?> supplier) {
Assert.notNull(supplier, "Supplier must not be null");
this.basePath = (basePath == null ? "" : basePath);
this.endpoints = getEndpoints(Collections.singleton(supplier));
}
/**
* Create a new {@link PathMappedEndpoints} instance for the given suppliers.
* @param basePath the base path of the endpoints
* @param suppliers the endpoint suppliers
*/
public PathMappedEndpoints(String basePath,
Collection<EndpointsSupplier<?>> suppliers) {
Assert.notNull(suppliers, "Suppliers must not be null");
this.basePath = (basePath == null ? "" : basePath);
this.endpoints = getEndpoints(suppliers);
}
private Map<String, PathMappedEndpoint> getEndpoints(
Collection<EndpointsSupplier<?>> suppliers) {
Map<String, PathMappedEndpoint> endpoints = new LinkedHashMap<>();
suppliers.forEach((supplier) -> {
supplier.getEndpoints().forEach((endpoint) -> {
if (endpoint instanceof PathMappedEndpoint) {
endpoints.put(endpoint.getId(), (PathMappedEndpoint) endpoint);
}
});
});
return Collections.unmodifiableMap(endpoints);
}
/**
* 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}
*/
public String getRootPath(String endpointId) {
PathMappedEndpoint endpoint = getEndpoint(endpointId);
return (endpoint == null ? null : endpoint.getRootPath());
}
/**
* 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}
*/
public String getPath(String endpointId) {
return getPath(getEndpoint(endpointId));
}
/**
* Return the root paths for each mapped endpoint.
* @return all root paths
*/
public Collection<String> getAllRootPaths() {
return asList(stream().map(PathMappedEndpoint::getRootPath));
}
/**
* Return the full paths for each mapped endpoint.
* @return all root paths
*/
public Collection<String> getAllPaths() {
return asList(stream().map(this::getPath));
}
/**
* 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}
*/
public PathMappedEndpoint getEndpoint(String endpointId) {
return this.endpoints.get(endpointId);
}
/**
* Stream all {@link PathMappedEndpoint path mapped endpoints}.
* @return a stream of endpoints
*/
public Stream<PathMappedEndpoint> stream() {
return this.endpoints.values().stream();
}
@Override
public Iterator<PathMappedEndpoint> iterator() {
return this.endpoints.values().iterator();
}
private String getPath(PathMappedEndpoint endpoint) {
return (endpoint == null ? null : this.basePath + "/" + endpoint.getRootPath());
}
private <T> List<T> asList(Stream<T> stream) {
return stream.collect(Collectors.collectingAndThen(Collectors.toList(),
Collections::unmodifiableList));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,26 +17,28 @@
package org.springframework.boot.actuate.endpoint.web;
/**
* Resolve the path of an endpoint.
* Strategy interface used to provide a mapping between an endpoint ID and the root path
* where it will be exposed.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @since 2.0.0
*/
@FunctionalInterface
public interface EndpointPathResolver {
public interface PathMapper {
/**
* Resolve the 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
* @return the path of the endpoint
*/
String resolvePath(String endpointId);
String getRootPath(String endpointId);
/**
* Returns an {@link EndpointPathResolver} that uses the endpoint ID as the path.
* @return an {@link EndpointPathResolver} 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
*/
static EndpointPathResolver useEndpointId() {
static PathMapper useEndpointId() {
return (endpointId) -> endpointId;
}

View File

@ -31,9 +31,17 @@ import org.springframework.boot.actuate.endpoint.web.WebOperation;
class DiscoveredWebEndpoint extends AbstractDiscoveredEndpoint<WebOperation>
implements ExposableWebEndpoint {
DiscoveredWebEndpoint(EndpointDiscoverer<?, ?> discoverer, String id,
private final String rootPath;
DiscoveredWebEndpoint(EndpointDiscoverer<?, ?> discoverer, String id, String rootPath,
boolean enabledByDefault, Collection<WebOperation> operations) {
super(discoverer, id, enabledByDefault, operations);
this.rootPath = rootPath;
}
@Override
public String getRootPath() {
return this.rootPath;
}
}

View File

@ -27,12 +27,12 @@ import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod;
import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
/**
* Factory to create a {@link WebOperationRequestPredicate}.
@ -45,18 +45,15 @@ class RequestPredicateFactory {
private final EndpointMediaTypes endpointMediaTypes;
private final EndpointPathResolver endpointPathResolver;
RequestPredicateFactory(EndpointMediaTypes endpointMediaTypes,
EndpointPathResolver endpointPathResolver) {
RequestPredicateFactory(EndpointMediaTypes endpointMediaTypes) {
Assert.notNull(endpointMediaTypes, "EndpointMediaTypes must not be null");
this.endpointMediaTypes = endpointMediaTypes;
this.endpointPathResolver = endpointPathResolver;
}
public WebOperationRequestPredicate getRequestPredicate(String endpointId,
DiscoveredOperationMethod operationMethod) {
String rootPath, DiscoveredOperationMethod operationMethod) {
Method method = operationMethod.getMethod();
String path = getPath(endpointId, method);
String path = getPath(endpointId, rootPath, method);
WebEndpointHttpMethod httpMethod = determineHttpMethod(
operationMethod.getOperationType());
Collection<String> consumes = getConsumes(httpMethod, method);
@ -64,10 +61,9 @@ class RequestPredicateFactory {
return new WebOperationRequestPredicate(path, httpMethod, consumes, produces);
}
private String getPath(String endpointId, Method method) {
return this.endpointPathResolver.resolvePath(endpointId)
+ Stream.of(method.getParameters()).filter(this::hasSelector)
.map(this::slashName).collect(Collectors.joining());
private String getPath(String endpointId, String rootPath, Method method) {
return rootPath + Stream.of(method.getParameters()).filter(this::hasSelector)
.map(this::slashName).collect(Collectors.joining());
}
private boolean hasSelector(Parameter parameter) {

View File

@ -25,12 +25,13 @@ import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.PathMapper;
import org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
/**
* {@link EndpointDiscoverer} for {@link ExposableWebEndpoint web endpoints}.
@ -42,6 +43,8 @@ public class WebEndpointDiscoverer
extends EndpointDiscoverer<ExposableWebEndpoint, WebOperation>
implements WebEndpointsSupplier {
private final PathMapper endpointPathMapper;
private final RequestPredicateFactory requestPredicateFactory;
/**
@ -49,32 +52,35 @@ public class WebEndpointDiscoverer
* @param applicationContext the source application context
* @param parameterValueMapper the parameter value mapper
* @param endpointMediaTypes the endpoint media types
* @param endpointPathResolver the endpoint path resolver
* @param endpointPathMapper the endpoint path mapper
* @param invokerAdvisors invoker advisors to apply
* @param filters filters to apply
*/
public WebEndpointDiscoverer(ApplicationContext applicationContext,
ParameterValueMapper parameterValueMapper,
EndpointMediaTypes endpointMediaTypes,
EndpointPathResolver endpointPathResolver,
EndpointMediaTypes endpointMediaTypes, PathMapper endpointPathMapper,
Collection<OperationInvokerAdvisor> invokerAdvisors,
Collection<EndpointFilter<ExposableWebEndpoint>> filters) {
super(applicationContext, parameterValueMapper, invokerAdvisors, filters);
this.requestPredicateFactory = new RequestPredicateFactory(endpointMediaTypes,
endpointPathResolver);
Assert.notNull(endpointPathMapper, "EndpointPathMapper must not be null");
this.endpointPathMapper = endpointPathMapper;
this.requestPredicateFactory = new RequestPredicateFactory(endpointMediaTypes);
}
@Override
protected ExposableWebEndpoint createEndpoint(String id, boolean enabledByDefault,
Collection<WebOperation> operations) {
return new DiscoveredWebEndpoint(this, id, enabledByDefault, operations);
String rootPath = this.endpointPathMapper.getRootPath(id);
return new DiscoveredWebEndpoint(this, id, rootPath, enabledByDefault,
operations);
}
@Override
protected WebOperation createOperation(String endpointId,
DiscoveredOperationMethod operationMethod, OperationInvoker invoker) {
String rootPath = this.endpointPathMapper.getRootPath(endpointId);
WebOperationRequestPredicate requestPredicate = this.requestPredicateFactory
.getRequestPredicate(endpointId, operationMethod);
.getRequestPredicate(endpointId, rootPath, operationMethod);
return new DiscoveredWebOperation(endpointId, operationMethod, invoker,
requestPredicate);
}

View File

@ -0,0 +1,153 @@
/*
* 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.web;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.actuate.endpoint.EndpointsSupplier;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.Operation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link PathMappedEndpoints}.
*
* @author Phillip Webb
*/
public class PathMappedEndpointsTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void createWhenSupplierIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Supplier must not be null");
new PathMappedEndpoints(null, (WebEndpointsSupplier) null);
}
@Test
public void createWhenSuppliersIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Suppliers must not be null");
new PathMappedEndpoints(null, (Collection<EndpointsSupplier<?>>) null);
}
@Test
public void iteratorShouldReturnPathMappedEndpoints() {
PathMappedEndpoints mapped = createTestMapped(null);
assertThat(mapped).hasSize(2);
assertThat(mapped).extracting("id").containsExactly("e2", "e3");
}
@Test
public void streamShouldReturnPathMappedEndpoints() {
PathMappedEndpoints mapped = createTestMapped(null);
assertThat(mapped.stream()).hasSize(2);
assertThat(mapped.stream()).extracting("id").containsExactly("e2", "e3");
}
@Test
public void getRootPathWhenContainsIdShouldReturnRootPath() {
PathMappedEndpoints mapped = createTestMapped(null);
assertThat(mapped.getRootPath("e2")).isEqualTo("p2");
}
@Test
public void getRootPathWhenMissingIdShouldReturnNull() {
PathMappedEndpoints mapped = createTestMapped(null);
assertThat(mapped.getRootPath("xx")).isNull();
}
@Test
public void getPathWhenContainsIdShouldReturnRootPath() {
assertThat(createTestMapped(null).getPath("e2")).isEqualTo("/p2");
assertThat(createTestMapped("/x").getPath("e2")).isEqualTo("/x/p2");
}
@Test
public void getPathWhenMissingIdShouldReturnNull() {
PathMappedEndpoints mapped = createTestMapped(null);
assertThat(mapped.getRootPath("xx")).isNull();
}
@Test
public void getAllRootPathsShouldReturnAllPaths() {
PathMappedEndpoints mapped = createTestMapped(null);
assertThat(mapped.getAllRootPaths()).containsExactly("p2", "p3");
}
@Test
public void getAllPathsShouldReturnAllPaths() {
assertThat(createTestMapped(null).getAllPaths()).containsExactly("/p2", "/p3");
assertThat(createTestMapped("/x").getAllPaths()).containsExactly("/x/p2",
"/x/p3");
}
@Test
public void getEndpointWhenContainsIdShouldReturnPathMappedEndpoint() {
PathMappedEndpoints mapped = createTestMapped(null);
assertThat(mapped.getEndpoint("e2").getRootPath()).isEqualTo("p2");
}
@Test
public void getEndpointWhenMissingIdShouldReturnNull() {
PathMappedEndpoints mapped = createTestMapped(null);
assertThat(mapped.getEndpoint("xx")).isNull();
}
private PathMappedEndpoints createTestMapped(String basePath) {
List<ExposableEndpoint<?>> endpoints = new ArrayList<>();
endpoints.add(mockEndpoint("e1"));
endpoints.add(mockEndpoint("e2", "p2"));
endpoints.add(mockEndpoint("e3", "p3"));
endpoints.add(mockEndpoint("e4"));
return new PathMappedEndpoints(basePath, () -> endpoints);
}
private TestPathMappedEndpoint mockEndpoint(String id, String rootPath) {
TestPathMappedEndpoint endpoint = mock(TestPathMappedEndpoint.class);
given(endpoint.getId()).willReturn(id);
given(endpoint.getRootPath()).willReturn(rootPath);
return endpoint;
}
private TestEndpoint mockEndpoint(String id) {
TestEndpoint endpoint = mock(TestEndpoint.class);
given(endpoint.getId()).willReturn(id);
return endpoint;
}
interface TestEndpoint extends ExposableEndpoint<Operation> {
}
interface TestPathMappedEndpoint
extends ExposableEndpoint<Operation>, PathMappedEndpoint {
}
}

View File

@ -24,7 +24,7 @@ import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.PathMapper;
import org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@ -68,7 +68,7 @@ class BaseConfiguration {
ParameterValueMapper parameterMapper = new ConversionServiceParameterValueMapper(
DefaultConversionService.getSharedInstance());
return new WebEndpointDiscoverer(applicationContext, parameterMapper,
endpointMediaTypes(), EndpointPathResolver.useEndpointId(),
endpointMediaTypes(), PathMapper.useEndpointId(),
Collections.emptyList(), Collections.emptyList());
}

View File

@ -43,8 +43,8 @@ import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationI
import org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor;
import org.springframework.boot.actuate.endpoint.jmx.annotation.JmxEndpoint;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
import org.springframework.boot.actuate.endpoint.web.PathMapper;
import org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod;
import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
@ -255,9 +255,8 @@ public class WebEndpointDiscovererTests {
this.load((id) -> null, (id) -> id, configuration, consumer);
}
private void load(Function<String, Long> timeToLive,
EndpointPathResolver endpointPathResolver, Class<?> configuration,
Consumer<WebEndpointDiscoverer> consumer) {
private void load(Function<String, Long> timeToLive, PathMapper endpointPathMapper,
Class<?> configuration, Consumer<WebEndpointDiscoverer> consumer) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
configuration);
try {
@ -267,7 +266,7 @@ public class WebEndpointDiscovererTests {
Collections.singletonList("application/json"),
Collections.singletonList("application/json"));
WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(context,
parameterMapper, mediaTypes, endpointPathResolver,
parameterMapper, mediaTypes, endpointPathMapper,
Collections.singleton(new CachingOperationInvokerAdvisor(timeToLive)),
Collections.emptyList());
consumer.accept(discoverer);

View File

@ -32,7 +32,7 @@ import org.junit.runners.model.InitializationError;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.PathMapper;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.web.jersey.JerseyEndpointResourceFactory;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
@ -100,7 +100,7 @@ class JerseyEndpointsRunner extends AbstractWebEndpointRunner {
mediaTypes);
WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(
this.applicationContext, new ConversionServiceParameterValueMapper(),
endpointMediaTypes, EndpointPathResolver.useEndpointId(),
endpointMediaTypes, PathMapper.useEndpointId(),
Collections.emptyList(), Collections.emptyList());
Collection<Resource> resources = new JerseyEndpointResourceFactory()
.createEndpointResources(new EndpointMapping("/actuator"),

View File

@ -26,7 +26,7 @@ import org.junit.runners.model.InitializationError;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.PathMapper;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
@ -106,7 +106,7 @@ class WebFluxEndpointsRunner extends AbstractWebEndpointRunner {
mediaTypes);
WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(
this.applicationContext, new ConversionServiceParameterValueMapper(),
endpointMediaTypes, EndpointPathResolver.useEndpointId(),
endpointMediaTypes, PathMapper.useEndpointId(),
Collections.emptyList(), Collections.emptyList());
return new WebFluxEndpointHandlerMapping(new EndpointMapping("/actuator"),
discoverer.getEndpoints(), endpointMediaTypes,

View File

@ -26,7 +26,7 @@ import org.junit.runners.model.InitializationError;
import org.springframework.boot.actuate.endpoint.http.ActuatorMediaType;
import org.springframework.boot.actuate.endpoint.invoke.convert.ConversionServiceParameterValueMapper;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.PathMapper;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
@ -89,7 +89,7 @@ class WebMvcEndpointRunner extends AbstractWebEndpointRunner {
mediaTypes);
WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(
this.applicationContext, new ConversionServiceParameterValueMapper(),
endpointMediaTypes, EndpointPathResolver.useEndpointId(),
endpointMediaTypes, PathMapper.useEndpointId(),
Collections.emptyList(), Collections.emptyList());
return new WebMvcEndpointHandlerMapping(new EndpointMapping("/actuator"),
discoverer.getEndpoints(), endpointMediaTypes,