Move Actuator infrastructure for WebMvc to spring-boot-webmvc
This commit is contained in:
committed by
Phillip Webb
parent
272eca17e5
commit
46a5ea0ee7
@@ -28,9 +28,8 @@ dependencies {
|
||||
optional("io.micrometer:micrometer-registry-prometheus-simpleclient")
|
||||
optional("io.prometheus:prometheus-metrics-exposition-formats")
|
||||
optional("io.prometheus:prometheus-metrics-exporter-pushgateway")
|
||||
optional("io.undertow:undertow-servlet")
|
||||
optional("jakarta.servlet:jakarta.servlet-api")
|
||||
optional("javax.cache:cache-api")
|
||||
optional("org.apache.tomcat.embed:tomcat-embed-core")
|
||||
optional("org.aspectj:aspectjweaver")
|
||||
optional("org.eclipse.angus:angus-mail")
|
||||
optional("org.hibernate.validator:hibernate-validator")
|
||||
@@ -39,8 +38,6 @@ dependencies {
|
||||
optional("org.springframework:spring-jdbc")
|
||||
optional("org.springframework:spring-messaging")
|
||||
optional("org.springframework:spring-webflux")
|
||||
optional("org.springframework:spring-web")
|
||||
optional("org.springframework:spring-webmvc")
|
||||
optional("org.springframework.graphql:spring-graphql")
|
||||
optional("org.springframework.data:spring-data-rest-webmvc")
|
||||
optional("org.springframework.security:spring-security-core")
|
||||
@@ -55,7 +52,6 @@ dependencies {
|
||||
testImplementation(project(":spring-boot-project:spring-boot-jsonb"))
|
||||
testImplementation(project(":spring-boot-project:spring-boot-test"))
|
||||
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
|
||||
testImplementation("com.squareup.okhttp3:mockwebserver")
|
||||
testImplementation("io.micrometer:micrometer-observation-test")
|
||||
testImplementation("io.projectreactor:reactor-test")
|
||||
testImplementation("net.minidev:json-smart")
|
||||
|
||||
@@ -1,513 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.servlet;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.aot.hint.annotation.Reflective;
|
||||
import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||
import org.springframework.boot.actuate.endpoint.OperationArgumentResolver;
|
||||
import org.springframework.boot.actuate.endpoint.ProducibleOperationArgumentResolver;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.AbstractWebMvcEndpointHandlerMappingRuntimeHints;
|
||||
import org.springframework.boot.web.server.context.WebServerApplicationContext;
|
||||
import org.springframework.context.annotation.ImportRuntimeHints;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.support.WebApplicationContextUtils;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
|
||||
|
||||
/**
|
||||
* A custom {@link HandlerMapping} that makes {@link ExposableWebEndpoint web endpoints}
|
||||
* available over HTTP using Spring MVC.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Madhura Bhave
|
||||
* @author Phillip Webb
|
||||
* @author Brian Clozel
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@ImportRuntimeHints(AbstractWebMvcEndpointHandlerMappingRuntimeHints.class)
|
||||
public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappingInfoHandlerMapping
|
||||
implements InitializingBean {
|
||||
|
||||
private final EndpointMapping endpointMapping;
|
||||
|
||||
private final Collection<ExposableWebEndpoint> endpoints;
|
||||
|
||||
private final EndpointMediaTypes endpointMediaTypes;
|
||||
|
||||
private final CorsConfiguration corsConfiguration;
|
||||
|
||||
private final boolean shouldRegisterLinksMapping;
|
||||
|
||||
private final Method handleMethod = ReflectionUtils.findMethod(OperationHandler.class, "handle",
|
||||
HttpServletRequest.class, Map.class);
|
||||
|
||||
private RequestMappingInfo.BuilderConfiguration builderConfig = new RequestMappingInfo.BuilderConfiguration();
|
||||
|
||||
/**
|
||||
* Creates a new {@code WebEndpointHandlerMapping} that provides mappings for the
|
||||
* operations of the given {@code webEndpoints}.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param endpoints the web endpoints
|
||||
* @param endpointMediaTypes media types consumed and produced by the endpoints
|
||||
* @param shouldRegisterLinksMapping whether the links endpoint should be registered
|
||||
*/
|
||||
public AbstractWebMvcEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<ExposableWebEndpoint> endpoints, EndpointMediaTypes endpointMediaTypes,
|
||||
boolean shouldRegisterLinksMapping) {
|
||||
this(endpointMapping, endpoints, endpointMediaTypes, null, shouldRegisterLinksMapping);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code AbstractWebMvcEndpointHandlerMapping} that provides mappings
|
||||
* for the operations of the given endpoints.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param endpoints the web endpoints
|
||||
* @param endpointMediaTypes media types consumed and produced by the endpoints
|
||||
* @param corsConfiguration the CORS configuration for the endpoints or {@code null}
|
||||
* @param shouldRegisterLinksMapping whether the links endpoint should be registered
|
||||
*/
|
||||
public AbstractWebMvcEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<ExposableWebEndpoint> endpoints, EndpointMediaTypes endpointMediaTypes,
|
||||
CorsConfiguration corsConfiguration, boolean shouldRegisterLinksMapping) {
|
||||
this.endpointMapping = endpointMapping;
|
||||
this.endpoints = endpoints;
|
||||
this.endpointMediaTypes = endpointMediaTypes;
|
||||
this.corsConfiguration = corsConfiguration;
|
||||
this.shouldRegisterLinksMapping = shouldRegisterLinksMapping;
|
||||
setOrder(-100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
this.builderConfig = new RequestMappingInfo.BuilderConfiguration();
|
||||
this.builderConfig.setPatternParser(getPatternParser());
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initHandlerMethods() {
|
||||
for (ExposableWebEndpoint endpoint : this.endpoints) {
|
||||
for (WebOperation operation : endpoint.getOperations()) {
|
||||
registerMappingForOperation(endpoint, operation);
|
||||
}
|
||||
}
|
||||
if (this.shouldRegisterLinksMapping) {
|
||||
registerLinksMapping();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HandlerMethod createHandlerMethod(Object handler, Method method) {
|
||||
HandlerMethod handlerMethod = super.createHandlerMethod(handler, method);
|
||||
return new WebMvcEndpointHandlerMethod(handlerMethod.getBean(), handlerMethod.getMethod());
|
||||
}
|
||||
|
||||
private void registerMappingForOperation(ExposableWebEndpoint endpoint, WebOperation operation) {
|
||||
WebOperationRequestPredicate predicate = operation.getRequestPredicate();
|
||||
String path = predicate.getPath();
|
||||
String matchAllRemainingPathSegmentsVariable = predicate.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
path = path.replace("{*" + matchAllRemainingPathSegmentsVariable + "}", "**");
|
||||
}
|
||||
registerMapping(endpoint, predicate, operation, path);
|
||||
}
|
||||
|
||||
protected void registerMapping(ExposableWebEndpoint endpoint, WebOperationRequestPredicate predicate,
|
||||
WebOperation operation, String path) {
|
||||
ServletWebOperation servletWebOperation = wrapServletWebOperation(endpoint, operation,
|
||||
new ServletWebOperationAdapter(operation));
|
||||
registerMapping(createRequestMappingInfo(predicate, path), new OperationHandler(servletWebOperation),
|
||||
this.handleMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook point that allows subclasses to wrap the {@link ServletWebOperation} before
|
||||
* it's called. Allows additional features, such as security, to be added.
|
||||
* @param endpoint the source endpoint
|
||||
* @param operation the source operation
|
||||
* @param servletWebOperation the servlet web operation to wrap
|
||||
* @return a wrapped servlet web operation
|
||||
*/
|
||||
protected ServletWebOperation wrapServletWebOperation(ExposableWebEndpoint endpoint, WebOperation operation,
|
||||
ServletWebOperation servletWebOperation) {
|
||||
return servletWebOperation;
|
||||
}
|
||||
|
||||
private RequestMappingInfo createRequestMappingInfo(WebOperationRequestPredicate predicate, String path) {
|
||||
String subPath = this.endpointMapping.createSubPath(path);
|
||||
List<String> paths = new ArrayList<>();
|
||||
paths.add(subPath);
|
||||
if (!StringUtils.hasLength(subPath)) {
|
||||
paths.add("/");
|
||||
}
|
||||
return RequestMappingInfo.paths(paths.toArray(new String[0]))
|
||||
.options(this.builderConfig)
|
||||
.methods(RequestMethod.valueOf(predicate.getHttpMethod().name()))
|
||||
.consumes(predicate.getConsumes().toArray(new String[0]))
|
||||
.produces(predicate.getProduces().toArray(new String[0]))
|
||||
.build();
|
||||
}
|
||||
|
||||
private void registerLinksMapping() {
|
||||
String path = this.endpointMapping.getPath();
|
||||
String linksPath = (StringUtils.hasLength(path)) ? this.endpointMapping.createSubPath("/") : "/";
|
||||
RequestMappingInfo mapping = RequestMappingInfo.paths(linksPath)
|
||||
.methods(RequestMethod.GET)
|
||||
.produces(this.endpointMediaTypes.getProduced().toArray(new String[0]))
|
||||
.options(this.builderConfig)
|
||||
.build();
|
||||
LinksHandler linksHandler = getLinksHandler();
|
||||
registerMapping(mapping, linksHandler, ReflectionUtils.findMethod(linksHandler.getClass(), "links",
|
||||
HttpServletRequest.class, HttpServletResponse.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasCorsConfigurationSource(Object handler) {
|
||||
return this.corsConfiguration != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mapping) {
|
||||
return this.corsConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {
|
||||
CorsConfiguration corsConfiguration = super.getCorsConfiguration(handler, request);
|
||||
return (corsConfiguration != null) ? corsConfiguration : this.corsConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isHandler(Class<?> beanType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Handler providing actuator links at the root endpoint.
|
||||
* @return the links handler
|
||||
*/
|
||||
protected abstract LinksHandler getLinksHandler();
|
||||
|
||||
/**
|
||||
* Return the web endpoints being mapped.
|
||||
* @return the endpoints
|
||||
*/
|
||||
public Collection<ExposableWebEndpoint> getEndpoints() {
|
||||
return this.endpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler providing actuator links at the root endpoint.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
protected interface LinksHandler {
|
||||
|
||||
Object links(HttpServletRequest request, HttpServletResponse response);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A servlet web operation that can be handled by Spring MVC.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
protected interface ServletWebOperation {
|
||||
|
||||
Object handle(HttpServletRequest request, Map<String, String> body);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter class to convert an {@link OperationInvoker} into a
|
||||
* {@link ServletWebOperation}.
|
||||
*/
|
||||
private static class ServletWebOperationAdapter implements ServletWebOperation {
|
||||
|
||||
private static final String PATH_SEPARATOR = AntPathMatcher.DEFAULT_PATH_SEPARATOR;
|
||||
|
||||
private static final List<Function<Object, Object>> BODY_CONVERTERS;
|
||||
|
||||
static {
|
||||
List<Function<Object, Object>> converters = new ArrayList<>();
|
||||
if (ClassUtils.isPresent("reactor.core.publisher.Flux",
|
||||
ServletWebOperationAdapter.class.getClassLoader())) {
|
||||
converters.add(new FluxBodyConverter());
|
||||
}
|
||||
BODY_CONVERTERS = Collections.unmodifiableList(converters);
|
||||
}
|
||||
|
||||
private final WebOperation operation;
|
||||
|
||||
ServletWebOperationAdapter(WebOperation operation) {
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object handle(HttpServletRequest request, @RequestBody(required = false) Map<String, String> body) {
|
||||
HttpHeaders headers = new ServletServerHttpRequest(request).getHeaders();
|
||||
Map<String, Object> arguments = getArguments(request, body);
|
||||
try {
|
||||
ServletSecurityContext securityContext = new ServletSecurityContext(request);
|
||||
ProducibleOperationArgumentResolver producibleOperationArgumentResolver = new ProducibleOperationArgumentResolver(
|
||||
() -> headers.get("Accept"));
|
||||
InvocationContext invocationContext = new InvocationContext(securityContext, arguments,
|
||||
serverNamespaceArgumentResolver(request), producibleOperationArgumentResolver);
|
||||
return handleResult(this.operation.invoke(invocationContext), HttpMethod.valueOf(request.getMethod()));
|
||||
}
|
||||
catch (InvalidEndpointRequestException ex) {
|
||||
throw new InvalidEndpointBadRequestException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private OperationArgumentResolver serverNamespaceArgumentResolver(HttpServletRequest request) {
|
||||
if (ClassUtils.isPresent("org.springframework.boot.web.server.context.WebServerApplicationContext", null)) {
|
||||
return OperationArgumentResolver.of(WebServerNamespace.class, () -> {
|
||||
WebApplicationContext applicationContext = WebApplicationContextUtils
|
||||
.getRequiredWebApplicationContext(request.getServletContext());
|
||||
return WebServerNamespace.from(WebServerApplicationContext.getServerNamespace(applicationContext));
|
||||
});
|
||||
}
|
||||
return OperationArgumentResolver.of(WebServerNamespace.class, () -> null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Actuator web endpoint '" + this.operation.getId() + "'";
|
||||
}
|
||||
|
||||
private Map<String, Object> getArguments(HttpServletRequest request, Map<String, String> body) {
|
||||
Map<String, Object> arguments = new LinkedHashMap<>(getTemplateVariables(request));
|
||||
String matchAllRemainingPathSegmentsVariable = this.operation.getRequestPredicate()
|
||||
.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
arguments.put(matchAllRemainingPathSegmentsVariable, getRemainingPathSegments(request));
|
||||
}
|
||||
if (body != null && HttpMethod.POST.name().equals(request.getMethod())) {
|
||||
arguments.putAll(body);
|
||||
}
|
||||
request.getParameterMap()
|
||||
.forEach((name, values) -> arguments.put(name,
|
||||
(values.length != 1) ? Arrays.asList(values) : values[0]));
|
||||
return arguments;
|
||||
}
|
||||
|
||||
private Object getRemainingPathSegments(HttpServletRequest request) {
|
||||
String[] pathTokens = tokenize(request, HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, true);
|
||||
String[] patternTokens = tokenize(request, HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, false);
|
||||
int numberOfRemainingPathSegments = pathTokens.length - patternTokens.length + 1;
|
||||
Assert.state(numberOfRemainingPathSegments >= 0, "Unable to extract remaining path segments");
|
||||
String[] remainingPathSegments = new String[numberOfRemainingPathSegments];
|
||||
System.arraycopy(pathTokens, patternTokens.length - 1, remainingPathSegments, 0,
|
||||
numberOfRemainingPathSegments);
|
||||
return remainingPathSegments;
|
||||
}
|
||||
|
||||
private String[] tokenize(HttpServletRequest request, String attributeName, boolean decode) {
|
||||
String value = (String) request.getAttribute(attributeName);
|
||||
String[] segments = StringUtils.tokenizeToStringArray(value, PATH_SEPARATOR, false, true);
|
||||
if (decode) {
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
if (segments[i].contains("%")) {
|
||||
segments[i] = StringUtils.uriDecode(segments[i], StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
}
|
||||
return segments;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, String> getTemplateVariables(HttpServletRequest request) {
|
||||
return (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||
}
|
||||
|
||||
private Object handleResult(Object result, HttpMethod httpMethod) {
|
||||
if (result == null) {
|
||||
return new ResponseEntity<>(
|
||||
(httpMethod != HttpMethod.GET) ? HttpStatus.NO_CONTENT : HttpStatus.NOT_FOUND);
|
||||
}
|
||||
if (!(result instanceof WebEndpointResponse<?> response)) {
|
||||
return convertIfNecessary(result);
|
||||
}
|
||||
MediaType contentType = (response.getContentType() != null) ? new MediaType(response.getContentType())
|
||||
: null;
|
||||
return ResponseEntity.status(response.getStatus())
|
||||
.contentType(contentType)
|
||||
.body(convertIfNecessary(response.getBody()));
|
||||
}
|
||||
|
||||
private Object convertIfNecessary(Object body) {
|
||||
for (Function<Object, Object> converter : BODY_CONVERTERS) {
|
||||
body = converter.apply(body);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
private static final class FluxBodyConverter implements Function<Object, Object> {
|
||||
|
||||
@Override
|
||||
public Object apply(Object body) {
|
||||
if (!(body instanceof Flux)) {
|
||||
return body;
|
||||
}
|
||||
return ((Flux<?>) body).collectList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for a {@link ServletWebOperation}.
|
||||
*/
|
||||
private static final class OperationHandler {
|
||||
|
||||
private final ServletWebOperation operation;
|
||||
|
||||
OperationHandler(ServletWebOperation operation) {
|
||||
this.operation = operation;
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@Reflective
|
||||
Object handle(HttpServletRequest request, @RequestBody(required = false) Map<String, String> body) {
|
||||
return this.operation.handle(request, body);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.operation.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link HandlerMethod} subclass for endpoint information logging.
|
||||
*/
|
||||
private static class WebMvcEndpointHandlerMethod extends HandlerMethod {
|
||||
|
||||
WebMvcEndpointHandlerMethod(Object bean, Method method) {
|
||||
super(bean, method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getBean().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerMethod createWithResolvedBean() {
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Nested exception used to wrap an {@link InvalidEndpointRequestException} and
|
||||
* provide a {@link HttpStatus#BAD_REQUEST} status.
|
||||
*/
|
||||
private static class InvalidEndpointBadRequestException extends ResponseStatusException {
|
||||
|
||||
InvalidEndpointBadRequestException(InvalidEndpointRequestException cause) {
|
||||
super(HttpStatus.BAD_REQUEST, cause.getReason(), cause);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class ServletSecurityContext implements SecurityContext {
|
||||
|
||||
private final HttpServletRequest request;
|
||||
|
||||
private ServletSecurityContext(HttpServletRequest request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getPrincipal() {
|
||||
return this.request.getUserPrincipal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserInRole(String role) {
|
||||
return this.request.isUserInRole(role);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class AbstractWebMvcEndpointHandlerMappingRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
private final ReflectiveRuntimeHintsRegistrar reflectiveRegistrar = new ReflectiveRuntimeHintsRegistrar();
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
this.reflectiveRegistrar.registerRuntimeHints(hints, OperationHandler.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2024 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
|
||||
*
|
||||
* https://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.servlet;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
/**
|
||||
* A custom {@link HandlerMapping} that allows health groups to be mapped to an additional
|
||||
* path.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public class AdditionalHealthEndpointPathsWebMvcHandlerMapping extends AbstractWebMvcEndpointHandlerMapping {
|
||||
|
||||
private final ExposableWebEndpoint healthEndpoint;
|
||||
|
||||
private final Set<HealthEndpointGroup> groups;
|
||||
|
||||
public AdditionalHealthEndpointPathsWebMvcHandlerMapping(ExposableWebEndpoint healthEndpoint,
|
||||
Set<HealthEndpointGroup> groups) {
|
||||
super(new EndpointMapping(""), asList(healthEndpoint), null, false);
|
||||
this.healthEndpoint = healthEndpoint;
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
private static Collection<ExposableWebEndpoint> asList(ExposableWebEndpoint healthEndpoint) {
|
||||
return (healthEndpoint != null) ? Collections.singletonList(healthEndpoint) : Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initHandlerMethods() {
|
||||
if (this.healthEndpoint == null) {
|
||||
return;
|
||||
}
|
||||
for (WebOperation operation : this.healthEndpoint.getOperations()) {
|
||||
WebOperationRequestPredicate predicate = operation.getRequestPredicate();
|
||||
String matchAllRemainingPathSegmentsVariable = predicate.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
for (HealthEndpointGroup group : this.groups) {
|
||||
AdditionalHealthEndpointPath additionalPath = group.getAdditionalPath();
|
||||
if (additionalPath != null) {
|
||||
registerMapping(this.healthEndpoint, predicate, operation, additionalPath.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LinksHandler getLinksHandler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.servlet;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Access;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointAccessResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.ExposableControllerEndpoint;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
|
||||
/**
|
||||
* {@link HandlerMapping} that exposes
|
||||
* {@link org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint @ControllerEndpoint}
|
||||
* and
|
||||
* {@link org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint @RestControllerEndpoint}
|
||||
* annotated endpoints over Spring MVC.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
* @deprecated since 3.3.5 in favor of {@code @Endpoint} and {@code @WebEndpoint} support
|
||||
*/
|
||||
@Deprecated(since = "3.3.5", forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public class ControllerEndpointHandlerMapping extends RequestMappingHandlerMapping {
|
||||
|
||||
private static final Set<RequestMethod> READ_ONLY_ACCESS_REQUEST_METHODS = EnumSet.of(RequestMethod.GET,
|
||||
RequestMethod.HEAD);
|
||||
|
||||
private final EndpointMapping endpointMapping;
|
||||
|
||||
private final CorsConfiguration corsConfiguration;
|
||||
|
||||
private final Map<Object, ExposableControllerEndpoint> handlers;
|
||||
|
||||
private final EndpointAccessResolver accessResolver;
|
||||
|
||||
/**
|
||||
* Create a new {@link ControllerEndpointHandlerMapping} instance providing mappings
|
||||
* for the specified endpoints.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param endpoints the web endpoints
|
||||
* @param corsConfiguration the CORS configuration for the endpoints or {@code null}
|
||||
*/
|
||||
public ControllerEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<ExposableControllerEndpoint> endpoints, CorsConfiguration corsConfiguration) {
|
||||
this(endpointMapping, endpoints, corsConfiguration, (endpointId, defaultAccess) -> Access.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ControllerEndpointHandlerMapping} instance providing mappings
|
||||
* for the specified endpoints.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param endpoints the web endpoints
|
||||
* @param corsConfiguration the CORS configuration for the endpoints or {@code null}
|
||||
* @param endpointAccessResolver resolver for endpoint access
|
||||
*/
|
||||
public ControllerEndpointHandlerMapping(EndpointMapping endpointMapping,
|
||||
Collection<ExposableControllerEndpoint> endpoints, CorsConfiguration corsConfiguration,
|
||||
EndpointAccessResolver endpointAccessResolver) {
|
||||
Assert.notNull(endpointMapping, "'endpointMapping' must not be null");
|
||||
Assert.notNull(endpoints, "'endpoints' must not be null");
|
||||
this.endpointMapping = endpointMapping;
|
||||
this.handlers = getHandlers(endpoints);
|
||||
this.corsConfiguration = corsConfiguration;
|
||||
this.accessResolver = endpointAccessResolver;
|
||||
setOrder(-100);
|
||||
}
|
||||
|
||||
private Map<Object, ExposableControllerEndpoint> getHandlers(Collection<ExposableControllerEndpoint> endpoints) {
|
||||
Map<Object, ExposableControllerEndpoint> handlers = new LinkedHashMap<>();
|
||||
endpoints.forEach((endpoint) -> handlers.put(endpoint.getController(), endpoint));
|
||||
return Collections.unmodifiableMap(handlers);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initHandlerMethods() {
|
||||
this.handlers.keySet().forEach(this::detectHandlerMethods);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
|
||||
ExposableControllerEndpoint endpoint = this.handlers.get(handler);
|
||||
Access access = this.accessResolver.accessFor(endpoint.getEndpointId(), endpoint.getDefaultAccess());
|
||||
if (access == Access.NONE) {
|
||||
return;
|
||||
}
|
||||
if (access == Access.READ_ONLY) {
|
||||
mapping = withReadOnlyAccess(access, mapping);
|
||||
if (CollectionUtils.isEmpty(mapping.getMethodsCondition().getMethods())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
mapping = withEndpointMappedPatterns(endpoint, mapping);
|
||||
super.registerHandlerMethod(handler, method, mapping);
|
||||
}
|
||||
|
||||
private RequestMappingInfo withReadOnlyAccess(Access access, RequestMappingInfo mapping) {
|
||||
Set<RequestMethod> methods = mapping.getMethodsCondition().getMethods();
|
||||
Set<RequestMethod> modifiedMethods = new HashSet<>(methods);
|
||||
if (modifiedMethods.isEmpty()) {
|
||||
modifiedMethods.addAll(READ_ONLY_ACCESS_REQUEST_METHODS);
|
||||
}
|
||||
else {
|
||||
modifiedMethods.retainAll(READ_ONLY_ACCESS_REQUEST_METHODS);
|
||||
}
|
||||
return mapping.mutate().methods(modifiedMethods.toArray(new RequestMethod[0])).build();
|
||||
}
|
||||
|
||||
private RequestMappingInfo withEndpointMappedPatterns(ExposableControllerEndpoint endpoint,
|
||||
RequestMappingInfo mapping) {
|
||||
Set<PathPattern> patterns = mapping.getPathPatternsCondition().getPatterns();
|
||||
if (patterns.isEmpty()) {
|
||||
patterns = Collections.singleton(getPatternParser().parse(""));
|
||||
}
|
||||
String[] endpointMappedPatterns = patterns.stream()
|
||||
.map((pattern) -> getEndpointMappedPattern(endpoint, pattern))
|
||||
.toArray(String[]::new);
|
||||
return mapping.mutate().paths(endpointMappedPatterns).build();
|
||||
}
|
||||
|
||||
private String getEndpointMappedPattern(ExposableControllerEndpoint endpoint, PathPattern pattern) {
|
||||
return this.endpointMapping.createSubPath(endpoint.getRootPath() + pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean hasCorsConfigurationSource(Object handler) {
|
||||
return this.corsConfiguration != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mapping) {
|
||||
return this.corsConfiguration;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.servlet;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.aot.hint.BindingReflectionHintsRegistrar;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.aot.hint.annotation.Reflective;
|
||||
import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar;
|
||||
import org.springframework.boot.actuate.endpoint.OperationResponseBody;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.Link;
|
||||
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping.WebMvcEndpointHandlerMappingRuntimeHints;
|
||||
import org.springframework.context.annotation.ImportRuntimeHints;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
/**
|
||||
* A custom {@link HandlerMapping} that makes web endpoints available over HTTP using
|
||||
* Spring MVC.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@ImportRuntimeHints(WebMvcEndpointHandlerMappingRuntimeHints.class)
|
||||
public class WebMvcEndpointHandlerMapping extends AbstractWebMvcEndpointHandlerMapping {
|
||||
|
||||
private final EndpointLinksResolver linksResolver;
|
||||
|
||||
/**
|
||||
* Creates a new {@code WebMvcEndpointHandlerMapping} instance that provides mappings
|
||||
* for the given endpoints.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param endpoints the web endpoints
|
||||
* @param endpointMediaTypes media types consumed and produced by the endpoints
|
||||
* @param corsConfiguration the CORS configuration for the endpoints or {@code null}
|
||||
* @param linksResolver resolver for determining links to available endpoints
|
||||
* @param shouldRegisterLinksMapping whether the links endpoint should be registered
|
||||
*/
|
||||
public WebMvcEndpointHandlerMapping(EndpointMapping endpointMapping, Collection<ExposableWebEndpoint> endpoints,
|
||||
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
|
||||
EndpointLinksResolver linksResolver, boolean shouldRegisterLinksMapping) {
|
||||
super(endpointMapping, endpoints, endpointMediaTypes, corsConfiguration, shouldRegisterLinksMapping);
|
||||
this.linksResolver = linksResolver;
|
||||
setOrder(-100);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LinksHandler getLinksHandler() {
|
||||
return new WebMvcLinksHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for root endpoint providing links.
|
||||
*/
|
||||
class WebMvcLinksHandler implements LinksHandler {
|
||||
|
||||
@Override
|
||||
@ResponseBody
|
||||
@Reflective
|
||||
public Map<String, Map<String, Link>> links(HttpServletRequest request, HttpServletResponse response) {
|
||||
Map<String, Link> links = WebMvcEndpointHandlerMapping.this.linksResolver
|
||||
.resolveLinks(request.getRequestURL().toString());
|
||||
return OperationResponseBody.of(Collections.singletonMap("_links", links));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Actuator root web endpoint";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class WebMvcEndpointHandlerMappingRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
private final ReflectiveRuntimeHintsRegistrar reflectiveRegistrar = new ReflectiveRuntimeHintsRegistrar();
|
||||
|
||||
private final BindingReflectionHintsRegistrar bindingRegistrar = new BindingReflectionHintsRegistrar();
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
this.reflectiveRegistrar.registerRuntimeHints(hints, WebMvcLinksHandler.class);
|
||||
this.bindingRegistrar.registerReflectionHints(hints.reflection(), Link.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Spring MVC support for actuator endpoints.
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint.web.servlet;
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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
|
||||
*
|
||||
* https://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.servlet;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
import org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.AbstractWebMvcEndpointHandlerMappingRuntimeHints;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link AbstractWebMvcEndpointHandlerMapping}.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class AbstractWebMvcEndpointHandlerMappingTests {
|
||||
|
||||
@Test
|
||||
void shouldRegisterHints() {
|
||||
RuntimeHints runtimeHints = new RuntimeHints();
|
||||
new AbstractWebMvcEndpointHandlerMappingRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader());
|
||||
assertThat(RuntimeHintsPredicates.reflection()
|
||||
.onType(TypeReference
|
||||
.of("org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping.OperationHandler")))
|
||||
.accepts(runtimeHints);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2024 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
|
||||
*
|
||||
* https://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.servlet;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.Access;
|
||||
import org.springframework.boot.actuate.endpoint.EndpointId;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.ExposableControllerEndpoint;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link ControllerEndpointHandlerMapping}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @deprecated since 3.3.5 in favor of {@code @Endpoint} and {@code @WebEndpoint} support
|
||||
*/
|
||||
@Deprecated(since = "3.3.5", forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
class ControllerEndpointHandlerMappingTests {
|
||||
|
||||
private final StaticApplicationContext context = new StaticApplicationContext();
|
||||
|
||||
@Test
|
||||
void mappingWithNoPrefix() throws Exception {
|
||||
ExposableControllerEndpoint first = firstEndpoint();
|
||||
ExposableControllerEndpoint second = secondEndpoint();
|
||||
ControllerEndpointHandlerMapping mapping = createMapping("", first, second);
|
||||
assertThat(mapping.getHandler(request("GET", "/first")).getHandler())
|
||||
.isEqualTo(handlerOf(first.getController(), "get"));
|
||||
assertThat(mapping.getHandler(request("POST", "/second")).getHandler())
|
||||
.isEqualTo(handlerOf(second.getController(), "save"));
|
||||
assertThat(mapping.getHandler(request("GET", "/third"))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void mappingWithPrefix() throws Exception {
|
||||
ExposableControllerEndpoint first = firstEndpoint();
|
||||
ExposableControllerEndpoint second = secondEndpoint();
|
||||
ControllerEndpointHandlerMapping mapping = createMapping("actuator", first, second);
|
||||
assertThat(mapping.getHandler(request("GET", "/actuator/first")).getHandler())
|
||||
.isEqualTo(handlerOf(first.getController(), "get"));
|
||||
assertThat(mapping.getHandler(request("POST", "/actuator/second")).getHandler())
|
||||
.isEqualTo(handlerOf(second.getController(), "save"));
|
||||
assertThat(mapping.getHandler(request("GET", "/first"))).isNull();
|
||||
assertThat(mapping.getHandler(request("GET", "/second"))).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void mappingNarrowedToMethod() {
|
||||
ExposableControllerEndpoint first = firstEndpoint();
|
||||
ControllerEndpointHandlerMapping mapping = createMapping("actuator", first);
|
||||
assertThatExceptionOfType(HttpRequestMethodNotSupportedException.class)
|
||||
.isThrownBy(() -> mapping.getHandler(request("POST", "/actuator/first")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mappingWithNoPath() throws Exception {
|
||||
ExposableControllerEndpoint pathless = pathlessEndpoint();
|
||||
ControllerEndpointHandlerMapping mapping = createMapping("actuator", pathless);
|
||||
assertThat(mapping.getHandler(request("GET", "/actuator/pathless")).getHandler())
|
||||
.isEqualTo(handlerOf(pathless.getController(), "get"));
|
||||
assertThat(mapping.getHandler(request("GET", "/pathless"))).isNull();
|
||||
assertThat(mapping.getHandler(request("GET", "/"))).isNull();
|
||||
}
|
||||
|
||||
private ControllerEndpointHandlerMapping createMapping(String prefix, ExposableControllerEndpoint... endpoints) {
|
||||
ControllerEndpointHandlerMapping mapping = new ControllerEndpointHandlerMapping(new EndpointMapping(prefix),
|
||||
Arrays.asList(endpoints), null, (endpointId, defaultAccess) -> Access.UNRESTRICTED);
|
||||
mapping.setApplicationContext(this.context);
|
||||
mapping.afterPropertiesSet();
|
||||
return mapping;
|
||||
}
|
||||
|
||||
private HandlerMethod handlerOf(Object source, String methodName) {
|
||||
return new HandlerMethod(source, ReflectionUtils.findMethod(source.getClass(), methodName));
|
||||
}
|
||||
|
||||
private MockHttpServletRequest request(String method, String requestURI) {
|
||||
return new MockHttpServletRequest(method, requestURI);
|
||||
}
|
||||
|
||||
private ExposableControllerEndpoint firstEndpoint() {
|
||||
return mockEndpoint(EndpointId.of("first"), new FirstTestMvcEndpoint());
|
||||
}
|
||||
|
||||
private ExposableControllerEndpoint secondEndpoint() {
|
||||
return mockEndpoint(EndpointId.of("second"), new SecondTestMvcEndpoint());
|
||||
}
|
||||
|
||||
private ExposableControllerEndpoint pathlessEndpoint() {
|
||||
return mockEndpoint(EndpointId.of("pathless"), new PathlessControllerEndpoint());
|
||||
}
|
||||
|
||||
private ExposableControllerEndpoint mockEndpoint(EndpointId id, Object controller) {
|
||||
ExposableControllerEndpoint endpoint = mock(ExposableControllerEndpoint.class);
|
||||
given(endpoint.getEndpointId()).willReturn(id);
|
||||
given(endpoint.getController()).willReturn(controller);
|
||||
given(endpoint.getRootPath()).willReturn(id.toString());
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
@ControllerEndpoint(id = "first")
|
||||
static class FirstTestMvcEndpoint {
|
||||
|
||||
@GetMapping("/")
|
||||
String get() {
|
||||
return "test";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ControllerEndpoint(id = "second")
|
||||
static class SecondTestMvcEndpoint {
|
||||
|
||||
@PostMapping("/")
|
||||
void save() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ControllerEndpoint(id = "pathless")
|
||||
static class PathlessControllerEndpoint {
|
||||
|
||||
@GetMapping
|
||||
String get() {
|
||||
return "test";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.servlet;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
import org.springframework.boot.actuate.endpoint.web.Link;
|
||||
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping.WebMvcEndpointHandlerMappingRuntimeHints;
|
||||
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping.WebMvcLinksHandler;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link WebMvcEndpointHandlerMapping}.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class WebMvcEndpointHandlerMappingTests {
|
||||
|
||||
@Test
|
||||
void shouldRegisterHints() {
|
||||
RuntimeHints runtimeHints = new RuntimeHints();
|
||||
new WebMvcEndpointHandlerMappingRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader());
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(WebMvcLinksHandler.class, "links"))
|
||||
.accepts(runtimeHints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(Link.class)).accepts(runtimeHints);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user