diff --git a/module/spring-boot-actuator/build.gradle b/module/spring-boot-actuator/build.gradle index 00544d29a2c..616229776b7 100644 --- a/module/spring-boot-actuator/build.gradle +++ b/module/spring-boot-actuator/build.gradle @@ -27,6 +27,7 @@ description = "Spring Boot Actuator" dependencies { api(project(":core:spring-boot")) + optional(project(":module:spring-boot-web-server")) optional("com.fasterxml.jackson.core:jackson-databind") optional("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") optional("com.github.ben-manes.caffeine:caffeine") diff --git a/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/WebServerNamespace.java b/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/WebServerNamespace.java index 09fc48e0746..b03cdf20ef4 100644 --- a/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/WebServerNamespace.java +++ b/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/WebServerNamespace.java @@ -18,6 +18,9 @@ package org.springframework.boot.actuate.endpoint.web; import org.jspecify.annotations.Nullable; +import org.springframework.boot.web.server.context.WebServerApplicationContext; +import org.springframework.context.ApplicationContext; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** @@ -30,6 +33,8 @@ import org.springframework.util.StringUtils; */ public final class WebServerNamespace { + private static final String WEB_SERVER_CONTEXT_CLASS = "org.springframework.boot.web.server.context.WebServerApplicationContext"; + /** * {@link WebServerNamespace} that represents the main server. */ @@ -76,6 +81,21 @@ public final class WebServerNamespace { return this.value; } + /** + * Factory method to create a new {@link WebServerNamespace} from a value. If the + * context is {@code null} or not a web server context then {@link #SERVER} is + * returned. + * @param context the application context + * @return the web server namespace + * @since 4.0.1 + */ + public static WebServerNamespace from(@Nullable ApplicationContext context) { + if (!ClassUtils.isPresent(WEB_SERVER_CONTEXT_CLASS, null)) { + return SERVER; + } + return from(WebServerApplicationContext.getServerNamespace(context)); + } + /** * Factory method to create a new {@link WebServerNamespace} from a value. If the * value is empty or {@code null} then {@link #SERVER} is returned. diff --git a/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebServerNamespaceTests.java b/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebServerNamespaceTests.java index 9776a9a00db..3db187e134d 100644 --- a/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebServerNamespaceTests.java +++ b/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/WebServerNamespaceTests.java @@ -35,7 +35,7 @@ class WebServerNamespaceTests { @Test void fromWhenValueIsNull() { - assertThat(WebServerNamespace.from(null)).isEqualTo(WebServerNamespace.SERVER); + assertThat(WebServerNamespace.from((String) null)).isEqualTo(WebServerNamespace.SERVER); } @Test diff --git a/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/reactive/EndpointRequest.java b/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/reactive/EndpointRequest.java index bb5f3c9b5f6..46601f6cccc 100644 --- a/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/reactive/EndpointRequest.java +++ b/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/reactive/EndpointRequest.java @@ -38,7 +38,6 @@ import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.boot.security.web.reactive.ApplicationContextServerWebExchangeMatcher; -import org.springframework.boot.web.server.context.WebServerApplicationContext; import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; @@ -221,14 +220,14 @@ public final class EndpointRequest { protected final boolean hasWebServerNamespace(@Nullable ApplicationContext applicationContext, WebServerNamespace webServerNamespace) { - return WebServerApplicationContext.hasServerNamespace(applicationContext, webServerNamespace.getValue()) + return hasServerNamespace(applicationContext, webServerNamespace.getValue()) || hasImplicitServerNamespace(applicationContext, webServerNamespace); } private boolean hasImplicitServerNamespace(@Nullable ApplicationContext applicationContext, WebServerNamespace webServerNamespace) { return WebServerNamespace.SERVER.equals(webServerNamespace) - && WebServerApplicationContext.getServerNamespace(applicationContext) == null + && getServerNamespace(applicationContext) == null && getApplicationContextParent(applicationContext) == null; } diff --git a/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/servlet/EndpointRequest.java b/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/servlet/EndpointRequest.java index 85ee64bef24..3f6f50a03f0 100644 --- a/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/servlet/EndpointRequest.java +++ b/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/actuate/web/servlet/EndpointRequest.java @@ -38,7 +38,6 @@ import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.boot.security.web.servlet.ApplicationContextRequestMatcher; -import org.springframework.boot.web.server.context.WebServerApplicationContext; import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; @@ -183,15 +182,14 @@ public final class EndpointRequest { protected final boolean hasWebServerNamespace(ApplicationContext applicationContext, WebServerNamespace webServerNamespace) { - return WebServerApplicationContext.hasServerNamespace(applicationContext, webServerNamespace.getValue()) + return hasServerNamespace(applicationContext, webServerNamespace.getValue()) || hasImplicitServerNamespace(applicationContext, webServerNamespace); } private boolean hasImplicitServerNamespace(ApplicationContext applicationContext, WebServerNamespace webServerNamespace) { return WebServerNamespace.SERVER.equals(webServerNamespace) - && WebServerApplicationContext.getServerNamespace(applicationContext) == null - && applicationContext.getParent() == null; + && getServerNamespace(applicationContext) == null && applicationContext.getParent() == null; } @Override diff --git a/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/web/servlet/PathRequest.java b/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/web/servlet/PathRequest.java index cad1ee0b4f4..848f224b4ac 100644 --- a/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/web/servlet/PathRequest.java +++ b/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/web/servlet/PathRequest.java @@ -24,7 +24,6 @@ import org.jspecify.annotations.Nullable; import org.springframework.boot.h2console.autoconfigure.H2ConsoleProperties; import org.springframework.boot.security.autoconfigure.web.StaticResourceLocation; import org.springframework.boot.security.web.servlet.ApplicationContextRequestMatcher; -import org.springframework.boot.web.server.context.WebServerApplicationContext; import org.springframework.context.ApplicationContext; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; @@ -76,7 +75,7 @@ public final class PathRequest { @Override protected boolean ignoreApplicationContext(WebApplicationContext applicationContext) { - return WebServerApplicationContext.hasServerNamespace(applicationContext, "management"); + return hasServerNamespace(applicationContext, "management"); } @Override diff --git a/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/web/servlet/StaticResourceRequest.java b/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/web/servlet/StaticResourceRequest.java index ef15fcaf90e..db0e8b1d1e6 100644 --- a/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/web/servlet/StaticResourceRequest.java +++ b/module/spring-boot-security/src/main/java/org/springframework/boot/security/autoconfigure/web/servlet/StaticResourceRequest.java @@ -27,7 +27,6 @@ import org.jspecify.annotations.Nullable; import org.springframework.boot.security.autoconfigure.web.StaticResourceLocation; import org.springframework.boot.security.web.servlet.ApplicationContextRequestMatcher; -import org.springframework.boot.web.server.context.WebServerApplicationContext; import org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher; @@ -148,7 +147,7 @@ public final class StaticResourceRequest { @Override protected boolean ignoreApplicationContext(WebApplicationContext applicationContext) { - return WebServerApplicationContext.hasServerNamespace(applicationContext, "management"); + return hasServerNamespace(applicationContext, "management"); } @Override diff --git a/module/spring-boot-security/src/main/java/org/springframework/boot/security/web/reactive/ApplicationContextServerWebExchangeMatcher.java b/module/spring-boot-security/src/main/java/org/springframework/boot/security/web/reactive/ApplicationContextServerWebExchangeMatcher.java index 1bba2d505fc..fc7bc6e76a9 100644 --- a/module/spring-boot-security/src/main/java/org/springframework/boot/security/web/reactive/ApplicationContextServerWebExchangeMatcher.java +++ b/module/spring-boot-security/src/main/java/org/springframework/boot/security/web/reactive/ApplicationContextServerWebExchangeMatcher.java @@ -22,9 +22,11 @@ import org.jspecify.annotations.Nullable; import reactor.core.publisher.Mono; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.boot.web.server.context.WebServerApplicationContext; import org.springframework.context.ApplicationContext; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.web.server.ServerWebExchange; /** @@ -41,6 +43,8 @@ import org.springframework.web.server.ServerWebExchange; */ public abstract class ApplicationContextServerWebExchangeMatcher implements ServerWebExchangeMatcher { + private static final String WEB_SERVER_CONTEXT_CLASS = "org.springframework.boot.web.server.context.WebServerApplicationContext"; + private final Class contextClass; private volatile @Nullable Supplier context; @@ -111,4 +115,34 @@ public abstract class ApplicationContextServerWebExchangeMatcher implements S return () -> context.getBean(this.contextClass); } + /** + * Returns {@code true} if the specified context is a + * {@link WebServerApplicationContext} with a matching server namespace. + * @param context the context to check + * @param serverNamespace the server namespace to match against + * @return {@code true} if the server namespace of the context matches + * @since 4.0.1 + */ + protected final boolean hasServerNamespace(@Nullable ApplicationContext context, String serverNamespace) { + if (!ClassUtils.isPresent(WEB_SERVER_CONTEXT_CLASS, null)) { + return false; + } + return WebServerApplicationContext.hasServerNamespace(context, serverNamespace); + } + + /** + * Returns the server namespace if the specified context is a + * {@link WebServerApplicationContext}. + * @param context the context + * @return the server namespace or {@code null} if the context is not a + * {@link WebServerApplicationContext} + * @since 4.0.1 + */ + protected final @Nullable String getServerNamespace(@Nullable ApplicationContext context) { + if (!ClassUtils.isPresent(WEB_SERVER_CONTEXT_CLASS, null)) { + return null; + } + return WebServerApplicationContext.getServerNamespace(context); + } + } diff --git a/module/spring-boot-security/src/main/java/org/springframework/boot/security/web/servlet/ApplicationContextRequestMatcher.java b/module/spring-boot-security/src/main/java/org/springframework/boot/security/web/servlet/ApplicationContextRequestMatcher.java index 4430251200a..0431e9b162c 100644 --- a/module/spring-boot-security/src/main/java/org/springframework/boot/security/web/servlet/ApplicationContextRequestMatcher.java +++ b/module/spring-boot-security/src/main/java/org/springframework/boot/security/web/servlet/ApplicationContextRequestMatcher.java @@ -19,11 +19,14 @@ package org.springframework.boot.security.web.servlet; import java.util.function.Supplier; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.boot.web.server.context.WebServerApplicationContext; import org.springframework.context.ApplicationContext; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; @@ -41,6 +44,8 @@ import org.springframework.web.context.support.WebApplicationContextUtils; */ public abstract class ApplicationContextRequestMatcher implements RequestMatcher { + private static final String WEB_SERVER_CONTEXT_CLASS = "org.springframework.boot.web.server.context.WebServerApplicationContext"; + private final Class contextClass; private volatile boolean initialized; @@ -110,4 +115,34 @@ public abstract class ApplicationContextRequestMatcher implements RequestMatc */ protected abstract boolean matches(HttpServletRequest request, Supplier context); + /** + * Returns {@code true} if the specified context is a + * {@link WebServerApplicationContext} with a matching server namespace. + * @param context the context to check + * @param serverNamespace the server namespace to match against + * @return {@code true} if the server namespace of the context matches + * @since 4.0.1 + */ + protected final boolean hasServerNamespace(@Nullable ApplicationContext context, String serverNamespace) { + if (!ClassUtils.isPresent(WEB_SERVER_CONTEXT_CLASS, null)) { + return false; + } + return WebServerApplicationContext.hasServerNamespace(context, serverNamespace); + } + + /** + * Returns the server namespace if the specified context is a + * {@link WebServerApplicationContext}. + * @param context the context + * @return the server namespace or {@code null} if the context is not a + * {@link WebServerApplicationContext} + * @since 4.0.1 + */ + protected final @Nullable String getServerNamespace(@Nullable ApplicationContext context) { + if (!ClassUtils.isPresent(WEB_SERVER_CONTEXT_CLASS, null)) { + return null; + } + return WebServerApplicationContext.getServerNamespace(context); + } + } diff --git a/module/spring-boot-security/src/test/java/org/springframework/boot/security/autoconfigure/web/servlet/PathRequestTests.java b/module/spring-boot-security/src/test/java/org/springframework/boot/security/autoconfigure/web/servlet/PathRequestTests.java index 273eddbb0c8..21159c42811 100644 --- a/module/spring-boot-security/src/test/java/org/springframework/boot/security/autoconfigure/web/servlet/PathRequestTests.java +++ b/module/spring-boot-security/src/test/java/org/springframework/boot/security/autoconfigure/web/servlet/PathRequestTests.java @@ -22,14 +22,19 @@ import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.springframework.boot.h2console.autoconfigure.H2ConsoleProperties; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.boot.web.server.autoconfigure.ServerProperties; +import org.springframework.boot.web.server.context.WebServerApplicationContext; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockServletContext; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.StringUtils; import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.StaticWebApplicationContext; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for {@link PathRequest}. @@ -59,14 +64,31 @@ class PathRequestTests { assertMatcher(matcher, "management").doesNotMatch("/js/file.js"); } + @Test + @ClassPathExclusions(packages = "org.springframework.boot.web.server.context") + void toH2ConsoleWhenNoWebServerContextClassPresent() { + assertThatExceptionOfType(NoClassDefFoundError.class) + .isThrownBy(() -> WebServerApplicationContext.class.getName()); + RequestMatcher matcher = PathRequest.toH2Console(); + StaticWebApplicationContext context = new StaticWebApplicationContext(); + assertMatcher(matcher, context).matches("/h2-console"); + assertMatcher(matcher, context).matches("/h2-console/subpath"); + assertMatcher(matcher, context).doesNotMatch("/js/file.js"); + } + private RequestMatcherAssert assertMatcher(RequestMatcher matcher) { - return assertMatcher(matcher, null); + return assertMatcher(matcher, (String) null); } private RequestMatcherAssert assertMatcher(RequestMatcher matcher, @Nullable String serverNamespace) { - TestWebApplicationContext context = new TestWebApplicationContext(serverNamespace); - context.registerBean(ServerProperties.class); - context.registerBean(H2ConsoleProperties.class); + StaticWebApplicationContext context = new TestWebApplicationContext(serverNamespace); + return assertMatcher(matcher, context); + } + + private RequestMatcherAssert assertMatcher(RequestMatcher matcher, WebApplicationContext context) { + GenericApplicationContext genericContext = (GenericApplicationContext) context; + genericContext.registerBean(ServerProperties.class); + genericContext.registerBean(H2ConsoleProperties.class); return assertThat(new RequestMatcherAssert(context, matcher)); } diff --git a/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/actuate/endpoint/web/AbstractWebFluxEndpointHandlerMapping.java b/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/actuate/endpoint/web/AbstractWebFluxEndpointHandlerMapping.java index a611702945a..c2b674360be 100644 --- a/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/actuate/endpoint/web/AbstractWebFluxEndpointHandlerMapping.java +++ b/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/actuate/endpoint/web/AbstractWebFluxEndpointHandlerMapping.java @@ -52,6 +52,7 @@ import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicat import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.boot.web.server.context.WebServerApplicationContext; import org.springframework.boot.webflux.actuate.endpoint.web.AbstractWebFluxEndpointHandlerMapping.AbstractWebFluxEndpointHandlerMappingRuntimeHints; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -372,19 +373,32 @@ public abstract class AbstractWebFluxEndpointHandlerMapping extends RequestMappi @Override public Mono> handle(ServerWebExchange exchange, @Nullable Map body) { - Map arguments = getArguments(exchange, body); - OperationArgumentResolver serverNamespaceArgumentResolver = OperationArgumentResolver - .of(WebServerNamespace.class, () -> WebServerNamespace - .from(WebServerApplicationContext.getServerNamespace(exchange.getApplicationContext()))); return this.securityContextSupplier.get() - .map((securityContext) -> new InvocationContext(securityContext, arguments, - serverNamespaceArgumentResolver, - new ProducibleOperationArgumentResolver( - () -> exchange.getRequest().getHeaders().get("Accept")))) - .flatMap((invocationContext) -> handleResult((Publisher) this.invoker.invoke(invocationContext), + .map((securityContext) -> getInvocationContext(securityContext, exchange, body)) + .flatMap((invocationContext) -> handleResult(invoke(invocationContext), exchange.getRequest().getMethod())); } + private InvocationContext getInvocationContext(SecurityContext securityContext, ServerWebExchange exchange, + @Nullable Map body) { + Map arguments = getArguments(exchange, body); + OperationArgumentResolver serverNamespaceResolver = OperationArgumentResolver.of(WebServerNamespace.class, + () -> getServerNamespace(exchange)); + OperationArgumentResolver producibleOperationResolver = new ProducibleOperationArgumentResolver( + () -> exchange.getRequest().getHeaders().get("Accept")); + return new InvocationContext(securityContext, arguments, serverNamespaceResolver, + producibleOperationResolver); + } + + private WebServerNamespace getServerNamespace(ServerWebExchange exchange) { + ApplicationContext context = exchange.getApplicationContext(); + return WebServerNamespace.from(WebServerApplicationContext.getServerNamespace(context)); + } + + private @Nullable Publisher invoke(InvocationContext invocationContext) { + return (@Nullable Publisher) this.invoker.invoke(invocationContext); + } + private Map getArguments(ServerWebExchange exchange, @Nullable Map body) { Map arguments = new LinkedHashMap<>(getTemplateVariables(exchange)); String matchAllRemainingPathSegmentsVariable = this.operation.getRequestPredicate() diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/AbstractWebMvcEndpointHandlerMapping.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/AbstractWebMvcEndpointHandlerMapping.java index a50fe1bfe90..cadeb10760f 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/AbstractWebMvcEndpointHandlerMapping.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/actuate/endpoint/web/AbstractWebMvcEndpointHandlerMapping.java @@ -51,7 +51,6 @@ 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.web.server.context.WebServerApplicationContext; import org.springframework.boot.webmvc.actuate.endpoint.web.AbstractWebMvcEndpointHandlerMapping.AbstractWebMvcEndpointHandlerMappingRuntimeHints; import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.http.HttpHeaders; @@ -323,13 +322,9 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin public @Nullable Object handle(HttpServletRequest request, @RequestBody(required = false) @Nullable Map body) { HttpHeaders headers = new ServletServerHttpRequest(request).getHeaders(); - Map 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); + InvocationContext invocationContext = getInvocationContext(request, body, headers, securityContext); return handleResult(this.operation.invoke(invocationContext), HttpMethod.valueOf(request.getMethod())); } catch (InvalidEndpointRequestException ex) { @@ -337,15 +332,21 @@ public abstract class AbstractWebMvcEndpointHandlerMapping extends RequestMappin } } - 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); + private InvocationContext getInvocationContext(HttpServletRequest request, @Nullable Map body, + HttpHeaders headers, ServletSecurityContext securityContext) { + Map arguments = getArguments(request, body); + OperationArgumentResolver serverNamespaceResolver = OperationArgumentResolver.of(WebServerNamespace.class, + () -> getServerNamespace(request)); + ProducibleOperationArgumentResolver producibleOperationResolver = new ProducibleOperationArgumentResolver( + () -> headers.get("Accept")); + return new InvocationContext(securityContext, arguments, serverNamespaceResolver, + producibleOperationResolver); + } + + private @Nullable WebServerNamespace getServerNamespace(HttpServletRequest request) { + WebApplicationContext context = WebApplicationContextUtils + .getRequiredWebApplicationContext(request.getServletContext()); + return WebServerNamespace.from(context); } @Override