Allow to customize the path of a web endpoint

This commit introduces a endpoints.<id>.web.path generic property that
allows to customize the path of an endpoint. By default the path is the
same as the id of the endpoint.

Such customization does not apply for the CloudFoundry specific
endpoints.

Closes gh-10181
This commit is contained in:
Stephane Nicoll
2017-10-26 14:07:50 +02:00
parent 622e65a290
commit 56afc25304
16 changed files with 244 additions and 26 deletions

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.endpoint.web;
/**
* Resolve the path of an endpoint.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
@FunctionalInterface
public interface EndpointPathResolver {
/**
* Resolve the path for the endpoint with the specified {@code endpointId}.
* @param endpointId the id of an endpoint
* @return the path of the endpoint
*/
String resolvePath(String endpointId);
}

View File

@@ -40,6 +40,7 @@ import org.springframework.boot.actuate.endpoint.cache.CachingConfiguration;
import org.springframework.boot.actuate.endpoint.cache.CachingConfigurationFactory;
import org.springframework.boot.actuate.endpoint.cache.CachingOperationInvoker;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.EndpointPathResolver;
import org.springframework.boot.actuate.endpoint.web.OperationRequestPredicate;
import org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod;
import org.springframework.boot.actuate.endpoint.web.WebEndpointOperation;
@@ -71,14 +72,17 @@ public class WebAnnotationEndpointDiscoverer extends
* @param cachingConfigurationFactory the {@link CachingConfiguration} factory to use
* @param endpointMediaTypes the media types produced and consumed by web endpoint
* operations
* @param endpointPathResolver the {@link EndpointPathResolver} used to resolve
* endpoint paths
*/
public WebAnnotationEndpointDiscoverer(ApplicationContext applicationContext,
OperationParameterMapper operationParameterMapper,
CachingConfigurationFactory cachingConfigurationFactory,
EndpointMediaTypes endpointMediaTypes) {
EndpointMediaTypes endpointMediaTypes,
EndpointPathResolver endpointPathResolver) {
super(applicationContext,
new WebEndpointOperationFactory(operationParameterMapper,
endpointMediaTypes),
endpointMediaTypes, endpointPathResolver),
WebEndpointOperation::getRequestPredicate, cachingConfigurationFactory);
}
@@ -121,10 +125,14 @@ public class WebAnnotationEndpointDiscoverer extends
private final EndpointMediaTypes endpointMediaTypes;
private final EndpointPathResolver endpointPathResolver;
private WebEndpointOperationFactory(OperationParameterMapper parameterMapper,
EndpointMediaTypes endpointMediaTypes) {
EndpointMediaTypes endpointMediaTypes,
EndpointPathResolver endpointPathResolver) {
this.parameterMapper = parameterMapper;
this.endpointMediaTypes = endpointMediaTypes;
this.endpointPathResolver = endpointPathResolver;
}
@Override
@@ -147,7 +155,8 @@ public class WebAnnotationEndpointDiscoverer extends
}
private String determinePath(String endpointId, Method operationMethod) {
StringBuilder path = new StringBuilder(endpointId);
StringBuilder path = new StringBuilder(
this.endpointPathResolver.resolvePath(endpointId));
Stream.of(operationMethod.getParameters())
.filter((
parameter) -> parameter.getAnnotation(Selector.class) != null)

View File

@@ -385,7 +385,7 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
DefaultConversionService.getSharedInstance());
return new WebAnnotationEndpointDiscoverer(applicationContext,
parameterMapper, (id) -> new CachingConfiguration(0),
endpointMediaTypes());
endpointMediaTypes(), (id) -> id);
}
@Bean

View File

@@ -189,8 +189,8 @@ public class WebAnnotationEndpointDiscovererTests {
@Test
public void endpointMainReadOperationIsCachedWithMatchingId() {
load((id) -> new CachingConfiguration(500), TestEndpointConfiguration.class,
(discoverer) -> {
load((id) -> new CachingConfiguration(500), (id) -> id,
TestEndpointConfiguration.class, (discoverer) -> {
Map<String, EndpointInfo<WebEndpointOperation>> endpoints = mapEndpoints(
discoverer.discoverEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
@@ -237,12 +237,29 @@ public class WebAnnotationEndpointDiscovererTests {
});
}
@Test
public void endpointPathCanBeCustomized() {
load((id) -> null, (id) -> "custom/" + id,
AdditionalOperationWebEndpointConfiguration.class, (discoverer) -> {
Map<String, EndpointInfo<WebEndpointOperation>> endpoints = mapEndpoints(
discoverer.discoverEndpoints());
assertThat(endpoints).containsOnlyKeys("test");
EndpointInfo<WebEndpointOperation> endpoint = endpoints.get("test");
assertThat(requestPredicates(endpoint)).has(requestPredicates(
path("custom/test").httpMethod(WebEndpointHttpMethod.GET).consumes()
.produces("application/json"),
path("custom/test/{id}").httpMethod(WebEndpointHttpMethod.GET).consumes()
.produces("application/json")));
});
}
private void load(Class<?> configuration,
Consumer<WebAnnotationEndpointDiscoverer> consumer) {
this.load((id) -> null, configuration, consumer);
this.load((id) -> null, (id) -> id, configuration, consumer);
}
private void load(CachingConfigurationFactory cachingConfigurationFactory,
EndpointPathResolver endpointPathResolver,
Class<?> configuration, Consumer<WebAnnotationEndpointDiscoverer> consumer) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
configuration);
@@ -254,7 +271,8 @@ public class WebAnnotationEndpointDiscovererTests {
cachingConfigurationFactory,
new EndpointMediaTypes(
Collections.singletonList("application/json"),
Collections.singletonList("application/json"))));
Collections.singletonList("application/json")),
endpointPathResolver));
}
finally {
context.close();

View File

@@ -99,7 +99,7 @@ class JerseyEndpointsRunner extends AbstractWebEndpointRunner {
WebAnnotationEndpointDiscoverer discoverer = new WebAnnotationEndpointDiscoverer(
this.applicationContext,
new ConversionServiceOperationParameterMapper(), (id) -> null,
endpointMediaTypes);
endpointMediaTypes, (id) -> id);
Collection<Resource> resources = new JerseyEndpointResourceFactory()
.createEndpointResources(new EndpointMapping("/application"),
discoverer.discoverEndpoints(), endpointMediaTypes);

View File

@@ -105,7 +105,7 @@ class WebFluxEndpointsRunner extends AbstractWebEndpointRunner {
WebAnnotationEndpointDiscoverer discoverer = new WebAnnotationEndpointDiscoverer(
this.applicationContext,
new ConversionServiceOperationParameterMapper(), (id) -> null,
endpointMediaTypes);
endpointMediaTypes, (id) -> id);
return new WebFluxEndpointHandlerMapping(new EndpointMapping("/application"),
discoverer.discoverEndpoints(), endpointMediaTypes,
new CorsConfiguration());

View File

@@ -88,7 +88,7 @@ class WebMvcEndpointRunner extends AbstractWebEndpointRunner {
WebAnnotationEndpointDiscoverer discoverer = new WebAnnotationEndpointDiscoverer(
this.applicationContext,
new ConversionServiceOperationParameterMapper(), (id) -> null,
endpointMediaTypes);
endpointMediaTypes, (id) -> id);
return new WebMvcEndpointHandlerMapping(new EndpointMapping("/application"),
discoverer.discoverEndpoints(), endpointMediaTypes,
new CorsConfiguration());