From 2522a73111f0c1d191cb06facef629be9cf81a9c Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Thu, 14 Nov 2024 17:50:41 +0100 Subject: [PATCH 001/701] Next development version (v7.0.0-SNAPSHOT) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index f39094ba73a2..2353ef8dc47e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=6.2.0-SNAPSHOT +version=7.0.0-SNAPSHOT org.gradle.caching=true org.gradle.jvmargs=-Xmx2048m From a4cea65ace9799d686dfd8e7f91f15f5b3fa3de7 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Thu, 14 Nov 2024 17:54:15 +0100 Subject: [PATCH 002/701] Update GitHub workflows for 7.0 generation --- .github/workflows/build-and-deploy-snapshot.yml | 2 +- .github/workflows/release-milestone.yml | 4 ++-- .github/workflows/release.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-deploy-snapshot.yml b/.github/workflows/build-and-deploy-snapshot.yml index 7f8885773aa0..875b8aa6fae6 100644 --- a/.github/workflows/build-and-deploy-snapshot.yml +++ b/.github/workflows/build-and-deploy-snapshot.yml @@ -27,7 +27,7 @@ jobs: /**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false /**/framework-api-*-docs.zip::zip.type=docs /**/framework-api-*-schema.zip::zip.type=schema - build-name: 'spring-framework-6.2.x' + build-name: 'spring-framework-7.0.x' folder: 'deployment-repository' password: ${{ secrets.ARTIFACTORY_PASSWORD }} repository: 'libs-snapshot-local' diff --git a/.github/workflows/release-milestone.yml b/.github/workflows/release-milestone.yml index 72b531e421a3..761419927811 100644 --- a/.github/workflows/release-milestone.yml +++ b/.github/workflows/release-milestone.yml @@ -2,8 +2,8 @@ name: Release Milestone on: push: tags: - - v6.2.0-M[1-9] - - v6.2.0-RC[1-9] + - v7.0.0-M[1-9] + - v7.0.0-RC[1-9] concurrency: group: ${{ github.workflow }}-${{ github.ref }} jobs: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ba555e9af9f5..88450b0b6bd9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Release on: push: tags: - - v6.2.[0-9]+ + - v7.0.[0-9]+ concurrency: group: ${{ github.workflow }}-${{ github.ref }} jobs: From c28cbfd58294fe9e4fe347c289fc2109b71d2c13 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 19 Nov 2024 18:04:29 +0100 Subject: [PATCH 003/701] Upgrade Servlet, JSP and WebSocket API versions This commit updates the Spring Framework baseline for the Servlet, JSP and WebSocket APIs. This also removes the previously deprecated APIs in JSP `PageContext` and guards against the deprecation of the `PushBuilder` API. See gh-33918 --- framework-platform/framework-platform.gradle | 10 +++++----- .../springframework/mock/web/MockPageContext.java | 14 -------------- .../server/reactive/ServletServerHttpResponse.java | 10 +--------- .../server/reactive/CookieIntegrationTests.java | 2 ++ .../web/testfixture/servlet/MockPageContext.java | 14 -------------- .../ServletRequestMethodArgumentResolver.java | 2 ++ 6 files changed, 10 insertions(+), 42 deletions(-) diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index 8f5f61ca4971..0e155f0a1181 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -75,13 +75,13 @@ dependencies { api("jakarta.mail:jakarta.mail-api:2.0.1") api("jakarta.persistence:jakarta.persistence-api:3.0.0") api("jakarta.resource:jakarta.resource-api:2.0.0") - api("jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.0") - api("jakarta.servlet.jsp:jakarta.servlet.jsp-api:3.1.1") - api("jakarta.servlet:jakarta.servlet-api:6.0.0") + api("jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.2") + api("jakarta.servlet.jsp:jakarta.servlet.jsp-api:4.0.0") + api("jakarta.servlet:jakarta.servlet-api:6.1.0") api("jakarta.transaction:jakarta.transaction-api:2.0.1") api("jakarta.validation:jakarta.validation-api:3.0.2") - api("jakarta.websocket:jakarta.websocket-api:2.1.0") - api("jakarta.websocket:jakarta.websocket-client-api:2.1.0") + api("jakarta.websocket:jakarta.websocket-api:2.2.0") + api("jakarta.websocket:jakarta.websocket-client-api:2.2.0") api("jakarta.xml.bind:jakarta.xml.bind-api:3.0.1") api("javax.annotation:javax.annotation-api:1.3.2") api("javax.cache:cache-api:1.1.1") diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockPageContext.java b/spring-test/src/main/java/org/springframework/mock/web/MockPageContext.java index 6bcbf97d57da..99f430ebb39b 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockPageContext.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockPageContext.java @@ -267,26 +267,12 @@ public JspWriter getOut() { return this.out; } - @Override - @Deprecated - @Nullable - public jakarta.servlet.jsp.el.ExpressionEvaluator getExpressionEvaluator() { - return null; - } - @Override @Nullable public ELContext getELContext() { return null; } - @Override - @Deprecated - @Nullable - public jakarta.servlet.jsp.el.VariableResolver getVariableResolver() { - return null; - } - @Override public HttpSession getSession() { return this.request.getSession(); diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java index f77b09569a8d..4d8e96b27e0b 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java @@ -39,7 +39,6 @@ import org.springframework.http.ResponseCookie; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; /** * Adapt {@link ServerHttpResponse} to the Servlet {@link HttpServletResponse}. @@ -50,8 +49,6 @@ */ class ServletServerHttpResponse extends AbstractListenerServerHttpResponse { - private static final boolean IS_SERVLET61 = ReflectionUtils.findField(HttpServletResponse.class, "SC_PERMANENT_REDIRECT") != null; - private final HttpServletResponse response; private final ServletOutputStream outputStream; @@ -186,12 +183,7 @@ protected void applyCookies() { cookie.setSecure(httpCookie.isSecure()); cookie.setHttpOnly(httpCookie.isHttpOnly()); if (httpCookie.isPartitioned()) { - if (IS_SERVLET61) { - cookie.setAttribute("Partitioned", ""); - } - else { - cookie.setAttribute("Partitioned", "true"); - } + cookie.setAttribute("Partitioned", ""); } this.response.addCookie(cookie); } diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/CookieIntegrationTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/CookieIntegrationTests.java index 4ceee53b45c9..4d6f1fa74438 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/CookieIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/CookieIntegrationTests.java @@ -30,6 +30,7 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.testfixture.http.server.reactive.bootstrap.AbstractHttpHandlerIntegrationTests; import org.springframework.web.testfixture.http.server.reactive.bootstrap.HttpServer; +import org.springframework.web.testfixture.http.server.reactive.bootstrap.JettyHttpServer; import org.springframework.web.testfixture.http.server.reactive.bootstrap.UndertowHttpServer; import static org.assertj.core.api.Assertions.assertThat; @@ -80,6 +81,7 @@ public void basicTest(HttpServer httpServer) throws Exception { @ParameterizedHttpServerTest public void partitionedAttributeTest(HttpServer httpServer) throws Exception { assumeFalse(httpServer instanceof UndertowHttpServer, "Undertow does not support Partitioned cookies"); + assumeFalse(httpServer instanceof JettyHttpServer, "Jetty does not support Servlet 6.1 yet"); startServer(httpServer); URI url = URI.create("http://localhost:" + port); diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockPageContext.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockPageContext.java index 3063910edf5c..6476710dad45 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockPageContext.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockPageContext.java @@ -267,26 +267,12 @@ public JspWriter getOut() { return this.out; } - @Override - @Deprecated - @Nullable - public jakarta.servlet.jsp.el.ExpressionEvaluator getExpressionEvaluator() { - return null; - } - @Override @Nullable public ELContext getELContext() { return null; } - @Override - @Deprecated - @Nullable - public jakarta.servlet.jsp.el.VariableResolver getVariableResolver() { - return null; - } - @Override public HttpSession getSession() { return this.request.getSession(); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolver.java index 6a72c052d8cf..319891fc1948 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolver.java @@ -70,6 +70,7 @@ public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override + @SuppressWarnings("deprecation") public boolean supportsParameter(MethodParameter parameter) { Class paramType = parameter.getParameterType(); return (WebRequest.class.isAssignableFrom(paramType) || @@ -121,6 +122,7 @@ private T resolveNativeRequest(NativeWebRequest webRequest, Class require } @Nullable + @SuppressWarnings("deprecation") private Object resolveArgument(Class paramType, HttpServletRequest request) throws IOException { if (HttpSession.class.isAssignableFrom(paramType)) { HttpSession session = request.getSession(); From 4b3e192ca16484bc3c5d1f81e11f1418025826aa Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 19 Nov 2024 18:07:22 +0100 Subject: [PATCH 004/701] Upgrade to Tomcat 11.0 This commit upgrades the baseline to Tomcat 11.0 and adapts to the following behavior changes in Tomcat: * the MimeHeaders#clear method has been removed * expired cookies do not set "Max-Age=0" anymore * responses to HEAD requests do not write the "Content-Length" header anymore. Closes gh-33916 --- framework-platform/framework-platform.gradle | 8 ++++---- .../http/server/reactive/TomcatHeadersAdapter.java | 5 +++-- .../web/server/session/WebSessionIntegrationTests.java | 4 ++-- .../method/annotation/RequestMappingIntegrationTests.java | 1 - 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index 0e155f0a1181..998c49185579 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -103,10 +103,10 @@ dependencies { api("org.apache.httpcomponents.client5:httpclient5:5.4.1") api("org.apache.httpcomponents.core5:httpcore5-reactive:5.3.1") api("org.apache.poi:poi-ooxml:5.2.5") - api("org.apache.tomcat.embed:tomcat-embed-core:10.1.28") - api("org.apache.tomcat.embed:tomcat-embed-websocket:10.1.28") - api("org.apache.tomcat:tomcat-util:10.1.28") - api("org.apache.tomcat:tomcat-websocket:10.1.28") + api("org.apache.tomcat.embed:tomcat-embed-core:11.0.1") + api("org.apache.tomcat.embed:tomcat-embed-websocket:11.0.1") + api("org.apache.tomcat:tomcat-util:11.0.1") + api("org.apache.tomcat:tomcat-websocket:11.0.1") api("org.aspectj:aspectjrt:1.9.22.1") api("org.aspectj:aspectjtools:1.9.22.1") api("org.aspectj:aspectjweaver:1.9.22.1") diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHeadersAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHeadersAdapter.java index ac49e0357f37..c921a7475006 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHeadersAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHeadersAdapter.java @@ -161,10 +161,11 @@ public void putAll(Map> map) { map.forEach(this::put); } - @SuppressWarnings("deprecation") // on Tomcat 10.1.16+ @Override public void clear() { - this.headers.clear(); + for (int i = 0 ; i < this.headers.size(); i++) { + this.headers.removeHeader(i); + } } @Override diff --git a/spring-web/src/test/java/org/springframework/web/server/session/WebSessionIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/server/session/WebSessionIntegrationTests.java index 1cae7c7c96d4..0b3643eafb50 100644 --- a/spring-web/src/test/java/org/springframework/web/server/session/WebSessionIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/web/server/session/WebSessionIntegrationTests.java @@ -141,7 +141,7 @@ public void expiredSessionEnds(HttpServer httpServer) throws Exception { assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); String value = response.getHeaders().getFirst("Set-Cookie"); assertThat(value).isNotNull(); - assertThat(value).as("Actual value: " + value).contains("Max-Age=0"); + assertThat(value).as("Actual value: " + value).containsAnyOf("Expires=Thu, 01 Jan 1970", "Max-Age=0"); } @ParameterizedHttpServerTest @@ -189,7 +189,7 @@ public void invalidate(HttpServer httpServer) throws Exception { assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); String value = response.getHeaders().getFirst("Set-Cookie"); assertThat(value).isNotNull(); - assertThat(value).as("Actual value: " + value).contains("Max-Age=0"); + assertThat(value).as("Actual value: " + value).containsAnyOf("Expires=Thu, 01 Jan 1970", "Max-Age=0"); } private String extractSessionId(HttpHeaders headers) { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java index ca52e7a255d8..cfd0b5f8f364 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingIntegrationTests.java @@ -93,7 +93,6 @@ void httpHead(HttpServer httpServer) throws Exception { HttpHeaders headers = getRestTemplate().headForHeaders(url); String contentType = headers.getFirst("Content-Type"); assertThat(contentType).isNotNull(); - assertThat(headers.getContentLength()).isEqualTo(3); } @ParameterizedHttpServerTest From f548da8a8dc5681124b6237eb6a070188ccccd2a Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 19 Nov 2024 18:08:39 +0100 Subject: [PATCH 005/701] Temporarily ignore webflux tomcat integration tests After our Tomcat 11 upgrade, several WebFlux integration tests with Tomcat started failing because Tomcat considers some chunked client requests as invalid. While we're investigating this, this commit temporarily disables the relevant tests. See gh-33917 --- ...MultipartRouterFunctionIntegrationTests.java | 11 +++++++++++ .../MultipartWebClientIntegrationTests.java | 17 +++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartRouterFunctionIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartRouterFunctionIntegrationTests.java index 5388b7566f97..ef5de62858b5 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartRouterFunctionIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/MultipartRouterFunctionIntegrationTests.java @@ -49,6 +49,7 @@ import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.testfixture.http.server.reactive.bootstrap.HttpServer; +import org.springframework.web.testfixture.http.server.reactive.bootstrap.TomcatHttpServer; import org.springframework.web.testfixture.http.server.reactive.bootstrap.UndertowHttpServer; import static org.assertj.core.api.Assertions.assertThat; @@ -68,6 +69,8 @@ class MultipartRouterFunctionIntegrationTests extends AbstractRouterFunctionInte @ParameterizedHttpServerTest void multipartData(HttpServer httpServer) throws Exception { + assumeFalse(httpServer instanceof TomcatHttpServer, + "TomcatHttpServer fails with invalid request body chunk"); startServer(httpServer); Mono> result = webClient @@ -86,6 +89,8 @@ void multipartData(HttpServer httpServer) throws Exception { @ParameterizedHttpServerTest void parts(HttpServer httpServer) throws Exception { + assumeFalse(httpServer instanceof TomcatHttpServer, + "TomcatHttpServer fails with invalid request body chunk"); startServer(httpServer); Mono> result = webClient @@ -104,6 +109,8 @@ void parts(HttpServer httpServer) throws Exception { @ParameterizedHttpServerTest void transferTo(HttpServer httpServer) throws Exception { + assumeFalse(httpServer instanceof TomcatHttpServer, + "TomcatHttpServer fails with invalid request body chunk"); // TODO Determine why Undertow fails: https://github.com/spring-projects/spring-framework/issues/25310 assumeFalse(httpServer instanceof UndertowHttpServer, "Undertow currently fails with transferTo"); verifyTransferTo(httpServer); @@ -144,6 +151,8 @@ private void verifyTransferTo(HttpServer httpServer) throws Exception { @ParameterizedHttpServerTest void partData(HttpServer httpServer) throws Exception { + assumeFalse(httpServer instanceof TomcatHttpServer, + "TomcatHttpServer fails with invalid request body chunk"); startServer(httpServer); Mono> result = webClient @@ -162,6 +171,8 @@ void partData(HttpServer httpServer) throws Exception { @ParameterizedHttpServerTest void proxy(HttpServer httpServer) throws Exception { + assumeFalse(httpServer instanceof TomcatHttpServer, + "TomcatHttpServer fails with invalid request body chunk"); assumeFalse(httpServer instanceof UndertowHttpServer, "Undertow currently fails proxying requests"); startServer(httpServer); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartWebClientIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartWebClientIntegrationTests.java index cc0fb65e35d7..dee5bf9cde68 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartWebClientIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MultipartWebClientIntegrationTests.java @@ -60,6 +60,7 @@ import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import org.springframework.web.testfixture.http.server.reactive.bootstrap.AbstractHttpHandlerIntegrationTests; import org.springframework.web.testfixture.http.server.reactive.bootstrap.HttpServer; +import org.springframework.web.testfixture.http.server.reactive.bootstrap.TomcatHttpServer; import org.springframework.web.testfixture.http.server.reactive.bootstrap.UndertowHttpServer; import static org.assertj.core.api.Assertions.assertThat; @@ -87,6 +88,8 @@ protected void startServer(HttpServer httpServer) throws Exception { @ParameterizedHttpServerTest void requestPart(HttpServer httpServer) throws Exception { + assumeFalse(httpServer instanceof TomcatHttpServer, + "TomcatHttpServer fails with invalid request body chunk"); startServer(httpServer); Mono> result = webClient @@ -104,6 +107,8 @@ void requestPart(HttpServer httpServer) throws Exception { @ParameterizedHttpServerTest void requestBodyMap(HttpServer httpServer) throws Exception { + assumeFalse(httpServer instanceof TomcatHttpServer, + "TomcatHttpServer fails with invalid request body chunk"); startServer(httpServer); Mono result = webClient @@ -120,6 +125,8 @@ void requestBodyMap(HttpServer httpServer) throws Exception { @ParameterizedHttpServerTest void requestBodyFlux(HttpServer httpServer) throws Exception { + assumeFalse(httpServer instanceof TomcatHttpServer, + "TomcatHttpServer fails with invalid request body chunk"); startServer(httpServer); Mono result = webClient @@ -136,6 +143,8 @@ void requestBodyFlux(HttpServer httpServer) throws Exception { @ParameterizedHttpServerTest void filePartsFlux(HttpServer httpServer) throws Exception { + assumeFalse(httpServer instanceof TomcatHttpServer, + "TomcatHttpServer fails with invalid request body chunk"); startServer(httpServer); Mono result = webClient @@ -152,6 +161,8 @@ void filePartsFlux(HttpServer httpServer) throws Exception { @ParameterizedHttpServerTest void filePartsMono(HttpServer httpServer) throws Exception { + assumeFalse(httpServer instanceof TomcatHttpServer, + "TomcatHttpServer fails with invalid request body chunk"); startServer(httpServer); Mono result = webClient @@ -168,6 +179,8 @@ void filePartsMono(HttpServer httpServer) throws Exception { @ParameterizedHttpServerTest void transferTo(HttpServer httpServer) throws Exception { + assumeFalse(httpServer instanceof TomcatHttpServer, + "TomcatHttpServer fails with invalid request body chunk"); // TODO Determine why Undertow fails: https://github.com/spring-projects/spring-framework/issues/25310 assumeFalse(httpServer instanceof UndertowHttpServer, "Undertow currently fails with transferTo"); startServer(httpServer); @@ -188,6 +201,8 @@ void transferTo(HttpServer httpServer) throws Exception { @ParameterizedHttpServerTest void modelAttribute(HttpServer httpServer) throws Exception { + assumeFalse(httpServer instanceof TomcatHttpServer, + "TomcatHttpServer fails with invalid request body chunk"); startServer(httpServer); Mono result = webClient @@ -204,6 +219,8 @@ void modelAttribute(HttpServer httpServer) throws Exception { @ParameterizedHttpServerTest void partData(HttpServer httpServer) throws Exception { + assumeFalse(httpServer instanceof TomcatHttpServer, + "TomcatHttpServer fails with invalid request body chunk"); startServer(httpServer); Mono result = webClient From 3b65506c13f18535e2fb055fd790bf45efb13de5 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 22 Nov 2024 15:08:02 +0100 Subject: [PATCH 006/701] Use ByteBuffer support in ServletHttpHandlerAdapter As of Servlet 6.1, the `ServletInputStream` and `ServletOutputStream` offer read and write variants based on `ByteBuffer` instead of byte arrays. This can improve performance and avoid memory copy for I/O calls. This was already partially supported for some servers like Tomcat through specific adapters. This commit moves this support to the standard `ServletHttpHandlerAdapter` and makes it available for all Servlet 6.1+ containers. Closes gh-33748 --- .../reactive/JettyHttpHandlerAdapter.java | 83 ------------------- .../reactive/ServletServerHttpRequest.java | 42 ++++++---- .../reactive/ServletServerHttpResponse.java | 19 +++-- .../reactive/TomcatHttpHandlerAdapter.java | 64 +------------- .../reactive/bootstrap/JettyHttpServer.java | 7 +- 5 files changed, 40 insertions(+), 175 deletions(-) delete mode 100644 spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java deleted file mode 100644 index d45c72b2666d..000000000000 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2002-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.http.server.reactive; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -import jakarta.servlet.AsyncContext; -import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.jetty.ee10.servlet.HttpOutput; - -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DataBufferFactory; - -/** - * {@link ServletHttpHandlerAdapter} extension that uses Jetty APIs for writing - * to the response with {@link ByteBuffer}. - * - * @author Violeta Georgieva - * @author Brian Clozel - * @author Juergen Hoeller - * @since 5.0 - * @see org.springframework.web.server.adapter.AbstractReactiveWebInitializer - */ -public class JettyHttpHandlerAdapter extends ServletHttpHandlerAdapter { - - public JettyHttpHandlerAdapter(HttpHandler httpHandler) { - super(httpHandler); - } - - - @Override - protected ServletServerHttpResponse createResponse(HttpServletResponse response, - AsyncContext context, ServletServerHttpRequest request) throws IOException { - - return new Jetty12ServerHttpResponse( - response, context, getDataBufferFactory(), getBufferSize(), request); - } - - - private static final class Jetty12ServerHttpResponse extends ServletServerHttpResponse { - - Jetty12ServerHttpResponse(HttpServletResponse response, AsyncContext asyncContext, - DataBufferFactory bufferFactory, int bufferSize, ServletServerHttpRequest request) - throws IOException { - - super(response, asyncContext, bufferFactory, bufferSize, request); - } - - @Override - protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException { - OutputStream output = getOutputStream(); - if (output instanceof HttpOutput httpOutput) { - int len = 0; - try (DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers()) { - while (iterator.hasNext() && httpOutput.isReady()) { - ByteBuffer byteBuffer = iterator.next(); - len += byteBuffer.remaining(); - httpOutput.write(byteBuffer); - } - } - return len; - } - return super.writeToOutputStream(dataBuffer); - } - } - -} diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java index ed5c52b112ee..ee2b9a5014bd 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpRequest.java @@ -20,6 +20,7 @@ import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; +import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.security.cert.X509Certificate; import java.util.Enumeration; @@ -38,6 +39,7 @@ import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.http.HttpCookie; import org.springframework.http.HttpHeaders; @@ -58,6 +60,7 @@ * * @author Rossen Stoyanchev * @author Juergen Hoeller + * @author Brian Clozel * @since 5.0 */ class ServletServerHttpRequest extends AbstractServerHttpRequest { @@ -75,7 +78,7 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest { private final DataBufferFactory bufferFactory; - private final byte[] buffer; + private final int bufferSize; private final AsyncListener asyncListener; @@ -99,7 +102,7 @@ public ServletServerHttpRequest(MultiValueMap headers, HttpServl this.request = request; this.bufferFactory = bufferFactory; - this.buffer = new byte[bufferSize]; + this.bufferSize = bufferSize; this.asyncListener = new RequestAsyncListener(); @@ -275,20 +278,31 @@ protected final ServletInputStream getInputStream() { * or {@link #EOF_BUFFER} if the input stream returned -1. */ DataBuffer readFromInputStream() throws IOException { - int read = this.inputStream.read(this.buffer); - logBytesRead(read); - - if (read > 0) { - DataBuffer dataBuffer = this.bufferFactory.allocateBuffer(read); - dataBuffer.write(this.buffer, 0, read); - return dataBuffer; + DataBuffer dataBuffer = this.bufferFactory.allocateBuffer(this.bufferSize); + int read = -1; + try { + try (DataBuffer.ByteBufferIterator iterator = dataBuffer.writableByteBuffers()) { + Assert.state(iterator.hasNext(), "No ByteBuffer available"); + ByteBuffer byteBuffer = iterator.next(); + read = this.inputStream.read(byteBuffer); + } + logBytesRead(read); + if (read > 0) { + dataBuffer.writePosition(read); + return dataBuffer; + } + else if (read == -1) { + return EOF_BUFFER; + } + else { + return AbstractListenerReadPublisher.EMPTY_BUFFER; + } } - - if (read == -1) { - return EOF_BUFFER; + finally { + if (read <= 0) { + DataBufferUtils.release(dataBuffer); + } } - - return AbstractListenerReadPublisher.EMPTY_BUFFER; } protected final void logBytesRead(int read) { diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java index 4d8e96b27e0b..ac145a04d454 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java @@ -17,7 +17,7 @@ package org.springframework.http.server.reactive; import java.io.IOException; -import java.io.InputStream; +import java.nio.ByteBuffer; import java.nio.charset.Charset; import jakarta.servlet.AsyncContext; @@ -45,6 +45,7 @@ * * @author Rossen Stoyanchev * @author Juergen Hoeller + * @author Brian Clozel * @since 5.0 */ class ServletServerHttpResponse extends AbstractListenerServerHttpResponse { @@ -222,15 +223,15 @@ protected final ServletOutputStream getOutputStream() { */ protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException { ServletOutputStream outputStream = this.outputStream; - InputStream input = dataBuffer.asInputStream(); - int bytesWritten = 0; - byte[] buffer = new byte[this.bufferSize]; - int bytesRead; - while (outputStream.isReady() && (bytesRead = input.read(buffer)) != -1) { - outputStream.write(buffer, 0, bytesRead); - bytesWritten += bytesRead; + int len = 0; + try (DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers()) { + while (iterator.hasNext() && outputStream.isReady()) { + ByteBuffer byteBuffer = iterator.next(); + len += byteBuffer.remaining(); + outputStream.write(byteBuffer); + } } - return bytesWritten; + return len; } private void flush() throws IOException { diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java index d93b4b89002a..20064837c71f 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -26,16 +26,12 @@ import jakarta.servlet.http.HttpServletRequestWrapper; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponseWrapper; -import org.apache.catalina.connector.CoyoteInputStream; -import org.apache.catalina.connector.CoyoteOutputStream; import org.apache.catalina.connector.RequestFacade; import org.apache.catalina.connector.ResponseFacade; import org.apache.coyote.Request; import org.apache.coyote.Response; -import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; -import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.HttpHeaders; import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; @@ -81,10 +77,6 @@ private static final class TomcatServerHttpRequest extends ServletServerHttpRequ private static final Field COYOTE_REQUEST_FIELD; - private final int bufferSize; - - private final DataBufferFactory factory; - static { Field field = ReflectionUtils.findField(RequestFacade.class, "request"); Assert.state(field != null, "Incompatible Tomcat implementation"); @@ -97,8 +89,6 @@ private static final class TomcatServerHttpRequest extends ServletServerHttpRequ throws IOException, URISyntaxException { super(createTomcatHttpHeaders(request), request, context, servletPath, factory, bufferSize); - this.factory = factory; - this.bufferSize = bufferSize; } private static MultiValueMap createTomcatHttpHeaders(HttpServletRequest request) { @@ -124,41 +114,6 @@ else if (request instanceof HttpServletRequestWrapper wrapper) { } } - @Override - protected DataBuffer readFromInputStream() throws IOException { - if (getInputStream() instanceof CoyoteInputStream coyoteInputStream) { - DataBuffer dataBuffer = this.factory.allocateBuffer(this.bufferSize); - int read = -1; - try { - try (DataBuffer.ByteBufferIterator iterator = dataBuffer.writableByteBuffers()) { - Assert.state(iterator.hasNext(), "No ByteBuffer available"); - ByteBuffer byteBuffer = iterator.next(); - read = coyoteInputStream.read(byteBuffer); - } - logBytesRead(read); - if (read > 0) { - dataBuffer.writePosition(read); - return dataBuffer; - } - else if (read == -1) { - return EOF_BUFFER; - } - else { - return AbstractListenerReadPublisher.EMPTY_BUFFER; - } - } - finally { - if (read <= 0) { - DataBufferUtils.release(dataBuffer); - } - } - } - else { - // It's possible InputStream can be wrapped, preventing use of CoyoteInputStream - return super.readFromInputStream(); - } - - } } @@ -208,23 +163,6 @@ protected void applyHeaders() { adaptHeaders(true); } - @Override - protected int writeToOutputStream(DataBuffer dataBuffer) throws IOException { - if (getOutputStream() instanceof CoyoteOutputStream coyoteOutputStream) { - int len = 0; - try (DataBuffer.ByteBufferIterator iterator = dataBuffer.readableByteBuffers()) { - while (iterator.hasNext() && coyoteOutputStream.isReady()) { - ByteBuffer byteBuffer = iterator.next(); - len += byteBuffer.remaining(); - coyoteOutputStream.write(byteBuffer); - } - } - return len; - } - else { - return super.writeToOutputStream(dataBuffer); - } - } } } diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/JettyHttpServer.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/JettyHttpServer.java index 12878528ff1f..eb1cc0859914 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/JettyHttpServer.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/http/server/reactive/bootstrap/JettyHttpServer.java @@ -22,7 +22,6 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; -import org.springframework.http.server.reactive.JettyHttpHandlerAdapter; import org.springframework.http.server.reactive.ServletHttpHandlerAdapter; /** @@ -41,7 +40,7 @@ protected void initServer() throws Exception { this.jettyServer = new Server(); - ServletHttpHandlerAdapter servlet = createServletAdapter(); + ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(resolveHttpHandler()); ServletHolder servletHolder = new ServletHolder(servlet); servletHolder.setAsyncSupported(true); @@ -56,10 +55,6 @@ protected void initServer() throws Exception { this.jettyServer.setHandler(this.contextHandler); } - private ServletHttpHandlerAdapter createServletAdapter() { - return new JettyHttpHandlerAdapter(resolveHttpHandler()); - } - @Override protected void startInternal() throws Exception { this.jettyServer.start(); From 5e08a88219dd5aaa2f3a3bf4ff36c7b2bcd6ed37 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 22 Nov 2024 17:07:44 +0100 Subject: [PATCH 007/701] Upgrade Servlet mock classes to Servlet 6.1 This commit upgrades our Mock Servlet classes for Servlet 6.1 support: * the read/write `ByteBuffer` variants for `ServletInputStream` and `ServletOutputStream` were not added as the default implementation matches well the testing use case. * Implement the session accessor with a simple lambda. Our mocks do not simulate the scheduling of request/response processing on different threads. * Ensure that the response content length can only be written before the response is committed. Calling those methods after commit is a no-op, per specification. Closes gh-33749 --- .../mock/web/MockHttpServletResponse.java | 12 ++++++++---- .../springframework/mock/web/MockHttpSession.java | 7 ++++++- .../servlet/MockHttpServletResponse.java | 14 +++++++++----- .../web/testfixture/servlet/MockHttpSession.java | 6 +++++- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java index 592e216763a3..18df83dbf6aa 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java @@ -319,8 +319,10 @@ public String getContentAsString(Charset fallbackCharset) throws UnsupportedEnco @Override public void setContentLength(int contentLength) { - this.contentLength = contentLength; - doAddHeaderValue(HttpHeaders.CONTENT_LENGTH, contentLength, true); + if (!this.committed) { + this.contentLength = contentLength; + doAddHeaderValue(HttpHeaders.CONTENT_LENGTH, contentLength, true); + } } /** @@ -334,8 +336,10 @@ public int getContentLength() { @Override public void setContentLengthLong(long contentLength) { - this.contentLength = contentLength; - doAddHeaderValue(HttpHeaders.CONTENT_LENGTH, contentLength, true); + if (!this.committed) { + this.contentLength = contentLength; + doAddHeaderValue(HttpHeaders.CONTENT_LENGTH, contentLength, true); + } } public long getContentLengthLong() { diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpSession.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpSession.java index 7b10ea96ae38..1881d802bc91 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpSession.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpSession.java @@ -45,7 +45,6 @@ * @author Vedran Pavic * @since 1.0.2 */ -@SuppressWarnings("deprecation") public class MockHttpSession implements HttpSession { /** @@ -240,6 +239,12 @@ public boolean isNew() { return this.isNew; } + @Override + public Accessor getAccessor() { + return sessionConsumer -> sessionConsumer.accept(MockHttpSession.this); + } + + /** * Serialize the attributes of this session into an object that can be * turned into a byte array with standard Java serialization. diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletResponse.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletResponse.java index cbcff8f57361..c51560554e31 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletResponse.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletResponse.java @@ -319,8 +319,10 @@ public String getContentAsString(Charset fallbackCharset) throws UnsupportedEnco @Override public void setContentLength(int contentLength) { - this.contentLength = contentLength; - doAddHeaderValue(HttpHeaders.CONTENT_LENGTH, contentLength, true); + if (!this.committed) { + this.contentLength = contentLength; + doAddHeaderValue(HttpHeaders.CONTENT_LENGTH, contentLength, true); + } } /** @@ -334,8 +336,10 @@ public int getContentLength() { @Override public void setContentLengthLong(long contentLength) { - this.contentLength = contentLength; - doAddHeaderValue(HttpHeaders.CONTENT_LENGTH, contentLength, true); + if (!this.committed) { + this.contentLength = contentLength; + doAddHeaderValue(HttpHeaders.CONTENT_LENGTH, contentLength, true); + } } public long getContentLengthLong() { @@ -636,7 +640,7 @@ public void sendRedirect(String url) throws IOException { sendRedirect(url, HttpServletResponse.SC_MOVED_TEMPORARILY, true); } - // @Override - on Servlet 6.1 + @Override public void sendRedirect(String url, int sc, boolean clearBuffer) throws IOException { Assert.state(!isCommitted(), "Cannot send redirect - response is already committed"); Assert.notNull(url, "Redirect URL must not be null"); diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpSession.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpSession.java index 0711d3884696..0ec6fae4b8ee 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpSession.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpSession.java @@ -45,7 +45,6 @@ * @author Vedran Pavic * @since 1.0.2 */ -@SuppressWarnings("deprecation") public class MockHttpSession implements HttpSession { /** @@ -239,6 +238,11 @@ public boolean isNew() { return this.isNew; } + @Override + public Accessor getAccessor() { + return sessionConsumer -> sessionConsumer.accept(MockHttpSession.this); + } + /** * Serialize the attributes of this session into an object that can be * turned into a byte array with standard Java serialization. From 1164ac3079b0d76b212257a5c9e015697626c123 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Mon, 25 Nov 2024 15:56:25 +0000 Subject: [PATCH 008/701] ContentCachingRequestWrapper requires cacheLimit Closes gh-33914 --- .../util/ContentCachingRequestWrapper.java | 31 ++++---- .../ContentCachingRequestWrapperTests.java | 70 +++++++++---------- 2 files changed, 49 insertions(+), 52 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java b/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java index c98ab1b9ac89..184ef4a51a17 100644 --- a/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java +++ b/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java @@ -71,31 +71,34 @@ public class ContentCachingRequestWrapper extends HttpServletRequestWrapper { /** * Create a new ContentCachingRequestWrapper for the given servlet request. * @param request the original servlet request + * @param cacheLimit the maximum number of bytes to cache per request; + * no limit is set if the value is 0 or less. It is recommended to set a + * concrete limit in order to avoid using too much memory. + * @since 4.3.6 + * @see #handleContentOverflow(int) */ - public ContentCachingRequestWrapper(HttpServletRequest request) { + public ContentCachingRequestWrapper(HttpServletRequest request, int cacheLimit) { super(request); int contentLength = request.getContentLength(); - this.cachedContent = (contentLength > 0) ? new FastByteArrayOutputStream(contentLength) : new FastByteArrayOutputStream(); - this.contentCacheLimit = null; + this.cachedContent = (contentLength > 0 ? + new FastByteArrayOutputStream((cacheLimit > 0 ? Math.min(contentLength, cacheLimit) : contentLength)) : + new FastByteArrayOutputStream()); + this.contentCacheLimit = (cacheLimit > 0 ? cacheLimit : null); } /** * Create a new ContentCachingRequestWrapper for the given servlet request. * @param request the original servlet request - * @param contentCacheLimit the maximum number of bytes to cache per request - * @since 4.3.6 - * @see #handleContentOverflow(int) + * @deprecated in favor of {@link #ContentCachingRequestWrapper(HttpServletRequest, int)} + * in order to explicitly choose the cache limit */ - public ContentCachingRequestWrapper(HttpServletRequest request, int contentCacheLimit) { + @Deprecated(since = "6.2.1", forRemoval = true) + public ContentCachingRequestWrapper(HttpServletRequest request) { super(request); int contentLength = request.getContentLength(); - if (contentLength > 0) { - this.cachedContent = new FastByteArrayOutputStream(Math.min(contentLength, contentCacheLimit)); - } - else { - this.cachedContent = new FastByteArrayOutputStream(); - } - this.contentCacheLimit = contentCacheLimit; + this.cachedContent = (contentLength > 0 ? + new FastByteArrayOutputStream(contentLength) : new FastByteArrayOutputStream()); + this.contentCacheLimit = null; } diff --git a/spring-web/src/test/java/org/springframework/web/util/ContentCachingRequestWrapperTests.java b/spring-web/src/test/java/org/springframework/web/util/ContentCachingRequestWrapperTests.java index d955c70010e5..20bc6cb8123e 100644 --- a/spring-web/src/test/java/org/springframework/web/util/ContentCachingRequestWrapperTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/ContentCachingRequestWrapperTests.java @@ -16,15 +16,13 @@ package org.springframework.web.util; -import java.io.UnsupportedEncodingException; -import java.nio.charset.StandardCharsets; - import org.junit.jupiter.api.Test; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.web.testfixture.servlet.MockHttpServletRequest; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; @@ -38,68 +36,59 @@ class ContentCachingRequestWrapperTests { protected static final String FORM_CONTENT_TYPE = MediaType.APPLICATION_FORM_URLENCODED_VALUE; - protected static final String CHARSET = StandardCharsets.UTF_8.name(); - - protected static final String GET = HttpMethod.GET.name(); - - protected static final String POST = HttpMethod.POST.name(); - - protected static final int CONTENT_CACHE_LIMIT = 3; - @Test - void cachedContentToByteArrayWithNoRead() throws Exception { - ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(createGetRequest("Hello")); + void cachedContentToByteArrayWithNoRead() { + ContentCachingRequestWrapper wrapper = createGetRequest("Hello", -1); assertThat(wrapper.getContentAsByteArray()).isEmpty(); } @Test - void cachedContentToStringWithNoRead() throws Exception { - ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(createGetRequest("Hello")); + void cachedContentToStringWithNoRead() { + ContentCachingRequestWrapper wrapper = createGetRequest("Hello", -1); assertThat(wrapper.getContentAsString()).isEqualTo(""); } @Test void cachedContentToByteArray() throws Exception { - ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(createGetRequest("Hello World")); + ContentCachingRequestWrapper wrapper = createGetRequest("Hello World", -1); byte[] response = wrapper.getInputStream().readAllBytes(); assertThat(wrapper.getContentAsByteArray()).isEqualTo(response); } @Test void cachedContentToString() throws Exception { - ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(createGetRequest("Hello World")); + ContentCachingRequestWrapper wrapper = createGetRequest("Hello World", -1); byte[] response = wrapper.getInputStream().readAllBytes(); - assertThat(wrapper.getContentAsString()).isEqualTo(new String(response, CHARSET)); + assertThat(wrapper.getContentAsString()).isEqualTo(new String(response, UTF_8)); } @Test void cachedContentToByteArrayWithLimit() throws Exception { - ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(createGetRequest("Hello World"), CONTENT_CACHE_LIMIT); + ContentCachingRequestWrapper wrapper = createGetRequest("Hello World", 3); byte[] response = wrapper.getInputStream().readAllBytes(); - assertThat(response).isEqualTo("Hello World".getBytes(CHARSET)); - assertThat(wrapper.getContentAsByteArray()).isEqualTo("Hel".getBytes(CHARSET)); + assertThat(response).isEqualTo("Hello World".getBytes(UTF_8)); + assertThat(wrapper.getContentAsByteArray()).isEqualTo("Hel".getBytes(UTF_8)); } @Test void cachedContentToStringWithLimit() throws Exception { - ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(createGetRequest("Hello World"), CONTENT_CACHE_LIMIT); + ContentCachingRequestWrapper wrapper = createGetRequest("Hello World", 3); byte[] response = wrapper.getInputStream().readAllBytes(); - assertThat(response).isEqualTo("Hello World".getBytes(CHARSET)); - assertThat(wrapper.getContentAsString()).isEqualTo(new String("Hel".getBytes(CHARSET), CHARSET)); + assertThat(response).isEqualTo("Hello World".getBytes(UTF_8)); + assertThat(wrapper.getContentAsString()).isEqualTo(new String("Hel".getBytes(UTF_8), UTF_8)); } @Test - void shouldNotAllocateMoreThanCacheLimit() throws Exception { - ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(createGetRequest("Hello World"), CONTENT_CACHE_LIMIT); - assertThat(wrapper).extracting("cachedContent.initialBlockSize").isEqualTo(CONTENT_CACHE_LIMIT); + void shouldNotAllocateMoreThanCacheLimit() { + ContentCachingRequestWrapper wrapper = createGetRequest("Hello World", 3); + assertThat(wrapper).extracting("cachedContent.initialBlockSize").isEqualTo(3); } @Test - void cachedContentWithOverflow() throws Exception { - ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper( - createGetRequest("Hello World"), CONTENT_CACHE_LIMIT) { + void cachedContentWithOverflow() { + ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(createGetRequest("Hello World"), 3) { @Override protected void handleContentOverflow(int contentCacheLimit) { throw new IllegalStateException(String.valueOf(contentCacheLimit)); @@ -117,7 +106,7 @@ void requestParams() throws Exception { request.setParameter("first", "value"); request.setParameter("second", "foo", "bar"); - ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(request); + ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(request, -1); // getting request parameters will consume the request body assertThat(wrapper.getParameterMap()).isNotEmpty(); assertThat(new String(wrapper.getContentAsByteArray())).isEqualTo("first=value&second=foo&second=bar"); @@ -131,25 +120,30 @@ void inputStreamFormPostRequest() throws Exception { request.setParameter("first", "value"); request.setParameter("second", "foo", "bar"); - ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(request); + ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(request, -1); byte[] response = wrapper.getInputStream().readAllBytes(); assertThat(wrapper.getContentAsByteArray()).isEqualTo(response); } - private MockHttpServletRequest createGetRequest(String content) throws UnsupportedEncodingException { + private ContentCachingRequestWrapper createGetRequest(String content, int cacheLimit) { + return new ContentCachingRequestWrapper(createGetRequest(content), cacheLimit); + } + + + private MockHttpServletRequest createGetRequest(String content) { MockHttpServletRequest request = new MockHttpServletRequest(); - request.setMethod(GET); - request.setCharacterEncoding(CHARSET); - request.setContent(content.getBytes(CHARSET)); + request.setMethod(HttpMethod.GET.name()); + request.setCharacterEncoding(UTF_8); + request.setContent(content.getBytes(UTF_8)); return request; } private MockHttpServletRequest createPostRequest() { MockHttpServletRequest request = new MockHttpServletRequest(); - request.setMethod(POST); + request.setMethod(HttpMethod.POST.name()); request.setContentType(FORM_CONTENT_TYPE); - request.setCharacterEncoding(CHARSET); + request.setCharacterEncoding(UTF_8.name()); return request; } From 1fd0b8730be944075cf06326d92c1cee98079bf7 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 26 Nov 2024 18:26:54 +0000 Subject: [PATCH 009/701] Improve Javadoc of FragmentsRendering --- .../web/reactive/result/view/Fragment.java | 4 +- .../result/view/FragmentsRendering.java | 46 ++++++++++--------- .../web/servlet/view/FragmentsRendering.java | 44 ++++++++++-------- 3 files changed, 49 insertions(+), 45 deletions(-) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/Fragment.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/Fragment.java index f835b3f6805e..f999f5ae3226 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/Fragment.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/Fragment.java @@ -26,9 +26,7 @@ import org.springframework.util.CollectionUtils; /** - * Container for a model and a view for use with {@link FragmentsRendering} and - * multi-view rendering. For full page rendering with a single model and view, - * use {@link Rendering}. + * Container for a model and a view pair. For use with {@link FragmentsRendering}. * * @author Rossen Stoyanchev * @since 6.2 diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/FragmentsRendering.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/FragmentsRendering.java index 1a7ad5bcc142..29728917acc3 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/FragmentsRendering.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/FragmentsRendering.java @@ -32,13 +32,16 @@ import org.springframework.util.Assert; /** - * Public API for HTML rendering of a collection of fragments each with a view - * and independent model. For use with frontends technologies such as + * Public API to render HTML fragments. A fragment is a portion of an HTML page. + * Normally HTML is rendered with a single model and view. This API allows + * using multiple model and view pairs, one for each HTML fragment. + * + *

For use with frontends technologies such as * htmx where multiple page fragments may be - * rendered in one response. Supported as a return value from Spring WebFlux - * controller methods. + * rendered in one response. * - *

For full page rendering with a single model and view, use {@link Rendering}. + *

Supported as a return value from annotated controller methods. + * For full page rendering with a single model and view, use {@link Rendering}. * * @author Rossen Stoyanchev * @since 6.2 @@ -63,10 +66,10 @@ public interface FragmentsRendering { /** - * Create a builder and add a fragment with a view name and a model. - * @param viewName the name of the view for the fragment - * @param model attributes for the fragment in addition to model - * attributes inherited from the model for the request + * Create a builder with one HTML fragment. + * @param viewName the view name for the fragment + * @param model attributes for the fragment, in addition to attributes from the + * shared model for the request * @return this builder */ static Builder with(String viewName, Map model) { @@ -74,8 +77,8 @@ static Builder with(String viewName, Map model) { } /** - * Variant of {@link #with(String, Map)} with a view name only, but also - * inheriting model attributes from the shared model for the request. + * Create a builder with one HTML fragment, also inheriting attributes from + * the shared model for the request. * @param viewName the name of the view for the fragment * @return this builder */ @@ -84,8 +87,8 @@ static Builder with(String viewName) { } /** - * Variant of {@link #with(String, Map)} with a collection of fragments. - * @param fragments the fragments to add; each fragment also inherits model + * Create a builder with multiple HTML fragments. + * @param fragments the fragments to add; each fragment also inherits * attributes from the shared model for the request * @return the created builder */ @@ -94,7 +97,7 @@ static Builder withCollection(Collection fragments) { } /** - * Variant of {@link #with(String, Map)} with a {@link Publisher} of fragments. + * Create a builder with a {@link Publisher} of fragments. * @param fragmentsPublisher the fragments to add; each fragment also * inherits model attributes from the shared model for the request * @return the created builder @@ -148,25 +151,24 @@ interface Builder { Builder headers(Consumer headersConsumer); /** - * Add a fragment with a view name and a model. - * @param viewName the name of the view for the fragment - * @param model attributes for the fragment in addition to model - * attributes inherited from the model for the request + * Add an HTML fragment. + * @param viewName the view name for the fragment + * @param model fragment attributes in addition to attributes from the + * shared model for the request * @return this builder */ Builder fragment(String viewName, Map model); /** - * Variant of {@link #fragment(String, Map)} with a view name only, where - * the fragment model also inherits model attributes from the shared + * Add an HTML fragment. The fragment will use attributes from the shared * model for the request. - * @param viewName the name of the view for the fragment + * @param viewName the view name for the fragment * @return this builder */ Builder fragment(String viewName); /** - * Variant of {@link #fragment(String, Map)} with a {@link Fragment}. + * Add an HTML fragment. * @param fragment the fragment to add * @return this builder */ diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/FragmentsRendering.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/FragmentsRendering.java index 8c3257c800df..739c20ee846e 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/FragmentsRendering.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/FragmentsRendering.java @@ -27,11 +27,15 @@ import org.springframework.web.servlet.SmartView; /** - * Public API for HTML rendering of a collection of fragments each with a view - * and independent model. For use with frontends technologies such as + * Public API to render HTML fragments. A fragment is a portion of an HTML page. + * Normally HTML is rendered with a single model and view. This API allows + * using multiple model and view pairs, one for each HTML fragment. + * + *

For use with frontends technologies such as * htmx where multiple page fragments may be - * rendered in one response. Supported as a return value from Spring MVC - * controller methods. + * rendered in one response. + * + *

Supported as a return value from controller methods. * * @author Rossen Stoyanchev * @since 6.2 @@ -51,10 +55,10 @@ public interface FragmentsRendering extends SmartView { /** - * Create a builder and add a fragment with a view name and a model. - * @param viewName the name of the view for the fragment - * @param model attributes for the fragment in addition to model - * attributes inherited from the shared model for the request + * Create a builder with one HTML fragment. + * @param viewName the view name for the fragment + * @param model attributes for the fragment, in addition to attributes from the + * shared model for the request * @return the created builder */ static Builder with(String viewName, Map model) { @@ -62,8 +66,8 @@ static Builder with(String viewName, Map model) { } /** - * Variant of {@link #with(String, Map)} with a view name only, but also - * inheriting model attributes from the shared model for the request. + * Create a builder with one HTML fragment, also inheriting attributes from + * the shared model for the request. * @param viewName the name of the view for the fragment * @return the created builder */ @@ -72,8 +76,8 @@ static Builder with(String viewName) { } /** - * Variant of {@link #with(String, Map)} with a collection of fragments. - * @param fragments the fragments to add; each fragment also inherits model + * Create a builder with multiple HTML fragments. + * @param fragments the fragments to add; each fragment also inherits * attributes from the shared model for the request * @return the created builder */ @@ -111,32 +115,32 @@ interface Builder { Builder headers(Consumer headersConsumer); /** - * Add a fragment with a view name and a model. + * Add an HTML fragment. * @param viewName the name of the view for the fragment - * @param model attributes for the fragment in addition to model - * attributes inherited from the shared model for the request + * @param model fragment attributes in addition to attributes from the + * shared model for the request * @return this builder */ Builder fragment(String viewName, Map model); /** - * Variant of {@link #fragment(String, Map)} with a view name only, but - * also inheriting model attributes from the shared model for the request. + * Add an HTML fragment. The fragment will use attributes from the shared + * model for the request. * @param viewName the name of the view for the fragment * @return this builder */ Builder fragment(String viewName); /** - * Variant of {@link #fragment(String, Map)} with a {@link ModelAndView}. - * @param fragment the fragment to add; the fragment also inherits model + * Add an HTML fragment. + * @param fragment the fragment to add; the fragment also inherits * attributes from the shared model for the request * @return this builder */ Builder fragment(ModelAndView fragment); /** - * Variant of {@link #fragment(String, Map)} with a collection of {@link ModelAndView}s. + * Add multiple HTML fragments. * @param fragments the fragments to add; each fragment also inherits model * attributes from the shared model for the request * @return this builder From 81ea35c72690434c06a0d196180e64373663063d Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Wed, 27 Nov 2024 11:21:19 +0000 Subject: [PATCH 010/701] Update method names in FragmentsRendering Closes gh-33974 --- .../modules/ROOT/pages/web/webflux-view.adoc | 4 +- .../pages/web/webmvc-view/mvc-fragments.adoc | 4 +- .../DefaultFragmentsRenderingBuilder.java | 18 ++-- .../result/view/FragmentsRendering.java | 101 +++++++++++++++--- .../view/ViewResolutionResultHandler.java | 4 +- ...gmentViewResolutionResultHandlerTests.java | 4 +- .../ModelAndViewMethodReturnValueHandler.java | 2 +- ...ResponseBodyEmitterReturnValueHandler.java | 2 +- .../web/servlet/view/FragmentsRendering.java | 46 +++++++- ...lAndViewMethodReturnValueHandlerTests.java | 2 +- .../view/DefaultFragmentsRenderingTests.java | 3 +- 11 files changed, 154 insertions(+), 36 deletions(-) diff --git a/framework-docs/modules/ROOT/pages/web/webflux-view.adoc b/framework-docs/modules/ROOT/pages/web/webflux-view.adoc index b37208c527ae..89b524bfb143 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux-view.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux-view.adoc @@ -466,7 +466,7 @@ Java:: ---- @GetMapping FragmentsRendering handle() { - return FragmentsRendering.with("posts").fragment("comments").build(); + return FragmentsRendering.fragment("posts").fragment("comments").build(); } ---- @@ -476,7 +476,7 @@ Kotlin:: ---- @GetMapping fun handle(): FragmentsRendering { - return FragmentsRendering.with("posts").fragment("comments").build() + return FragmentsRendering.fragment("posts").fragment("comments").build() } ---- ====== diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-fragments.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-fragments.adoc index 45e4a57adc4e..953883659a9c 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-fragments.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc-view/mvc-fragments.adoc @@ -48,7 +48,7 @@ Java:: ---- @GetMapping FragmentsRendering handle() { - return FragmentsRendering.with("posts").fragment("comments").build(); + return FragmentsRendering.fragment("posts").fragment("comments").build(); } ---- @@ -58,7 +58,7 @@ Kotlin:: ---- @GetMapping fun handle(): FragmentsRendering { - return FragmentsRendering.with("posts").fragment("comments").build() + return FragmentsRendering.fragment("posts").fragment("comments").build() } ---- ====== diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/DefaultFragmentsRenderingBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/DefaultFragmentsRenderingBuilder.java index 5eeb4094a0c6..cbdb6c9dd413 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/DefaultFragmentsRenderingBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/DefaultFragmentsRenderingBuilder.java @@ -49,8 +49,8 @@ class DefaultFragmentsRenderingBuilder implements FragmentsRendering.Builder { @Nullable private HttpHeaders headers; - DefaultFragmentsRenderingBuilder(Collection fragments) { - this.fragmentsCollection = new ArrayList<>(fragments); + DefaultFragmentsRenderingBuilder() { + this.fragmentsCollection = null; this.fragmentsFlux = null; } @@ -85,13 +85,13 @@ private HttpHeaders initHeaders() { } @Override - public FragmentsRendering.Builder fragment(String viewName, Map model) { - return fragment(Fragment.create(viewName, model)); + public FragmentsRendering.Builder fragment(String viewName) { + return fragment(Fragment.create(viewName)); } @Override - public FragmentsRendering.Builder fragment(String viewName) { - return fragment(Fragment.create(viewName)); + public FragmentsRendering.Builder fragment(String viewName, Map model) { + return fragment(Fragment.create(viewName, model)); } @Override @@ -100,6 +100,12 @@ public FragmentsRendering.Builder fragment(Fragment fragment) { return this; } + @Override + public FragmentsRendering.Builder fragments(Collection fragments) { + initFragmentsCollection().addAll(fragments); + return this; + } + private Collection initFragmentsCollection() { if (this.fragmentsCollection == null) { this.fragmentsCollection = new ArrayList<>(); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/FragmentsRendering.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/FragmentsRendering.java index 29728917acc3..0f821a11789d 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/FragmentsRendering.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/FragmentsRendering.java @@ -17,7 +17,6 @@ package org.springframework.web.reactive.result.view; import java.util.Collection; -import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -65,15 +64,62 @@ public interface FragmentsRendering { Flux fragments(); + /** + * Create a builder with one HTML fragment, also inheriting attributes from + * the shared model for the request. + * @param viewName the name of the view for the fragment + * @return this builder + * @since 6.2.1 + */ + static Builder fragment(String viewName) { + return new DefaultFragmentsRenderingBuilder().fragment(viewName); + } + /** * Create a builder with one HTML fragment. * @param viewName the view name for the fragment * @param model attributes for the fragment, in addition to attributes from the * shared model for the request * @return this builder + * @since 6.2.1 */ - static Builder with(String viewName, Map model) { - return withCollection(List.of(Fragment.create(viewName, model))); + static Builder fragment(String viewName, Map model) { + return new DefaultFragmentsRenderingBuilder().fragment(viewName, model); + } + + /** + * Create a builder with multiple HTML fragments. + * @param fragments the fragments to add; each fragment also inherits + * attributes from the shared model for the request + * @return the created builder + * @since 6.2.1 + */ + static Builder fragments(Collection fragments) { + return new DefaultFragmentsRenderingBuilder().fragments(fragments); + } + + /** + * Create a builder with a {@link Publisher} of fragments. + * @param fragmentsPublisher the fragments to add; each fragment also + * inherits model attributes from the shared model for the request + * @return the created builder + * @since 6.2.1 + */ + static

> Builder fragmentsPublisher(P fragmentsPublisher) { + return new DefaultFragmentsRenderingBuilder(fragmentsPublisher); + } + + /** + * Variant of {@link #fragmentsPublisher(Publisher)} that allows using any + * producer that can be resolved to {@link Publisher} via + * {@link ReactiveAdapterRegistry}. + * @since 6.2.1 + */ + static Builder fragmentsProducer(Object fragmentsProducer) { + ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(fragmentsProducer.getClass()); + Assert.isTrue(adapter != null, "Unknown producer " + fragmentsProducer.getClass()); + Publisher publisher = adapter.toPublisher(fragmentsProducer); + return fragmentsPublisher(publisher); } /** @@ -81,9 +127,24 @@ static Builder with(String viewName, Map model) { * the shared model for the request. * @param viewName the name of the view for the fragment * @return this builder + * @deprecated in favor of {@link #fragment(String)} */ + @Deprecated(since = "6.2.1", forRemoval = true) static Builder with(String viewName) { - return withCollection(List.of(Fragment.create(viewName))); + return fragment(viewName); + } + + /** + * Create a builder with one HTML fragment. + * @param viewName the view name for the fragment + * @param model attributes for the fragment, in addition to attributes from the + * shared model for the request + * @return this builder + * @deprecated in favor of {@link #fragment(String, Map)} + */ + @Deprecated(since = "6.2.1", forRemoval = true) + static Builder with(String viewName, Map model) { + return fragment(viewName, model); } /** @@ -91,9 +152,11 @@ static Builder with(String viewName) { * @param fragments the fragments to add; each fragment also inherits * attributes from the shared model for the request * @return the created builder + * @deprecated in favor of {@link #fragments(Collection)} */ + @Deprecated(since = "6.2.1", forRemoval = true) static Builder withCollection(Collection fragments) { - return new DefaultFragmentsRenderingBuilder(fragments); + return fragments(fragments); } /** @@ -101,24 +164,22 @@ static Builder withCollection(Collection fragments) { * @param fragmentsPublisher the fragments to add; each fragment also * inherits model attributes from the shared model for the request * @return the created builder + * @deprecated in favor of {@link #fragmentsPublisher(Publisher)} */ + @Deprecated(since = "6.2.1", forRemoval = true) static

> Builder withPublisher(P fragmentsPublisher) { - return new DefaultFragmentsRenderingBuilder(fragmentsPublisher); + return fragmentsPublisher(fragmentsPublisher); } /** - * Variant of {@link #withPublisher(Publisher)} that allows using any + * Variant of {@link #fragmentsPublisher(Publisher)} that allows using any * producer that can be resolved to {@link Publisher} via * {@link ReactiveAdapterRegistry}. + * @deprecated in favor of {@link #fragmentsProducer(Object)} */ + @Deprecated(since = "6.2.1", forRemoval = true) static Builder withProducer(Object fragmentsProducer) { - return new DefaultFragmentsRenderingBuilder(adaptProducer(fragmentsProducer)); - } - - private static Publisher adaptProducer(Object producer) { - ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(producer.getClass()); - Assert.isTrue(adapter != null, "Unknown producer " + producer.getClass()); - return adapter.toPublisher(producer); + return fragmentsProducer(fragmentsProducer); } @@ -169,11 +230,21 @@ interface Builder { /** * Add an HTML fragment. - * @param fragment the fragment to add + * @param fragment the fragment to add; the fragment also inherits + * attributes from the shared model for the request * @return this builder */ Builder fragment(Fragment fragment); + /** + * Add HTML fragments. + * @param fragments the fragments to add; each fragment also inherits + * attributes from the shared model for the request + * @return this builder + * @since 6.2.1 + */ + Builder fragments(Collection fragments); + /** * Build the {@link FragmentsRendering} instance. */ diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java index 51866ba9dce7..f492dabe69f7 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/ViewResolutionResultHandler.java @@ -208,7 +208,7 @@ public Mono handleResult(ServerWebExchange exchange, HandlerResult result) if (adapter != null) { if (adapter.isMultiValue()) { valueMono = (result.getReturnValue() != null ? - Mono.just(FragmentsRendering.withPublisher(adapter.toPublisher(result.getReturnValue())).build()) : + Mono.just(FragmentsRendering.fragmentsPublisher(adapter.toPublisher(result.getReturnValue())).build()) : Mono.empty()); valueType = ResolvableType.forClass(FragmentsRendering.class); @@ -242,7 +242,7 @@ public Mono handleResult(ServerWebExchange exchange, HandlerResult result) } if (Collection.class.isAssignableFrom(clazz)) { - returnValue = FragmentsRendering.withCollection((Collection) returnValue).build(); + returnValue = FragmentsRendering.fragments((Collection) returnValue).build(); clazz = FragmentsRendering.class; } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/FragmentViewResolutionResultHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/FragmentViewResolutionResultHandlerTests.java index 40122c3f7a21..09fb2b21541d 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/FragmentViewResolutionResultHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/FragmentViewResolutionResultHandlerTests.java @@ -64,9 +64,9 @@ public class FragmentViewResolutionResultHandlerTests { static Stream arguments() { Flux fragmentFlux = Flux.just(fragment1, fragment2).subscribeOn(Schedulers.boundedElastic()); return Stream.of( - Arguments.of(FragmentsRendering.withPublisher(fragmentFlux).build(), + Arguments.of(FragmentsRendering.fragmentsPublisher(fragmentFlux).build(), on(Handler.class).resolveReturnType(FragmentsRendering.class)), - Arguments.of(FragmentsRendering.withCollection(List.of(fragment1, fragment2)).build(), + Arguments.of(FragmentsRendering.fragments(List.of(fragment1, fragment2)).build(), on(Handler.class).resolveReturnType(FragmentsRendering.class)), Arguments.of(fragmentFlux, on(Handler.class).resolveReturnType(Flux.class, Fragment.class)), diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java index 11dfd8fcdddf..9d2b199040cd 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandler.java @@ -92,7 +92,7 @@ public void handleReturnValue(@Nullable Object returnValue, MethodParameter retu } if (returnValue instanceof Collection mavs) { - returnValue = FragmentsRendering.with((Collection) mavs).build(); + returnValue = FragmentsRendering.fragments((Collection) mavs).build(); } if (returnValue instanceof FragmentsRendering rendering) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java index 288f644ae963..92a571ac449a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java @@ -393,7 +393,7 @@ public void handle(ModelAndView modelAndView) throws IOException { FragmentHttpServletResponse fragmentResponse = new FragmentHttpServletResponse(this.response, this.charset); - FragmentsRendering render = FragmentsRendering.with(List.of(modelAndView)).build(); + FragmentsRendering render = FragmentsRendering.fragments(List.of(modelAndView)).build(); render.resolveNestedViews(this::resolveViewName, this.locale); render.render(modelAndView.getModel(), this.request, fragmentResponse); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/FragmentsRendering.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/FragmentsRendering.java index 739c20ee846e..8c0a4bc38ca3 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/FragmentsRendering.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/FragmentsRendering.java @@ -54,25 +54,63 @@ public interface FragmentsRendering extends SmartView { HttpHeaders headers(); + /** + * Create a builder with one HTML fragment, also inheriting attributes from + * the shared model for the request. + * @param viewName the name of the view for the fragment + * @return the created builder + * @since 6.2.1 + */ + static Builder fragment(String viewName) { + return new DefaultFragmentsRenderingBuilder().fragment(viewName); + } + /** * Create a builder with one HTML fragment. * @param viewName the view name for the fragment * @param model attributes for the fragment, in addition to attributes from the * shared model for the request * @return the created builder + * @since 6.2.1 */ - static Builder with(String viewName, Map model) { + static Builder fragment(String viewName, Map model) { return new DefaultFragmentsRenderingBuilder().fragment(viewName, model); } + /** + * Create a builder with multiple HTML fragments. + * @param fragments the fragments to add; each fragment also inherits + * attributes from the shared model for the request + * @return the created builder + * @since 6.2.1 + */ + static Builder fragments(Collection fragments) { + return new DefaultFragmentsRenderingBuilder().fragments(fragments); + } + /** * Create a builder with one HTML fragment, also inheriting attributes from * the shared model for the request. * @param viewName the name of the view for the fragment * @return the created builder + * @deprecated in favor of {@link #fragment(String)} */ + @Deprecated(since = "6.2.1", forRemoval = true) static Builder with(String viewName) { - return new DefaultFragmentsRenderingBuilder().fragment(viewName); + return fragment(viewName); + } + + /** + * Create a builder with one HTML fragment. + * @param viewName the view name for the fragment + * @param model attributes for the fragment, in addition to attributes from the + * shared model for the request + * @return the created builder + * @deprecated in favor of {@link #fragment(String, Map)} + */ + @Deprecated(since = "6.2.1", forRemoval = true) + static Builder with(String viewName, Map model) { + return fragment(viewName, model); } /** @@ -80,9 +118,11 @@ static Builder with(String viewName) { * @param fragments the fragments to add; each fragment also inherits * attributes from the shared model for the request * @return the created builder + * @deprecated in favor of {@link #fragments(Collection)} */ + @Deprecated(since = "6.2.1", forRemoval = true) static Builder with(Collection fragments) { - return new DefaultFragmentsRenderingBuilder().fragments(fragments); + return fragments(fragments); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandlerTests.java index d48b682cc7b6..56e47c7478f2 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandlerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ModelAndViewMethodReturnValueHandlerTests.java @@ -91,7 +91,7 @@ void handleViewInstance() throws Exception { @Test void handleFragmentsRendering() throws Exception { - FragmentsRendering rendering = FragmentsRendering.with("viewName").build(); + FragmentsRendering rendering = FragmentsRendering.fragment("viewName").build(); handler.handleReturnValue(rendering, returnParamModelAndView, mavContainer, webRequest); assertThat(mavContainer.getView()).isInstanceOf(SmartView.class); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/DefaultFragmentsRenderingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/DefaultFragmentsRenderingTests.java index 0031ddb84c97..160012b6aaac 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/DefaultFragmentsRenderingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/DefaultFragmentsRenderingTests.java @@ -56,7 +56,8 @@ void render() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); - FragmentsRendering view = FragmentsRendering.with("fragment1", Map.of("foo", "Foo")) + FragmentsRendering view = FragmentsRendering + .fragment("fragment1", Map.of("foo", "Foo")) .fragment("fragment2", Map.of("bar", "Bar")) .header("headerName", "headerValue") .build(); From c213724a470924532067642f9b2fc152e57dbfba Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Thu, 28 Nov 2024 15:55:53 +0100 Subject: [PATCH 011/701] Document Servlet PushBuilder API deprecation See gh-33918 --- .../web/webmvc/mvc-controller/ann-methods/arguments.adoc | 3 +-- .../modules/ROOT/pages/web/webmvc/mvc-http2.adoc | 8 ++------ .../ServletRequestMethodArgumentResolverTests.java | 4 +++- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/arguments.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/arguments.adoc index 4e3b30ea3c09..fb4e6c5ed8d1 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/arguments.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-methods/arguments.adoc @@ -30,8 +30,7 @@ and others) and is equivalent to `required=false`. | `jakarta.servlet.http.PushBuilder` | Servlet 4.0 push builder API for programmatic HTTP/2 resource pushes. - Note that, per the Servlet specification, the injected `PushBuilder` instance can be null if the client - does not support that HTTP/2 feature. + Note that this API has been deprecated as of Servlet 6.1. | `java.security.Principal` | Currently authenticated user -- possibly a specific `Principal` implementation class if known. diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-http2.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-http2.adoc index da03fba9b4a1..e5e19ea705a0 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-http2.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-http2.adoc @@ -4,12 +4,8 @@ [.small]#xref:web/webflux/http2.adoc[See equivalent in the Reactive stack]# -Servlet 4 containers are required to support HTTP/2, and Spring Framework 5 is compatible -with Servlet API 4. From a programming model perspective, there is nothing specific that +Servlet 4 containers are required to support HTTP/2, and Spring Framework requires +Servlet API 6.1. From a programming model perspective, there is nothing specific that applications need to do. However, there are considerations related to server configuration. For more details, see the {spring-framework-wiki}/HTTP-2-support[HTTP/2 wiki page]. - -The Servlet API does expose one construct related to HTTP/2. You can use the -`jakarta.servlet.http.PushBuilder` to proactively push resources to clients, and it -is supported as a xref:web/webmvc/mvc-controller/ann-methods/arguments.adoc[method argument] to `@RequestMapping` methods. diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolverTests.java index fa591d7ccbeb..928fd5f19e43 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolverTests.java @@ -69,6 +69,7 @@ class ServletRequestMethodArgumentResolverTests { @BeforeEach + @SuppressWarnings("deprecation") void setup() throws Exception { resolver = new ServletRequestMethodArgumentResolver(); mavContainer = new ModelAndViewContainer(); @@ -241,6 +242,7 @@ void httpMethod() throws Exception { } @Test + @SuppressWarnings("deprecation") void pushBuilder() throws Exception { final PushBuilder pushBuilder = mock(); servletRequest = new MockHttpServletRequest("GET", "") { @@ -259,7 +261,7 @@ public PushBuilder newPushBuilder() { } - @SuppressWarnings("unused") + @SuppressWarnings({"unused", "deprecation"}) public void supportedParams(ServletRequest p0, MultipartRequest p1, HttpSession p2, From 28273b93094e2134dddf077dc91340074749ff75 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Thu, 28 Nov 2024 16:27:17 +0100 Subject: [PATCH 012/701] Add build DSL extension to enable Java preview features This commit adds a DSL Gradle extension for optionally enabling Java preview features in a specific project module. The "--enable-preview" JVM flag will be configured automatically for compile and test tasks where this is applied: ``` springFramework { enableJavaPreviewFeatures = true } ``` See gh-33616 --- buildSrc/README.md | 10 ++++ .../build/ConventionsPlugin.java | 1 + .../build/JavaConventions.java | 32 +++++++------ .../build/SpringFrameworkExtension.java | 46 +++++++++++++++++++ .../build/TestConventions.java | 2 + 5 files changed, 78 insertions(+), 13 deletions(-) create mode 100644 buildSrc/src/main/java/org/springframework/build/SpringFrameworkExtension.java diff --git a/buildSrc/README.md b/buildSrc/README.md index 9e35b5b766cf..260ebcf24eea 100644 --- a/buildSrc/README.md +++ b/buildSrc/README.md @@ -11,6 +11,16 @@ The `org.springframework.build.conventions` plugin applies all conventions to th * Configuring the Kotlin compiler, see `KotlinConventions` * Configuring testing in the build with `TestConventions` +This plugin also provides a DSL extension to optionally enable Java preview features for +compiling and testing sources in a module. This can be applied with the following in a +module build file: + +```groovy +springFramework { + enableJavaPreviewFeatures = true +} +``` + ## Build Plugins diff --git a/buildSrc/src/main/java/org/springframework/build/ConventionsPlugin.java b/buildSrc/src/main/java/org/springframework/build/ConventionsPlugin.java index 34978ba377d2..3e63c16ed42f 100644 --- a/buildSrc/src/main/java/org/springframework/build/ConventionsPlugin.java +++ b/buildSrc/src/main/java/org/springframework/build/ConventionsPlugin.java @@ -36,6 +36,7 @@ public class ConventionsPlugin implements Plugin { @Override public void apply(Project project) { + project.getExtensions().create("springFramework", SpringFrameworkExtension.class); new CheckstyleConventions().apply(project); new JavaConventions().apply(project); new KotlinConventions().apply(project); diff --git a/buildSrc/src/main/java/org/springframework/build/JavaConventions.java b/buildSrc/src/main/java/org/springframework/build/JavaConventions.java index 60b791799f52..60581d44cb98 100644 --- a/buildSrc/src/main/java/org/springframework/build/JavaConventions.java +++ b/buildSrc/src/main/java/org/springframework/build/JavaConventions.java @@ -75,19 +75,25 @@ private void applyJavaCompileConventions(Project project) { toolchain.getVendor().set(JvmVendorSpec.BELLSOFT); toolchain.getLanguageVersion().set(JavaLanguageVersion.of(17)); }); - project.getTasks().withType(JavaCompile.class) - .matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_JAVA_TASK_NAME)) - .forEach(compileTask -> { - compileTask.getOptions().setCompilerArgs(COMPILER_ARGS); - compileTask.getOptions().setEncoding("UTF-8"); - }); - project.getTasks().withType(JavaCompile.class) - .matching(compileTask -> compileTask.getName().equals(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME) - || compileTask.getName().equals("compileTestFixturesJava")) - .forEach(compileTask -> { - compileTask.getOptions().setCompilerArgs(TEST_COMPILER_ARGS); - compileTask.getOptions().setEncoding("UTF-8"); - }); + SpringFrameworkExtension frameworkExtension = project.getExtensions().getByType(SpringFrameworkExtension.class); + project.afterEvaluate(p -> { + p.getTasks().withType(JavaCompile.class) + .matching(compileTask -> compileTask.getName().startsWith(JavaPlugin.COMPILE_JAVA_TASK_NAME)) + .forEach(compileTask -> { + compileTask.getOptions().setCompilerArgs(COMPILER_ARGS); + compileTask.getOptions().getCompilerArgumentProviders().add(frameworkExtension.asArgumentProvider()); + compileTask.getOptions().setEncoding("UTF-8"); + }); + p.getTasks().withType(JavaCompile.class) + .matching(compileTask -> compileTask.getName().startsWith(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME) + || compileTask.getName().equals("compileTestFixturesJava")) + .forEach(compileTask -> { + compileTask.getOptions().setCompilerArgs(TEST_COMPILER_ARGS); + compileTask.getOptions().getCompilerArgumentProviders().add(frameworkExtension.asArgumentProvider()); + compileTask.getOptions().setEncoding("UTF-8"); + }); + + }); } } diff --git a/buildSrc/src/main/java/org/springframework/build/SpringFrameworkExtension.java b/buildSrc/src/main/java/org/springframework/build/SpringFrameworkExtension.java new file mode 100644 index 000000000000..78dae151e45d --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/build/SpringFrameworkExtension.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-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.build; + +import java.util.Collections; +import java.util.List; + +import org.gradle.api.Project; +import org.gradle.api.provider.Property; +import org.gradle.process.CommandLineArgumentProvider; + +public class SpringFrameworkExtension { + + private final Property enableJavaPreviewFeatures; + + public SpringFrameworkExtension(Project project) { + this.enableJavaPreviewFeatures = project.getObjects().property(Boolean.class); + } + + public Property getEnableJavaPreviewFeatures() { + return this.enableJavaPreviewFeatures; + } + + public CommandLineArgumentProvider asArgumentProvider() { + return () -> { + if (getEnableJavaPreviewFeatures().getOrElse(false)) { + return List.of("--enable-preview"); + } + return Collections.emptyList(); + }; + } +} diff --git a/buildSrc/src/main/java/org/springframework/build/TestConventions.java b/buildSrc/src/main/java/org/springframework/build/TestConventions.java index 1283d233765d..715abe0d7022 100644 --- a/buildSrc/src/main/java/org/springframework/build/TestConventions.java +++ b/buildSrc/src/main/java/org/springframework/build/TestConventions.java @@ -67,6 +67,8 @@ private void configureTests(Project project, Test test) { "--add-opens=java.base/java.util=ALL-UNNAMED", "-Xshare:off" ); + test.getJvmArgumentProviders().add(project.getExtensions() + .getByType(SpringFrameworkExtension.class).asArgumentProvider()); } private void configureTestRetryPlugin(Project project, Test test) { From 4f260a451102e613764e99f50b69736ecb315fbc Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Thu, 28 Nov 2024 16:27:28 +0100 Subject: [PATCH 013/701] Add ClassFile implementation for class metadata Prior to this commit, Spring Framework would allow two ways of getting class metadata: * `StandardClassMetadata`, using the Java reflection API * `SimpleMetadataReaderFactory`, using ASM to read the class bytecode This commit adds a new implementation for this feature, this time using the new `ClassFile` API which is taken out of preview in Java 24. See gh-33616 --- spring-core/spring-core.gradle | 6 +- .../classreading/MetadataReaderFactory.java | 24 +- .../MetadataReaderFactoryDelegate.java | 39 +++ .../ClassFileAnnotationMetadata.java | 129 +++++++ .../classreading/ClassFileClassMetadata.java | 315 ++++++++++++++++++ .../classreading/ClassFileMetadataReader.java | 68 ++++ .../ClassFileMetadataReaderFactory.java | 104 ++++++ .../classreading/ClassFileMethodMetadata.java | 162 +++++++++ .../MetadataReaderFactoryDelegate.java | 38 +++ .../type/AbstractAnnotationMetadataTests.java | 33 +- .../SimpleAnnotationMetadataTests.java | 5 +- 11 files changed, 916 insertions(+), 7 deletions(-) create mode 100644 spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactoryDelegate.java create mode 100644 spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationMetadata.java create mode 100644 spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileClassMetadata.java create mode 100644 spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMetadataReader.java create mode 100644 spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMetadataReaderFactory.java create mode 100644 spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMethodMetadata.java create mode 100644 spring-core/src/main/java24/org/springframework/core/type/classreading/MetadataReaderFactoryDelegate.java diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index a08d0e166723..1ea7bd08c54e 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -11,7 +11,7 @@ apply plugin: "kotlin" apply plugin: "kotlinx-serialization" multiRelease { - targetVersions 17, 21 + targetVersions 17, 21, 24 } def javapoetVersion = "1.13.0" @@ -25,6 +25,10 @@ configurations { graalvm } +springFramework { + enableJavaPreviewFeatures = true +} + task javapoetRepackJar(type: ShadowJar) { archiveBaseName = 'spring-javapoet-repack' archiveVersion = javapoetVersion diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java b/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java index 4eddbfa6cd24..0748fdafe074 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -19,12 +19,15 @@ import java.io.IOException; import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.lang.Nullable; /** * Factory interface for {@link MetadataReader} instances. * Allows for caching a MetadataReader per original resource. * * @author Juergen Hoeller + * @author Brian Clozel * @since 2.5 * @see SimpleMetadataReaderFactory * @see CachingMetadataReaderFactory @@ -49,4 +52,23 @@ public interface MetadataReaderFactory { */ MetadataReader getMetadataReader(Resource resource) throws IOException; + /** + * Create a default {@link MetadataReaderFactory} implementation that's suitable + * for the current JVM. + * @return a new factory instance + * @since 7.0 + */ + static MetadataReaderFactory create(@Nullable ResourceLoader resourceLoader) { + return MetadataReaderFactoryDelegate.create(resourceLoader); + } + + /** + * Create a default {@link MetadataReaderFactory} implementation that's suitable + * for the current JVM. + * @return a new factory instance + * @since 7.0 + */ + static MetadataReaderFactory create(@Nullable ClassLoader classLoader) { + return MetadataReaderFactoryDelegate.create(classLoader); + } } diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactoryDelegate.java b/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactoryDelegate.java new file mode 100644 index 000000000000..e24b004b9680 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactoryDelegate.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-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.core.type.classreading; + +import org.springframework.core.io.ResourceLoader; +import org.springframework.lang.Nullable; + +/** + * Internal delegate for instantiating {@link MetadataReaderFactory} implementations. + * For JDK < 24, the {@link SimpleMetadataReaderFactory} is being used. + * + * @author Brian Clozel + * @since 7.0 + * @see MetadataReaderFactory + */ +abstract class MetadataReaderFactoryDelegate { + + static MetadataReaderFactory create(@Nullable ResourceLoader resourceLoader) { + return new SimpleMetadataReaderFactory(resourceLoader); + } + + static MetadataReaderFactory create(@Nullable ClassLoader classLoader) { + return new SimpleMetadataReaderFactory(classLoader); + } +} diff --git a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationMetadata.java b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationMetadata.java new file mode 100644 index 000000000000..7972c9b03f99 --- /dev/null +++ b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationMetadata.java @@ -0,0 +1,129 @@ +/* + * Copyright 2002-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.core.type.classreading; + + +import java.lang.classfile.Annotation; +import java.lang.classfile.AnnotationElement; +import java.lang.classfile.AnnotationValue; +import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.core.annotation.AnnotationFilter; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; + +abstract class ClassFileAnnotationMetadata { + + static MergedAnnotations createMergedAnnotations(String entryName, RuntimeVisibleAnnotationsAttribute annotationAttribute, @Nullable ClassLoader classLoader) { + Set> annotations = new LinkedHashSet<>(4); + annotationAttribute.annotations().forEach(ann -> { + MergedAnnotation mergedAnnotation = createMergedAnnotation(entryName, ann, classLoader); + if (mergedAnnotation != null) { + annotations.add(mergedAnnotation); + } + }); + return MergedAnnotations.of(annotations); + } + + @SuppressWarnings("unchecked") + @Nullable + private static MergedAnnotation createMergedAnnotation(String entryName, Annotation annotation, @Nullable ClassLoader classLoader) { + String typeName = fromTypeDescriptor(annotation.className().stringValue()); + if (AnnotationFilter.PLAIN.matches(typeName)) { + return null; + } + Map attributes = new LinkedHashMap<>(4); + try { + Class annotationType = (Class) ClassUtils.forName(typeName, classLoader); + for (AnnotationElement element : annotation.elements()) { + attributes.put(element.name().stringValue(), readAnnotationValue(element.value(), classLoader)); + } + Map compactedAttributes = (attributes.isEmpty() ? Collections.emptyMap() : attributes); + return MergedAnnotation.of(classLoader, new Source(entryName), annotationType, compactedAttributes); + } + catch (ClassNotFoundException | LinkageError ex) { + return null; + } + } + + private static Object readAnnotationValue(AnnotationValue elementValue, @Nullable ClassLoader classLoader) throws ClassNotFoundException { + switch (elementValue) { + case AnnotationValue.OfArray arrayValue -> { + List rawValues = arrayValue.values(); + List values = new ArrayList<>(rawValues.size()); + for (AnnotationValue arrayEntry : rawValues) { + values.add(readAnnotationValue(arrayEntry, classLoader)); + } + Class elementType = getArrayElementType(values); + return values.toArray((Object[]) Array.newInstance(elementType, rawValues.size())); + } + case AnnotationValue.OfAnnotation annotationValue -> { + return annotationValue.annotation(); + } + case AnnotationValue.OfClass classValue -> { + return fromTypeDescriptor(classValue.className().stringValue()); + } + case AnnotationValue.OfEnum enumValue -> { + return parseEnum(enumValue, classLoader); + } + case AnnotationValue.OfConstant constantValue -> { + return constantValue.resolvedValue(); + } + default -> { + return elementValue; + } + } + } + + private static Class getArrayElementType(List values) { + if (values.isEmpty()) { + return Object.class; + } + Object firstElement = values.getFirst(); + if (firstElement instanceof Enum enumeration) { + return enumeration.getDeclaringClass(); + } + return firstElement.getClass(); + } + + private static String fromTypeDescriptor(String descriptor) { + return descriptor.substring(1, descriptor.length() - 1) + .replace('/', '.'); + } + + @SuppressWarnings("unchecked") + private static > Enum parseEnum(AnnotationValue.OfEnum enumValue, @Nullable ClassLoader classLoader) throws ClassNotFoundException { + String enumClassName = fromTypeDescriptor(enumValue.className().stringValue()); + Class enumClass = (Class) ClassUtils.forName(enumClassName, classLoader); + return Enum.valueOf(enumClass, enumValue.constantName().stringValue()); + } + + record Source(String entryName) { + + } + +} diff --git a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileClassMetadata.java b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileClassMetadata.java new file mode 100644 index 000000000000..d5677a34a6fc --- /dev/null +++ b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileClassMetadata.java @@ -0,0 +1,315 @@ +/* + * Copyright 2002-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.core.type.classreading; + +import java.lang.classfile.AccessFlags; +import java.lang.classfile.ClassModel; +import java.lang.classfile.Interfaces; +import java.lang.classfile.MethodModel; +import java.lang.classfile.Superclass; +import java.lang.classfile.attribute.InnerClassInfo; +import java.lang.classfile.attribute.InnerClassesAttribute; +import java.lang.classfile.attribute.NestHostAttribute; +import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.reflect.AccessFlag; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.MethodMetadata; +import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * {@link AnnotationMetadata} implementation that leverages + * the {@link java.lang.classfile.ClassFile} API. + * @author Brian Clozel + */ +class ClassFileClassMetadata implements AnnotationMetadata { + + private final String className; + + private final AccessFlags accessFlags; + + @Nullable + private final String enclosingClassName; + + @Nullable + private final String superClassName; + + private final boolean independentInnerClass; + + private final Set interfaceNames; + + private final Set memberClassNames; + + private final Set declaredMethods; + + private final MergedAnnotations mergedAnnotations; + + @Nullable + private Set annotationTypes; + + ClassFileClassMetadata(String className, AccessFlags accessFlags, @Nullable String enclosingClassName, + @Nullable String superClassName, boolean independentInnerClass, Set interfaceNames, + Set memberClassNames, Set declaredMethods, MergedAnnotations mergedAnnotations) { + this.className = className; + this.accessFlags = accessFlags; + this.enclosingClassName = enclosingClassName; + this.superClassName = superClassName; + this.independentInnerClass = independentInnerClass; + this.interfaceNames = interfaceNames; + this.memberClassNames = memberClassNames; + this.declaredMethods = declaredMethods; + this.mergedAnnotations = mergedAnnotations; + } + + @Override + public String getClassName() { + return this.className; + } + + @Override + public boolean isInterface() { + return this.accessFlags.has(AccessFlag.INTERFACE); + } + + @Override + public boolean isAnnotation() { + return this.accessFlags.has(AccessFlag.ANNOTATION); + } + + @Override + public boolean isAbstract() { + return this.accessFlags.has(AccessFlag.ABSTRACT); + } + + @Override + public boolean isFinal() { + return this.accessFlags.has(AccessFlag.FINAL); + } + + @Override + public boolean isIndependent() { + return (this.enclosingClassName == null || this.independentInnerClass); + } + + @Override + @Nullable + public String getEnclosingClassName() { + return this.enclosingClassName; + } + + @Override + @Nullable + public String getSuperClassName() { + return this.superClassName; + } + + @Override + public String[] getInterfaceNames() { + return StringUtils.toStringArray(this.interfaceNames); + } + + @Override + public String[] getMemberClassNames() { + return StringUtils.toStringArray(this.memberClassNames); + } + + @Override + public MergedAnnotations getAnnotations() { + return this.mergedAnnotations; + } + + @Override + public Set getAnnotationTypes() { + Set annotationTypes = this.annotationTypes; + if (annotationTypes == null) { + annotationTypes = Collections.unmodifiableSet( + AnnotationMetadata.super.getAnnotationTypes()); + this.annotationTypes = annotationTypes; + } + return annotationTypes; + } + + @Override + public Set getAnnotatedMethods(String annotationName) { + Set result = new LinkedHashSet<>(4); + for (MethodMetadata annotatedMethod : this.declaredMethods) { + if (annotatedMethod.isAnnotated(annotationName)) { + result.add(annotatedMethod); + } + } + return Collections.unmodifiableSet(result); + } + + @Override + public Set getDeclaredMethods() { + return Collections.unmodifiableSet(this.declaredMethods); + } + + + @Override + public boolean equals(@Nullable Object other) { + return (this == other || (other instanceof ClassFileClassMetadata that && this.className.equals(that.className))); + } + + @Override + public int hashCode() { + return this.className.hashCode(); + } + + @Override + public String toString() { + return this.className; + } + + + static ClassFileClassMetadata of(ClassModel classModel, @Nullable ClassLoader classLoader) { + Builder builder = new Builder(classLoader); + builder.classEntry(classModel.thisClass()); + String currentClassName = classModel.thisClass().name().stringValue(); + classModel.elementStream().forEach(classElement -> { + switch (classElement) { + case AccessFlags flags -> { + builder.accessFlags(flags); + } + case NestHostAttribute nestHost -> { + builder.enclosingClass(nestHost.nestHost()); + } + case InnerClassesAttribute innerClasses -> { + builder.nestMembers(currentClassName, innerClasses); + } + case RuntimeVisibleAnnotationsAttribute annotationsAttribute -> { + builder.mergedAnnotations(ClassFileAnnotationMetadata.createMergedAnnotations(currentClassName, annotationsAttribute, classLoader)); + } + case Superclass superclass -> { + builder.superClass(superclass); + } + case Interfaces interfaces -> { + builder.interfaces(interfaces); + } + case MethodModel method -> { + builder.method(method); + } + default -> { + // ignore class element + } + } + }); + return builder.build(); + } + + static class Builder { + + private final ClassLoader clasLoader; + + private String className; + + private AccessFlags accessFlags; + + private Set innerAccessFlags; + + @Nullable + private String enclosingClassName; + + @Nullable + private String superClassName; + + private Set interfaceNames = new HashSet<>(); + + private Set memberClassNames = new HashSet<>(); + + private Set declaredMethods = new HashSet<>(); + + private MergedAnnotations mergedAnnotations = MergedAnnotations.of(Collections.emptySet()); + + public Builder(ClassLoader classLoader) { + this.clasLoader = classLoader; + } + + Builder classEntry(ClassEntry classEntry) { + this.className = ClassUtils.convertResourcePathToClassName(classEntry.name().stringValue()); + return this; + } + + Builder accessFlags(AccessFlags accessFlags) { + this.accessFlags = accessFlags; + return this; + } + + Builder innerAccessFlags(Set innerAccessFlags) { + this.innerAccessFlags = innerAccessFlags; + return this; + } + + Builder enclosingClass(ClassEntry enclosingClass) { + this.enclosingClassName = ClassUtils.convertResourcePathToClassName(enclosingClass.name().stringValue()); + return this; + } + + Builder superClass(Superclass superClass) { + this.superClassName = ClassUtils.convertResourcePathToClassName(superClass.superclassEntry().name().stringValue()); + return this; + } + + Builder interfaces(Interfaces interfaces) { + for (ClassEntry entry : interfaces.interfaces()) { + this.interfaceNames.add(ClassUtils.convertResourcePathToClassName(entry.name().stringValue())); + } + return this; + } + + Builder nestMembers(String currentClassName, InnerClassesAttribute innerClasses) { + for (InnerClassInfo classInfo : innerClasses.classes()) { + String innerClassName = classInfo.innerClass().name().stringValue(); + // the current class is an inner class + if (currentClassName.equals(innerClassName)) { + this.innerAccessFlags = classInfo.flags(); + } + // collecting data about actual inner classes + else { + this.memberClassNames.add(ClassUtils.convertResourcePathToClassName(innerClassName)); + } + } + return this; + } + + Builder mergedAnnotations(MergedAnnotations mergedAnnotations) { + this.mergedAnnotations = mergedAnnotations; + return this; + } + + Builder method(MethodModel method) { + this.declaredMethods.add(ClassFileMethodMetadata.of(method, this.clasLoader)); + return this; + } + + ClassFileClassMetadata build() { + boolean independentInnerClass = (this.enclosingClassName != null) && this.innerAccessFlags.contains(AccessFlag.STATIC); + return new ClassFileClassMetadata(this.className, this.accessFlags, this.enclosingClassName, this.superClassName, + independentInnerClass, this.interfaceNames, this.memberClassNames, this.declaredMethods, this.mergedAnnotations); + } + + } + +} diff --git a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMetadataReader.java b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMetadataReader.java new file mode 100644 index 000000000000..4205f9ab5d08 --- /dev/null +++ b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMetadataReader.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-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.core.type.classreading; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassModel; + +import org.springframework.core.io.Resource; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.ClassMetadata; +import org.springframework.lang.Nullable; + +/** + * {@link MetadataReader} implementation based on the {@link ClassFile} API. + * + * @author Brian Clozel + */ +final class ClassFileMetadataReader implements MetadataReader { + + private final Resource resource; + + private final AnnotationMetadata annotationMetadata; + + + ClassFileMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException { + this.resource = resource; + this.annotationMetadata = ClassFileClassMetadata.of(getClassModel(resource), classLoader); + } + + private static ClassModel getClassModel(Resource resource) throws IOException { + try (InputStream is = resource.getInputStream()) { + byte[] bytes = is.readAllBytes(); + return ClassFile.of().parse(bytes); + } + } + + @Override + public Resource getResource() { + return this.resource; + } + + @Override + public ClassMetadata getClassMetadata() { + return this.annotationMetadata; + } + + @Override + public AnnotationMetadata getAnnotationMetadata() { + return this.annotationMetadata; + } + +} diff --git a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMetadataReaderFactory.java b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMetadataReaderFactory.java new file mode 100644 index 000000000000..3d56ebf58062 --- /dev/null +++ b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMetadataReaderFactory.java @@ -0,0 +1,104 @@ +/* + * Copyright 2002-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.core.type.classreading; + +import java.io.FileNotFoundException; +import java.io.IOException; + +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; + +/** + * Implementation of the {@link MetadataReaderFactory} interface, + * using the {@link java.lang.classfile.ClassFile} API for parsing the bytecode. + * + * @author Brian Clozel + * @since 7.0 + */ +public class ClassFileMetadataReaderFactory implements MetadataReaderFactory { + + + private final ResourceLoader resourceLoader; + + + /** + * Create a new ClassFileMetadataReaderFactory for the default class loader. + */ + public ClassFileMetadataReaderFactory() { + this.resourceLoader = new DefaultResourceLoader(); + } + + /** + * Create a new ClassFileMetadataReaderFactory for the given resource loader. + * @param resourceLoader the Spring ResourceLoader to use + * (also determines the ClassLoader to use) + */ + public ClassFileMetadataReaderFactory(@Nullable ResourceLoader resourceLoader) { + this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader()); + } + + /** + * Create a new ClassFileMetadataReaderFactory for the given class loader. + * @param classLoader the ClassLoader to use + */ + public ClassFileMetadataReaderFactory(@Nullable ClassLoader classLoader) { + this.resourceLoader = + (classLoader != null ? new DefaultResourceLoader(classLoader) : new DefaultResourceLoader()); + } + + /** + * Return the ResourceLoader that this MetadataReaderFactory has been + * constructed with. + */ + public final ResourceLoader getResourceLoader() { + return this.resourceLoader; + } + + @Override + public MetadataReader getMetadataReader(String className) throws IOException { + try { + String resourcePath = ResourceLoader.CLASSPATH_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(className) + ClassUtils.CLASS_FILE_SUFFIX; + Resource resource = this.resourceLoader.getResource(resourcePath); + return getMetadataReader(resource); + } + catch (FileNotFoundException ex) { + // Maybe an inner class name using the dot name syntax? Need to use the dollar syntax here... + // ClassUtils.forName has an equivalent check for resolution into Class references later on. + int lastDotIndex = className.lastIndexOf('.'); + if (lastDotIndex != -1) { + String innerClassName = + className.substring(0, lastDotIndex) + '$' + className.substring(lastDotIndex + 1); + String innerClassResourcePath = ResourceLoader.CLASSPATH_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(innerClassName) + ClassUtils.CLASS_FILE_SUFFIX; + Resource innerClassResource = this.resourceLoader.getResource(innerClassResourcePath); + if (innerClassResource.exists()) { + return getMetadataReader(innerClassResource); + } + } + throw ex; + } + } + + @Override + public MetadataReader getMetadataReader(Resource resource) throws IOException { + return new ClassFileMetadataReader(resource, this.resourceLoader.getClassLoader()); + } +} diff --git a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMethodMetadata.java b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMethodMetadata.java new file mode 100644 index 000000000000..676c5da1bfe2 --- /dev/null +++ b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMethodMetadata.java @@ -0,0 +1,162 @@ +/* + * Copyright 2002-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.core.type.classreading; + +import java.lang.classfile.AccessFlags; +import java.lang.classfile.MethodModel; +import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.reflect.AccessFlag; +import java.util.Collections; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.type.MethodMetadata; +import org.springframework.lang.Nullable; + +/** + * {@link MethodMetadata} extracted from class bytecode using the + * {@link java.lang.classfile.ClassFile} API. + * @author Brian Clozel + */ +class ClassFileMethodMetadata implements MethodMetadata { + + private final String methodName; + + private final AccessFlags accessFlags; + + @Nullable + private final String declaringClassName; + + private final String returnTypeName; + + // The source implements equals(), hashCode(), and toString() for the underlying method. + private final Object source; + + private final MergedAnnotations annotations; + + ClassFileMethodMetadata(String methodName, AccessFlags accessFlags, String declaringClassName, String returnTypeName, Object source, MergedAnnotations annotations) { + this.methodName = methodName; + this.accessFlags = accessFlags; + this.declaringClassName = declaringClassName; + this.returnTypeName = returnTypeName; + this.source = source; + this.annotations = annotations; + } + + @Override + public String getMethodName() { + return this.methodName; + } + + @Override + @Nullable + public String getDeclaringClassName() { + return this.declaringClassName; + } + + @Override + public String getReturnTypeName() { + return this.returnTypeName; + } + + @Override + public boolean isAbstract() { + return this.accessFlags.has(AccessFlag.ABSTRACT); + } + + @Override + public boolean isStatic() { + return this.accessFlags.has(AccessFlag.STATIC); + } + + @Override + public boolean isFinal() { + return this.accessFlags.has(AccessFlag.FINAL); + } + + @Override + public boolean isOverridable() { + return !isStatic() && !isFinal() && !isPrivate(); + } + + private boolean isPrivate() { + return this.accessFlags.has(AccessFlag.PRIVATE); + } + + @Override + public MergedAnnotations getAnnotations() { + return this.annotations; + } + + + @Override + public boolean equals(@Nullable Object other) { + return (this == other || (other instanceof ClassFileMethodMetadata that && this.source.equals(that.source))); + } + + @Override + public int hashCode() { + return this.source.hashCode(); + } + + @Override + public String toString() { + return this.source.toString(); + } + + static ClassFileMethodMetadata of(MethodModel methodModel, ClassLoader classLoader) { + String methodName = methodModel.methodName().stringValue(); + AccessFlags flags = methodModel.flags(); + String declaringClassName = methodModel.parent().map(parent -> parent.thisClass().name().stringValue()).orElse(null); + String returnTypeName = methodModel.methodTypeSymbol().returnType().displayName(); + Source source = new Source(declaringClassName, methodName, methodModel.methodTypeSymbol()); + MergedAnnotations annotations = methodModel.elementStream() + .filter(element -> element instanceof RuntimeVisibleAnnotationsAttribute) + .findFirst() + .map(element -> ClassFileAnnotationMetadata.createMergedAnnotations(methodName, (RuntimeVisibleAnnotationsAttribute) element, classLoader)) + .orElse(MergedAnnotations.of(Collections.emptyList())); + return new ClassFileMethodMetadata(methodName, flags, declaringClassName, returnTypeName, source, annotations); + } + + /** + * {@link MergedAnnotation} source. + * @param declaringClassName the name of the declaring class + * @param methodName the name of the method + * @param descriptor the bytecode descriptor for this method + */ + record Source(String declaringClassName, String methodName, MethodTypeDesc descriptor) { + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(this.declaringClassName); + builder.append('.'); + builder.append(this.methodName); + builder.append('('); + builder.append(Stream.of(this.descriptor.parameterArray()) + .map(ClassDesc::displayName) + .collect(Collectors.joining(","))); + builder.append(')'); + return builder.toString(); + } + } + +} diff --git a/spring-core/src/main/java24/org/springframework/core/type/classreading/MetadataReaderFactoryDelegate.java b/spring-core/src/main/java24/org/springframework/core/type/classreading/MetadataReaderFactoryDelegate.java new file mode 100644 index 000000000000..94e33954b418 --- /dev/null +++ b/spring-core/src/main/java24/org/springframework/core/type/classreading/MetadataReaderFactoryDelegate.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-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.core.type.classreading; + +import org.springframework.core.io.ResourceLoader; +import org.springframework.lang.Nullable; + +/** + * Internal delegate for instantiating {@link MetadataReaderFactory} implementations. + * For JDK >= 24, the {@link ClassFileMetadataReaderFactory} is being used. + * + * @author Brian Clozel + * @see MetadataReaderFactory + */ +abstract class MetadataReaderFactoryDelegate { + + static MetadataReaderFactory create(@Nullable ResourceLoader resourceLoader) { + return new ClassFileMetadataReaderFactory(resourceLoader); + } + + static MetadataReaderFactory create(@Nullable ClassLoader classLoader) { + return new ClassFileMetadataReaderFactory(classLoader); + } +} diff --git a/spring-core/src/test/java/org/springframework/core/type/AbstractAnnotationMetadataTests.java b/spring-core/src/test/java/org/springframework/core/type/AbstractAnnotationMetadataTests.java index cc966f179955..83d20cd450de 100644 --- a/spring-core/src/test/java/org/springframework/core/type/AbstractAnnotationMetadataTests.java +++ b/spring-core/src/test/java/org/springframework/core/type/AbstractAnnotationMetadataTests.java @@ -146,8 +146,8 @@ void getSuperClassNameWhenHasSuperClassReturnsName() { @Test void getSuperClassNameWhenHasNoSuperClassReturnsNull() { assertThat(get(Object.class).getSuperClassName()).isNull(); - assertThat(get(TestInterface.class).getSuperClassName()).isNull(); - assertThat(get(TestSubInterface.class).getSuperClassName()).isNull(); + assertThat(get(TestInterface.class).getSuperClassName()).isIn(null, "java.lang.Object"); + assertThat(get(TestSubInterface.class).getSuperClassName()).isIn(null, "java.lang.Object"); } @Test @@ -210,6 +210,17 @@ void getAllAnnotationAttributesReturnsAllAttributes() { assertThat(attributes.get("size")).containsExactlyInAnyOrder(1, 2); } + @Test + void getComplexAttributeTypesReturnsAll() { + MultiValueMap attributes = + get(WithComplexAttributeTypes.class).getAllAnnotationAttributes(ComplexAttributes.class.getName()); + assertThat(attributes).containsOnlyKeys("names", "count", "type"); + assertThat(attributes.get("names")).hasSize(1); + assertThat(attributes.get("names").get(0)).isEqualTo(new String[]{"first", "second"}); + assertThat(attributes.get("count")).containsExactlyInAnyOrder(TestEnum.ONE); + assertThat(attributes.get("type")).containsExactlyInAnyOrder(TestEnum.class); + } + @Test void getAnnotationTypesReturnsDirectAnnotations() { AnnotationMetadata metadata = get(WithDirectAnnotations.class); @@ -407,4 +418,22 @@ public static class WithMetaAnnotationAttributes { } + @ComplexAttributes(names = {"first", "second"}, count = TestEnum.ONE, type = TestEnum.class) + public static class WithComplexAttributeTypes { + } + + @Retention(RetentionPolicy.RUNTIME) + public @interface ComplexAttributes { + + String[] names(); + + TestEnum count(); + + Class type(); + } + + public enum TestEnum { + ONE, TWO, THREE + } + } diff --git a/spring-core/src/test/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataTests.java b/spring-core/src/test/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataTests.java index f66c81bc35d3..8f8e50c40fd3 100644 --- a/spring-core/src/test/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataTests.java +++ b/spring-core/src/test/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataTests.java @@ -30,9 +30,8 @@ class SimpleAnnotationMetadataTests extends AbstractAnnotationMetadataTests { @Override protected AnnotationMetadata get(Class source) { try { - return new SimpleMetadataReaderFactory( - source.getClassLoader()).getMetadataReader( - source.getName()).getAnnotationMetadata(); + return MetadataReaderFactory.create(source.getClassLoader()) + .getMetadataReader(source.getName()).getAnnotationMetadata(); } catch (Exception ex) { throw new IllegalStateException(ex); From 5d492689b773f6a97aab6ac0d6e6bb5a679cb9a8 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Thu, 28 Nov 2024 16:27:35 +0100 Subject: [PATCH 014/701] Create MetadataReaderFactory instances with factory methods Prior to this commit, Spring would create directly `SimpleMetadataReaderFactory` instances or would manually create `CachingMetadataReaderFactory` which extend `SimpleMetadataReaderFactory`. This would prevent usage of the new `ClassFileMetadataReaderFactory` in our internal codebase. This commit replaces manual instantiations with calls to `MetadataReaderFactory` factory methods and makes `CachingMetadataReaderFactory` delegate to another factory created with the same factory methods. This allows internal usage of `ClassFileMetadataReaderFactory` internally. Closes gh-33616 --- .../AutowiredAnnotationBeanPostProcessor.java | 3 +-- .../CachingMetadataReaderFactory.java | 23 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index f711222c0f11..cd5ce1ea7dbe 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -83,7 +83,6 @@ import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.classreading.MetadataReaderFactory; -import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; import org.springframework.javapoet.ClassName; import org.springframework.javapoet.CodeBlock; import org.springframework.lang.Nullable; @@ -284,7 +283,7 @@ public void setBeanFactory(BeanFactory beanFactory) { "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory); } this.beanFactory = clbf; - this.metadataReaderFactory = new SimpleMetadataReaderFactory(clbf.getBeanClassLoader()); + this.metadataReaderFactory = MetadataReaderFactory.create(clbf.getBeanClassLoader()); } diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java b/spring-core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java index 9d9956fcf26a..b29a72153887 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -35,11 +35,13 @@ * @author Costin Leau * @since 2.5 */ -public class CachingMetadataReaderFactory extends SimpleMetadataReaderFactory { +public class CachingMetadataReaderFactory implements MetadataReaderFactory { /** Default maximum number of entries for a local MetadataReader cache: 256. */ public static final int DEFAULT_CACHE_LIMIT = 256; + private final MetadataReaderFactory delegate; + /** MetadataReader cache: either local or shared at the ResourceLoader level. */ @Nullable private Map metadataReaderCache; @@ -50,7 +52,7 @@ public class CachingMetadataReaderFactory extends SimpleMetadataReaderFactory { * using a local resource cache. */ public CachingMetadataReaderFactory() { - super(); + this.delegate = MetadataReaderFactory.create((ClassLoader) null); setCacheLimit(DEFAULT_CACHE_LIMIT); } @@ -60,7 +62,7 @@ public CachingMetadataReaderFactory() { * @param classLoader the ClassLoader to use */ public CachingMetadataReaderFactory(@Nullable ClassLoader classLoader) { - super(classLoader); + this.delegate = MetadataReaderFactory.create(classLoader); setCacheLimit(DEFAULT_CACHE_LIMIT); } @@ -72,7 +74,7 @@ public CachingMetadataReaderFactory(@Nullable ClassLoader classLoader) { * @see DefaultResourceLoader#getResourceCache */ public CachingMetadataReaderFactory(@Nullable ResourceLoader resourceLoader) { - super(resourceLoader); + this.delegate = MetadataReaderFactory.create(resourceLoader); if (resourceLoader instanceof DefaultResourceLoader defaultResourceLoader) { this.metadataReaderCache = defaultResourceLoader.getResourceCache(MetadataReader.class); } @@ -81,7 +83,6 @@ public CachingMetadataReaderFactory(@Nullable ResourceLoader resourceLoader) { } } - /** * Specify the maximum number of entries for the MetadataReader cache. *

Default is 256 for a local cache, whereas a shared cache is @@ -112,6 +113,10 @@ public int getCacheLimit() { } } + @Override + public MetadataReader getMetadataReader(String className) throws IOException { + return this.delegate.getMetadataReader(className); + } @Override public MetadataReader getMetadataReader(Resource resource) throws IOException { @@ -119,7 +124,7 @@ public MetadataReader getMetadataReader(Resource resource) throws IOException { // No synchronization necessary... MetadataReader metadataReader = this.metadataReaderCache.get(resource); if (metadataReader == null) { - metadataReader = super.getMetadataReader(resource); + metadataReader = this.delegate.getMetadataReader(resource); this.metadataReaderCache.put(resource, metadataReader); } return metadataReader; @@ -128,14 +133,14 @@ else if (this.metadataReaderCache != null) { synchronized (this.metadataReaderCache) { MetadataReader metadataReader = this.metadataReaderCache.get(resource); if (metadataReader == null) { - metadataReader = super.getMetadataReader(resource); + metadataReader = this.delegate.getMetadataReader(resource); this.metadataReaderCache.put(resource, metadataReader); } return metadataReader; } } else { - return super.getMetadataReader(resource); + return this.delegate.getMetadataReader(resource); } } From 5fa9460bf6f73775706c7e4f11c38ee3572294d8 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Thu, 28 Nov 2024 18:07:23 +0100 Subject: [PATCH 015/701] Temporarily revert changes for gh-33616 See gh-33616 --- .../AutowiredAnnotationBeanPostProcessor.java | 3 +- spring-core/spring-core.gradle | 6 +- .../CachingMetadataReaderFactory.java | 23 +- .../classreading/MetadataReaderFactory.java | 24 +- .../MetadataReaderFactoryDelegate.java | 39 --- .../ClassFileAnnotationMetadata.java | 129 ------- .../classreading/ClassFileClassMetadata.java | 315 ------------------ .../classreading/ClassFileMetadataReader.java | 68 ---- .../ClassFileMetadataReaderFactory.java | 104 ------ .../classreading/ClassFileMethodMetadata.java | 162 --------- .../MetadataReaderFactoryDelegate.java | 38 --- .../type/AbstractAnnotationMetadataTests.java | 33 +- .../SimpleAnnotationMetadataTests.java | 5 +- 13 files changed, 18 insertions(+), 931 deletions(-) delete mode 100644 spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactoryDelegate.java delete mode 100644 spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationMetadata.java delete mode 100644 spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileClassMetadata.java delete mode 100644 spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMetadataReader.java delete mode 100644 spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMetadataReaderFactory.java delete mode 100644 spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMethodMetadata.java delete mode 100644 spring-core/src/main/java24/org/springframework/core/type/classreading/MetadataReaderFactoryDelegate.java diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index cd5ce1ea7dbe..f711222c0f11 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -83,6 +83,7 @@ import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; import org.springframework.javapoet.ClassName; import org.springframework.javapoet.CodeBlock; import org.springframework.lang.Nullable; @@ -283,7 +284,7 @@ public void setBeanFactory(BeanFactory beanFactory) { "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory); } this.beanFactory = clbf; - this.metadataReaderFactory = MetadataReaderFactory.create(clbf.getBeanClassLoader()); + this.metadataReaderFactory = new SimpleMetadataReaderFactory(clbf.getBeanClassLoader()); } diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index 1ea7bd08c54e..a08d0e166723 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -11,7 +11,7 @@ apply plugin: "kotlin" apply plugin: "kotlinx-serialization" multiRelease { - targetVersions 17, 21, 24 + targetVersions 17, 21 } def javapoetVersion = "1.13.0" @@ -25,10 +25,6 @@ configurations { graalvm } -springFramework { - enableJavaPreviewFeatures = true -} - task javapoetRepackJar(type: ShadowJar) { archiveBaseName = 'spring-javapoet-repack' archiveVersion = javapoetVersion diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java b/spring-core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java index b29a72153887..9d9956fcf26a 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2022 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. @@ -35,13 +35,11 @@ * @author Costin Leau * @since 2.5 */ -public class CachingMetadataReaderFactory implements MetadataReaderFactory { +public class CachingMetadataReaderFactory extends SimpleMetadataReaderFactory { /** Default maximum number of entries for a local MetadataReader cache: 256. */ public static final int DEFAULT_CACHE_LIMIT = 256; - private final MetadataReaderFactory delegate; - /** MetadataReader cache: either local or shared at the ResourceLoader level. */ @Nullable private Map metadataReaderCache; @@ -52,7 +50,7 @@ public class CachingMetadataReaderFactory implements MetadataReaderFactory { * using a local resource cache. */ public CachingMetadataReaderFactory() { - this.delegate = MetadataReaderFactory.create((ClassLoader) null); + super(); setCacheLimit(DEFAULT_CACHE_LIMIT); } @@ -62,7 +60,7 @@ public CachingMetadataReaderFactory() { * @param classLoader the ClassLoader to use */ public CachingMetadataReaderFactory(@Nullable ClassLoader classLoader) { - this.delegate = MetadataReaderFactory.create(classLoader); + super(classLoader); setCacheLimit(DEFAULT_CACHE_LIMIT); } @@ -74,7 +72,7 @@ public CachingMetadataReaderFactory(@Nullable ClassLoader classLoader) { * @see DefaultResourceLoader#getResourceCache */ public CachingMetadataReaderFactory(@Nullable ResourceLoader resourceLoader) { - this.delegate = MetadataReaderFactory.create(resourceLoader); + super(resourceLoader); if (resourceLoader instanceof DefaultResourceLoader defaultResourceLoader) { this.metadataReaderCache = defaultResourceLoader.getResourceCache(MetadataReader.class); } @@ -83,6 +81,7 @@ public CachingMetadataReaderFactory(@Nullable ResourceLoader resourceLoader) { } } + /** * Specify the maximum number of entries for the MetadataReader cache. *

Default is 256 for a local cache, whereas a shared cache is @@ -113,10 +112,6 @@ public int getCacheLimit() { } } - @Override - public MetadataReader getMetadataReader(String className) throws IOException { - return this.delegate.getMetadataReader(className); - } @Override public MetadataReader getMetadataReader(Resource resource) throws IOException { @@ -124,7 +119,7 @@ public MetadataReader getMetadataReader(Resource resource) throws IOException { // No synchronization necessary... MetadataReader metadataReader = this.metadataReaderCache.get(resource); if (metadataReader == null) { - metadataReader = this.delegate.getMetadataReader(resource); + metadataReader = super.getMetadataReader(resource); this.metadataReaderCache.put(resource, metadataReader); } return metadataReader; @@ -133,14 +128,14 @@ else if (this.metadataReaderCache != null) { synchronized (this.metadataReaderCache) { MetadataReader metadataReader = this.metadataReaderCache.get(resource); if (metadataReader == null) { - metadataReader = this.delegate.getMetadataReader(resource); + metadataReader = super.getMetadataReader(resource); this.metadataReaderCache.put(resource, metadataReader); } return metadataReader; } } else { - return this.delegate.getMetadataReader(resource); + return super.getMetadataReader(resource); } } diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java b/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java index 0748fdafe074..4eddbfa6cd24 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-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. @@ -19,15 +19,12 @@ import java.io.IOException; import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; -import org.springframework.lang.Nullable; /** * Factory interface for {@link MetadataReader} instances. * Allows for caching a MetadataReader per original resource. * * @author Juergen Hoeller - * @author Brian Clozel * @since 2.5 * @see SimpleMetadataReaderFactory * @see CachingMetadataReaderFactory @@ -52,23 +49,4 @@ public interface MetadataReaderFactory { */ MetadataReader getMetadataReader(Resource resource) throws IOException; - /** - * Create a default {@link MetadataReaderFactory} implementation that's suitable - * for the current JVM. - * @return a new factory instance - * @since 7.0 - */ - static MetadataReaderFactory create(@Nullable ResourceLoader resourceLoader) { - return MetadataReaderFactoryDelegate.create(resourceLoader); - } - - /** - * Create a default {@link MetadataReaderFactory} implementation that's suitable - * for the current JVM. - * @return a new factory instance - * @since 7.0 - */ - static MetadataReaderFactory create(@Nullable ClassLoader classLoader) { - return MetadataReaderFactoryDelegate.create(classLoader); - } } diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactoryDelegate.java b/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactoryDelegate.java deleted file mode 100644 index e24b004b9680..000000000000 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactoryDelegate.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2002-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.core.type.classreading; - -import org.springframework.core.io.ResourceLoader; -import org.springframework.lang.Nullable; - -/** - * Internal delegate for instantiating {@link MetadataReaderFactory} implementations. - * For JDK < 24, the {@link SimpleMetadataReaderFactory} is being used. - * - * @author Brian Clozel - * @since 7.0 - * @see MetadataReaderFactory - */ -abstract class MetadataReaderFactoryDelegate { - - static MetadataReaderFactory create(@Nullable ResourceLoader resourceLoader) { - return new SimpleMetadataReaderFactory(resourceLoader); - } - - static MetadataReaderFactory create(@Nullable ClassLoader classLoader) { - return new SimpleMetadataReaderFactory(classLoader); - } -} diff --git a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationMetadata.java b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationMetadata.java deleted file mode 100644 index 7972c9b03f99..000000000000 --- a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileAnnotationMetadata.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2002-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.core.type.classreading; - - -import java.lang.classfile.Annotation; -import java.lang.classfile.AnnotationElement; -import java.lang.classfile.AnnotationValue; -import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.springframework.core.annotation.AnnotationFilter; -import org.springframework.core.annotation.MergedAnnotation; -import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.lang.Nullable; -import org.springframework.util.ClassUtils; - -abstract class ClassFileAnnotationMetadata { - - static MergedAnnotations createMergedAnnotations(String entryName, RuntimeVisibleAnnotationsAttribute annotationAttribute, @Nullable ClassLoader classLoader) { - Set> annotations = new LinkedHashSet<>(4); - annotationAttribute.annotations().forEach(ann -> { - MergedAnnotation mergedAnnotation = createMergedAnnotation(entryName, ann, classLoader); - if (mergedAnnotation != null) { - annotations.add(mergedAnnotation); - } - }); - return MergedAnnotations.of(annotations); - } - - @SuppressWarnings("unchecked") - @Nullable - private static MergedAnnotation createMergedAnnotation(String entryName, Annotation annotation, @Nullable ClassLoader classLoader) { - String typeName = fromTypeDescriptor(annotation.className().stringValue()); - if (AnnotationFilter.PLAIN.matches(typeName)) { - return null; - } - Map attributes = new LinkedHashMap<>(4); - try { - Class annotationType = (Class) ClassUtils.forName(typeName, classLoader); - for (AnnotationElement element : annotation.elements()) { - attributes.put(element.name().stringValue(), readAnnotationValue(element.value(), classLoader)); - } - Map compactedAttributes = (attributes.isEmpty() ? Collections.emptyMap() : attributes); - return MergedAnnotation.of(classLoader, new Source(entryName), annotationType, compactedAttributes); - } - catch (ClassNotFoundException | LinkageError ex) { - return null; - } - } - - private static Object readAnnotationValue(AnnotationValue elementValue, @Nullable ClassLoader classLoader) throws ClassNotFoundException { - switch (elementValue) { - case AnnotationValue.OfArray arrayValue -> { - List rawValues = arrayValue.values(); - List values = new ArrayList<>(rawValues.size()); - for (AnnotationValue arrayEntry : rawValues) { - values.add(readAnnotationValue(arrayEntry, classLoader)); - } - Class elementType = getArrayElementType(values); - return values.toArray((Object[]) Array.newInstance(elementType, rawValues.size())); - } - case AnnotationValue.OfAnnotation annotationValue -> { - return annotationValue.annotation(); - } - case AnnotationValue.OfClass classValue -> { - return fromTypeDescriptor(classValue.className().stringValue()); - } - case AnnotationValue.OfEnum enumValue -> { - return parseEnum(enumValue, classLoader); - } - case AnnotationValue.OfConstant constantValue -> { - return constantValue.resolvedValue(); - } - default -> { - return elementValue; - } - } - } - - private static Class getArrayElementType(List values) { - if (values.isEmpty()) { - return Object.class; - } - Object firstElement = values.getFirst(); - if (firstElement instanceof Enum enumeration) { - return enumeration.getDeclaringClass(); - } - return firstElement.getClass(); - } - - private static String fromTypeDescriptor(String descriptor) { - return descriptor.substring(1, descriptor.length() - 1) - .replace('/', '.'); - } - - @SuppressWarnings("unchecked") - private static > Enum parseEnum(AnnotationValue.OfEnum enumValue, @Nullable ClassLoader classLoader) throws ClassNotFoundException { - String enumClassName = fromTypeDescriptor(enumValue.className().stringValue()); - Class enumClass = (Class) ClassUtils.forName(enumClassName, classLoader); - return Enum.valueOf(enumClass, enumValue.constantName().stringValue()); - } - - record Source(String entryName) { - - } - -} diff --git a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileClassMetadata.java b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileClassMetadata.java deleted file mode 100644 index d5677a34a6fc..000000000000 --- a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileClassMetadata.java +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright 2002-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.core.type.classreading; - -import java.lang.classfile.AccessFlags; -import java.lang.classfile.ClassModel; -import java.lang.classfile.Interfaces; -import java.lang.classfile.MethodModel; -import java.lang.classfile.Superclass; -import java.lang.classfile.attribute.InnerClassInfo; -import java.lang.classfile.attribute.InnerClassesAttribute; -import java.lang.classfile.attribute.NestHostAttribute; -import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; -import java.lang.classfile.constantpool.ClassEntry; -import java.lang.reflect.AccessFlag; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Set; - -import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.core.type.MethodMetadata; -import org.springframework.lang.Nullable; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; - -/** - * {@link AnnotationMetadata} implementation that leverages - * the {@link java.lang.classfile.ClassFile} API. - * @author Brian Clozel - */ -class ClassFileClassMetadata implements AnnotationMetadata { - - private final String className; - - private final AccessFlags accessFlags; - - @Nullable - private final String enclosingClassName; - - @Nullable - private final String superClassName; - - private final boolean independentInnerClass; - - private final Set interfaceNames; - - private final Set memberClassNames; - - private final Set declaredMethods; - - private final MergedAnnotations mergedAnnotations; - - @Nullable - private Set annotationTypes; - - ClassFileClassMetadata(String className, AccessFlags accessFlags, @Nullable String enclosingClassName, - @Nullable String superClassName, boolean independentInnerClass, Set interfaceNames, - Set memberClassNames, Set declaredMethods, MergedAnnotations mergedAnnotations) { - this.className = className; - this.accessFlags = accessFlags; - this.enclosingClassName = enclosingClassName; - this.superClassName = superClassName; - this.independentInnerClass = independentInnerClass; - this.interfaceNames = interfaceNames; - this.memberClassNames = memberClassNames; - this.declaredMethods = declaredMethods; - this.mergedAnnotations = mergedAnnotations; - } - - @Override - public String getClassName() { - return this.className; - } - - @Override - public boolean isInterface() { - return this.accessFlags.has(AccessFlag.INTERFACE); - } - - @Override - public boolean isAnnotation() { - return this.accessFlags.has(AccessFlag.ANNOTATION); - } - - @Override - public boolean isAbstract() { - return this.accessFlags.has(AccessFlag.ABSTRACT); - } - - @Override - public boolean isFinal() { - return this.accessFlags.has(AccessFlag.FINAL); - } - - @Override - public boolean isIndependent() { - return (this.enclosingClassName == null || this.independentInnerClass); - } - - @Override - @Nullable - public String getEnclosingClassName() { - return this.enclosingClassName; - } - - @Override - @Nullable - public String getSuperClassName() { - return this.superClassName; - } - - @Override - public String[] getInterfaceNames() { - return StringUtils.toStringArray(this.interfaceNames); - } - - @Override - public String[] getMemberClassNames() { - return StringUtils.toStringArray(this.memberClassNames); - } - - @Override - public MergedAnnotations getAnnotations() { - return this.mergedAnnotations; - } - - @Override - public Set getAnnotationTypes() { - Set annotationTypes = this.annotationTypes; - if (annotationTypes == null) { - annotationTypes = Collections.unmodifiableSet( - AnnotationMetadata.super.getAnnotationTypes()); - this.annotationTypes = annotationTypes; - } - return annotationTypes; - } - - @Override - public Set getAnnotatedMethods(String annotationName) { - Set result = new LinkedHashSet<>(4); - for (MethodMetadata annotatedMethod : this.declaredMethods) { - if (annotatedMethod.isAnnotated(annotationName)) { - result.add(annotatedMethod); - } - } - return Collections.unmodifiableSet(result); - } - - @Override - public Set getDeclaredMethods() { - return Collections.unmodifiableSet(this.declaredMethods); - } - - - @Override - public boolean equals(@Nullable Object other) { - return (this == other || (other instanceof ClassFileClassMetadata that && this.className.equals(that.className))); - } - - @Override - public int hashCode() { - return this.className.hashCode(); - } - - @Override - public String toString() { - return this.className; - } - - - static ClassFileClassMetadata of(ClassModel classModel, @Nullable ClassLoader classLoader) { - Builder builder = new Builder(classLoader); - builder.classEntry(classModel.thisClass()); - String currentClassName = classModel.thisClass().name().stringValue(); - classModel.elementStream().forEach(classElement -> { - switch (classElement) { - case AccessFlags flags -> { - builder.accessFlags(flags); - } - case NestHostAttribute nestHost -> { - builder.enclosingClass(nestHost.nestHost()); - } - case InnerClassesAttribute innerClasses -> { - builder.nestMembers(currentClassName, innerClasses); - } - case RuntimeVisibleAnnotationsAttribute annotationsAttribute -> { - builder.mergedAnnotations(ClassFileAnnotationMetadata.createMergedAnnotations(currentClassName, annotationsAttribute, classLoader)); - } - case Superclass superclass -> { - builder.superClass(superclass); - } - case Interfaces interfaces -> { - builder.interfaces(interfaces); - } - case MethodModel method -> { - builder.method(method); - } - default -> { - // ignore class element - } - } - }); - return builder.build(); - } - - static class Builder { - - private final ClassLoader clasLoader; - - private String className; - - private AccessFlags accessFlags; - - private Set innerAccessFlags; - - @Nullable - private String enclosingClassName; - - @Nullable - private String superClassName; - - private Set interfaceNames = new HashSet<>(); - - private Set memberClassNames = new HashSet<>(); - - private Set declaredMethods = new HashSet<>(); - - private MergedAnnotations mergedAnnotations = MergedAnnotations.of(Collections.emptySet()); - - public Builder(ClassLoader classLoader) { - this.clasLoader = classLoader; - } - - Builder classEntry(ClassEntry classEntry) { - this.className = ClassUtils.convertResourcePathToClassName(classEntry.name().stringValue()); - return this; - } - - Builder accessFlags(AccessFlags accessFlags) { - this.accessFlags = accessFlags; - return this; - } - - Builder innerAccessFlags(Set innerAccessFlags) { - this.innerAccessFlags = innerAccessFlags; - return this; - } - - Builder enclosingClass(ClassEntry enclosingClass) { - this.enclosingClassName = ClassUtils.convertResourcePathToClassName(enclosingClass.name().stringValue()); - return this; - } - - Builder superClass(Superclass superClass) { - this.superClassName = ClassUtils.convertResourcePathToClassName(superClass.superclassEntry().name().stringValue()); - return this; - } - - Builder interfaces(Interfaces interfaces) { - for (ClassEntry entry : interfaces.interfaces()) { - this.interfaceNames.add(ClassUtils.convertResourcePathToClassName(entry.name().stringValue())); - } - return this; - } - - Builder nestMembers(String currentClassName, InnerClassesAttribute innerClasses) { - for (InnerClassInfo classInfo : innerClasses.classes()) { - String innerClassName = classInfo.innerClass().name().stringValue(); - // the current class is an inner class - if (currentClassName.equals(innerClassName)) { - this.innerAccessFlags = classInfo.flags(); - } - // collecting data about actual inner classes - else { - this.memberClassNames.add(ClassUtils.convertResourcePathToClassName(innerClassName)); - } - } - return this; - } - - Builder mergedAnnotations(MergedAnnotations mergedAnnotations) { - this.mergedAnnotations = mergedAnnotations; - return this; - } - - Builder method(MethodModel method) { - this.declaredMethods.add(ClassFileMethodMetadata.of(method, this.clasLoader)); - return this; - } - - ClassFileClassMetadata build() { - boolean independentInnerClass = (this.enclosingClassName != null) && this.innerAccessFlags.contains(AccessFlag.STATIC); - return new ClassFileClassMetadata(this.className, this.accessFlags, this.enclosingClassName, this.superClassName, - independentInnerClass, this.interfaceNames, this.memberClassNames, this.declaredMethods, this.mergedAnnotations); - } - - } - -} diff --git a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMetadataReader.java b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMetadataReader.java deleted file mode 100644 index 4205f9ab5d08..000000000000 --- a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMetadataReader.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2002-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.core.type.classreading; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.classfile.ClassFile; -import java.lang.classfile.ClassModel; - -import org.springframework.core.io.Resource; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.core.type.ClassMetadata; -import org.springframework.lang.Nullable; - -/** - * {@link MetadataReader} implementation based on the {@link ClassFile} API. - * - * @author Brian Clozel - */ -final class ClassFileMetadataReader implements MetadataReader { - - private final Resource resource; - - private final AnnotationMetadata annotationMetadata; - - - ClassFileMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException { - this.resource = resource; - this.annotationMetadata = ClassFileClassMetadata.of(getClassModel(resource), classLoader); - } - - private static ClassModel getClassModel(Resource resource) throws IOException { - try (InputStream is = resource.getInputStream()) { - byte[] bytes = is.readAllBytes(); - return ClassFile.of().parse(bytes); - } - } - - @Override - public Resource getResource() { - return this.resource; - } - - @Override - public ClassMetadata getClassMetadata() { - return this.annotationMetadata; - } - - @Override - public AnnotationMetadata getAnnotationMetadata() { - return this.annotationMetadata; - } - -} diff --git a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMetadataReaderFactory.java b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMetadataReaderFactory.java deleted file mode 100644 index 3d56ebf58062..000000000000 --- a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMetadataReaderFactory.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2002-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.core.type.classreading; - -import java.io.FileNotFoundException; -import java.io.IOException; - -import org.springframework.core.io.DefaultResourceLoader; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; -import org.springframework.lang.Nullable; -import org.springframework.util.ClassUtils; - -/** - * Implementation of the {@link MetadataReaderFactory} interface, - * using the {@link java.lang.classfile.ClassFile} API for parsing the bytecode. - * - * @author Brian Clozel - * @since 7.0 - */ -public class ClassFileMetadataReaderFactory implements MetadataReaderFactory { - - - private final ResourceLoader resourceLoader; - - - /** - * Create a new ClassFileMetadataReaderFactory for the default class loader. - */ - public ClassFileMetadataReaderFactory() { - this.resourceLoader = new DefaultResourceLoader(); - } - - /** - * Create a new ClassFileMetadataReaderFactory for the given resource loader. - * @param resourceLoader the Spring ResourceLoader to use - * (also determines the ClassLoader to use) - */ - public ClassFileMetadataReaderFactory(@Nullable ResourceLoader resourceLoader) { - this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader()); - } - - /** - * Create a new ClassFileMetadataReaderFactory for the given class loader. - * @param classLoader the ClassLoader to use - */ - public ClassFileMetadataReaderFactory(@Nullable ClassLoader classLoader) { - this.resourceLoader = - (classLoader != null ? new DefaultResourceLoader(classLoader) : new DefaultResourceLoader()); - } - - /** - * Return the ResourceLoader that this MetadataReaderFactory has been - * constructed with. - */ - public final ResourceLoader getResourceLoader() { - return this.resourceLoader; - } - - @Override - public MetadataReader getMetadataReader(String className) throws IOException { - try { - String resourcePath = ResourceLoader.CLASSPATH_URL_PREFIX + - ClassUtils.convertClassNameToResourcePath(className) + ClassUtils.CLASS_FILE_SUFFIX; - Resource resource = this.resourceLoader.getResource(resourcePath); - return getMetadataReader(resource); - } - catch (FileNotFoundException ex) { - // Maybe an inner class name using the dot name syntax? Need to use the dollar syntax here... - // ClassUtils.forName has an equivalent check for resolution into Class references later on. - int lastDotIndex = className.lastIndexOf('.'); - if (lastDotIndex != -1) { - String innerClassName = - className.substring(0, lastDotIndex) + '$' + className.substring(lastDotIndex + 1); - String innerClassResourcePath = ResourceLoader.CLASSPATH_URL_PREFIX + - ClassUtils.convertClassNameToResourcePath(innerClassName) + ClassUtils.CLASS_FILE_SUFFIX; - Resource innerClassResource = this.resourceLoader.getResource(innerClassResourcePath); - if (innerClassResource.exists()) { - return getMetadataReader(innerClassResource); - } - } - throw ex; - } - } - - @Override - public MetadataReader getMetadataReader(Resource resource) throws IOException { - return new ClassFileMetadataReader(resource, this.resourceLoader.getClassLoader()); - } -} diff --git a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMethodMetadata.java b/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMethodMetadata.java deleted file mode 100644 index 676c5da1bfe2..000000000000 --- a/spring-core/src/main/java24/org/springframework/core/type/classreading/ClassFileMethodMetadata.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2002-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.core.type.classreading; - -import java.lang.classfile.AccessFlags; -import java.lang.classfile.MethodModel; -import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; -import java.lang.constant.ClassDesc; -import java.lang.constant.MethodTypeDesc; -import java.lang.reflect.AccessFlag; -import java.util.Collections; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.springframework.core.annotation.MergedAnnotation; -import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.core.type.MethodMetadata; -import org.springframework.lang.Nullable; - -/** - * {@link MethodMetadata} extracted from class bytecode using the - * {@link java.lang.classfile.ClassFile} API. - * @author Brian Clozel - */ -class ClassFileMethodMetadata implements MethodMetadata { - - private final String methodName; - - private final AccessFlags accessFlags; - - @Nullable - private final String declaringClassName; - - private final String returnTypeName; - - // The source implements equals(), hashCode(), and toString() for the underlying method. - private final Object source; - - private final MergedAnnotations annotations; - - ClassFileMethodMetadata(String methodName, AccessFlags accessFlags, String declaringClassName, String returnTypeName, Object source, MergedAnnotations annotations) { - this.methodName = methodName; - this.accessFlags = accessFlags; - this.declaringClassName = declaringClassName; - this.returnTypeName = returnTypeName; - this.source = source; - this.annotations = annotations; - } - - @Override - public String getMethodName() { - return this.methodName; - } - - @Override - @Nullable - public String getDeclaringClassName() { - return this.declaringClassName; - } - - @Override - public String getReturnTypeName() { - return this.returnTypeName; - } - - @Override - public boolean isAbstract() { - return this.accessFlags.has(AccessFlag.ABSTRACT); - } - - @Override - public boolean isStatic() { - return this.accessFlags.has(AccessFlag.STATIC); - } - - @Override - public boolean isFinal() { - return this.accessFlags.has(AccessFlag.FINAL); - } - - @Override - public boolean isOverridable() { - return !isStatic() && !isFinal() && !isPrivate(); - } - - private boolean isPrivate() { - return this.accessFlags.has(AccessFlag.PRIVATE); - } - - @Override - public MergedAnnotations getAnnotations() { - return this.annotations; - } - - - @Override - public boolean equals(@Nullable Object other) { - return (this == other || (other instanceof ClassFileMethodMetadata that && this.source.equals(that.source))); - } - - @Override - public int hashCode() { - return this.source.hashCode(); - } - - @Override - public String toString() { - return this.source.toString(); - } - - static ClassFileMethodMetadata of(MethodModel methodModel, ClassLoader classLoader) { - String methodName = methodModel.methodName().stringValue(); - AccessFlags flags = methodModel.flags(); - String declaringClassName = methodModel.parent().map(parent -> parent.thisClass().name().stringValue()).orElse(null); - String returnTypeName = methodModel.methodTypeSymbol().returnType().displayName(); - Source source = new Source(declaringClassName, methodName, methodModel.methodTypeSymbol()); - MergedAnnotations annotations = methodModel.elementStream() - .filter(element -> element instanceof RuntimeVisibleAnnotationsAttribute) - .findFirst() - .map(element -> ClassFileAnnotationMetadata.createMergedAnnotations(methodName, (RuntimeVisibleAnnotationsAttribute) element, classLoader)) - .orElse(MergedAnnotations.of(Collections.emptyList())); - return new ClassFileMethodMetadata(methodName, flags, declaringClassName, returnTypeName, source, annotations); - } - - /** - * {@link MergedAnnotation} source. - * @param declaringClassName the name of the declaring class - * @param methodName the name of the method - * @param descriptor the bytecode descriptor for this method - */ - record Source(String declaringClassName, String methodName, MethodTypeDesc descriptor) { - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append(this.declaringClassName); - builder.append('.'); - builder.append(this.methodName); - builder.append('('); - builder.append(Stream.of(this.descriptor.parameterArray()) - .map(ClassDesc::displayName) - .collect(Collectors.joining(","))); - builder.append(')'); - return builder.toString(); - } - } - -} diff --git a/spring-core/src/main/java24/org/springframework/core/type/classreading/MetadataReaderFactoryDelegate.java b/spring-core/src/main/java24/org/springframework/core/type/classreading/MetadataReaderFactoryDelegate.java deleted file mode 100644 index 94e33954b418..000000000000 --- a/spring-core/src/main/java24/org/springframework/core/type/classreading/MetadataReaderFactoryDelegate.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2002-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.core.type.classreading; - -import org.springframework.core.io.ResourceLoader; -import org.springframework.lang.Nullable; - -/** - * Internal delegate for instantiating {@link MetadataReaderFactory} implementations. - * For JDK >= 24, the {@link ClassFileMetadataReaderFactory} is being used. - * - * @author Brian Clozel - * @see MetadataReaderFactory - */ -abstract class MetadataReaderFactoryDelegate { - - static MetadataReaderFactory create(@Nullable ResourceLoader resourceLoader) { - return new ClassFileMetadataReaderFactory(resourceLoader); - } - - static MetadataReaderFactory create(@Nullable ClassLoader classLoader) { - return new ClassFileMetadataReaderFactory(classLoader); - } -} diff --git a/spring-core/src/test/java/org/springframework/core/type/AbstractAnnotationMetadataTests.java b/spring-core/src/test/java/org/springframework/core/type/AbstractAnnotationMetadataTests.java index 83d20cd450de..cc966f179955 100644 --- a/spring-core/src/test/java/org/springframework/core/type/AbstractAnnotationMetadataTests.java +++ b/spring-core/src/test/java/org/springframework/core/type/AbstractAnnotationMetadataTests.java @@ -146,8 +146,8 @@ void getSuperClassNameWhenHasSuperClassReturnsName() { @Test void getSuperClassNameWhenHasNoSuperClassReturnsNull() { assertThat(get(Object.class).getSuperClassName()).isNull(); - assertThat(get(TestInterface.class).getSuperClassName()).isIn(null, "java.lang.Object"); - assertThat(get(TestSubInterface.class).getSuperClassName()).isIn(null, "java.lang.Object"); + assertThat(get(TestInterface.class).getSuperClassName()).isNull(); + assertThat(get(TestSubInterface.class).getSuperClassName()).isNull(); } @Test @@ -210,17 +210,6 @@ void getAllAnnotationAttributesReturnsAllAttributes() { assertThat(attributes.get("size")).containsExactlyInAnyOrder(1, 2); } - @Test - void getComplexAttributeTypesReturnsAll() { - MultiValueMap attributes = - get(WithComplexAttributeTypes.class).getAllAnnotationAttributes(ComplexAttributes.class.getName()); - assertThat(attributes).containsOnlyKeys("names", "count", "type"); - assertThat(attributes.get("names")).hasSize(1); - assertThat(attributes.get("names").get(0)).isEqualTo(new String[]{"first", "second"}); - assertThat(attributes.get("count")).containsExactlyInAnyOrder(TestEnum.ONE); - assertThat(attributes.get("type")).containsExactlyInAnyOrder(TestEnum.class); - } - @Test void getAnnotationTypesReturnsDirectAnnotations() { AnnotationMetadata metadata = get(WithDirectAnnotations.class); @@ -418,22 +407,4 @@ public static class WithMetaAnnotationAttributes { } - @ComplexAttributes(names = {"first", "second"}, count = TestEnum.ONE, type = TestEnum.class) - public static class WithComplexAttributeTypes { - } - - @Retention(RetentionPolicy.RUNTIME) - public @interface ComplexAttributes { - - String[] names(); - - TestEnum count(); - - Class type(); - } - - public enum TestEnum { - ONE, TWO, THREE - } - } diff --git a/spring-core/src/test/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataTests.java b/spring-core/src/test/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataTests.java index 8f8e50c40fd3..f66c81bc35d3 100644 --- a/spring-core/src/test/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataTests.java +++ b/spring-core/src/test/java/org/springframework/core/type/classreading/SimpleAnnotationMetadataTests.java @@ -30,8 +30,9 @@ class SimpleAnnotationMetadataTests extends AbstractAnnotationMetadataTests { @Override protected AnnotationMetadata get(Class source) { try { - return MetadataReaderFactory.create(source.getClassLoader()) - .getMetadataReader(source.getName()).getAnnotationMetadata(); + return new SimpleMetadataReaderFactory( + source.getClassLoader()).getMetadataReader( + source.getName()).getAnnotationMetadata(); } catch (Exception ex) { throw new IllegalStateException(ex); From a0cc6419f490bde1b786ed7e2cb1f27040b69107 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Thu, 28 Nov 2024 17:03:39 +0000 Subject: [PATCH 016/701] Polishing in DefaultResponseErrorHandler See gh-33980 --- .../client/DefaultResponseErrorHandler.java | 140 +++++++++--------- .../ExtractingResponseErrorHandler.java | 50 ++++--- .../DefaultResponseErrorHandlerTests.java | 24 +-- .../ExtractingResponseErrorHandlerTests.java | 44 +++--- 4 files changed, 127 insertions(+), 131 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java b/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java index 485e70b132a1..11ecdb5795ff 100644 --- a/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java @@ -38,7 +38,6 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; -import org.springframework.util.FileCopyUtils; import org.springframework.util.ObjectUtils; /** @@ -135,8 +134,7 @@ protected boolean hasError(int statusCode) { */ @Override public void handleError(ClientHttpResponse response) throws IOException { - HttpStatusCode statusCode = response.getStatusCode(); - handleError(response, statusCode, null, null); + handleError(response, response.getStatusCode(), null, null); } /** @@ -159,46 +157,7 @@ public void handleError(ClientHttpResponse response) throws IOException { */ @Override public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { - HttpStatusCode statusCode = response.getStatusCode(); - handleError(response, statusCode, url, method); - } - - /** - * Return error message with details from the response body. For example: - *
-	 * 404 Not Found on GET request for "https://example.com": [{'id': 123, 'message': 'my message'}]
-	 * 
- */ - private String getErrorMessage(int rawStatusCode, String statusText, @Nullable byte[] responseBody, @Nullable Charset charset, - @Nullable URI url, @Nullable HttpMethod method) { - - StringBuilder msg = new StringBuilder(rawStatusCode + " " + statusText); - if (method != null) { - msg.append(" on ").append(method).append(" request"); - } - if (url != null) { - msg.append(" for \""); - String urlString = url.toString(); - int idx = urlString.indexOf('?'); - if (idx != -1) { - msg.append(urlString, 0, idx); - } - else { - msg.append(urlString); - } - msg.append("\""); - } - msg.append(": "); - if (ObjectUtils.isEmpty(responseBody)) { - msg.append("[no body]"); - } - else { - charset = (charset != null ? charset : StandardCharsets.UTF_8); - String bodyText = new String(responseBody, charset); - bodyText = LogFormatUtils.formatValue(bodyText, -1, true); - msg.append(bodyText); - } - return msg.toString(); + handleError(response, response.getStatusCode(), url, method); } /** @@ -211,7 +170,8 @@ private String getErrorMessage(int rawStatusCode, String statusText, @Nullable b * @see HttpClientErrorException#create * @see HttpServerErrorException#create */ - protected void handleError(ClientHttpResponse response, HttpStatusCode statusCode, + protected void handleError( + ClientHttpResponse response, HttpStatusCode statusCode, @Nullable URI url, @Nullable HttpMethod method) throws IOException { String statusText = response.getStatusText(); @@ -238,6 +198,68 @@ else if (statusCode.is5xxServerError()) { throw ex; } + /** + * Read the body of the given response (for inclusion in a status exception). + * @param response the response to inspect + * @return the response body as a byte array, + * or an empty byte array if the body could not be read + * @since 4.3.8 + */ + protected byte[] getResponseBody(ClientHttpResponse response) { + return RestClientUtils.getBody(response); + } + + /** + * Determine the charset of the response (for inclusion in a status exception). + * @param response the response to inspect + * @return the associated charset, or {@code null} if none + * @since 4.3.8 + */ + @Nullable + protected Charset getCharset(ClientHttpResponse response) { + MediaType contentType = response.getHeaders().getContentType(); + return (contentType != null ? contentType.getCharset() : null); + } + + /** + * Return an error message with details from the response body. For example: + *
+	 * 404 Not Found on GET request for "https://example.com": [{'id': 123, 'message': 'my message'}]
+	 * 
+ */ + private String getErrorMessage( + int rawStatusCode, String statusText, @Nullable byte[] responseBody, @Nullable Charset charset, + @Nullable URI url, @Nullable HttpMethod method) { + + StringBuilder msg = new StringBuilder(rawStatusCode + " " + statusText); + if (method != null) { + msg.append(" on ").append(method).append(" request"); + } + if (url != null) { + msg.append(" for \""); + String urlString = url.toString(); + int idx = urlString.indexOf('?'); + if (idx != -1) { + msg.append(urlString, 0, idx); + } + else { + msg.append(urlString); + } + msg.append("\""); + } + msg.append(": "); + if (ObjectUtils.isEmpty(responseBody)) { + msg.append("[no body]"); + } + else { + charset = (charset != null ? charset : StandardCharsets.UTF_8); + String bodyText = new String(responseBody, charset); + bodyText = LogFormatUtils.formatValue(bodyText, -1, true); + msg.append(bodyText); + } + return msg.toString(); + } + /** * Return a function for decoding the error content. This can be passed to * {@link RestClientResponseException#setBodyConvertFunction(Function)}. @@ -265,34 +287,4 @@ public InputStream getBody() { }; } - /** - * Read the body of the given response (for inclusion in a status exception). - * @param response the response to inspect - * @return the response body as a byte array, - * or an empty byte array if the body could not be read - * @since 4.3.8 - */ - protected byte[] getResponseBody(ClientHttpResponse response) { - try { - return FileCopyUtils.copyToByteArray(response.getBody()); - } - catch (IOException ex) { - // ignore - } - return new byte[0]; - } - - /** - * Determine the charset of the response (for inclusion in a status exception). - * @param response the response to inspect - * @return the associated charset, or {@code null} if none - * @since 4.3.8 - */ - @Nullable - protected Charset getCharset(ClientHttpResponse response) { - HttpHeaders headers = response.getHeaders(); - MediaType contentType = headers.getContentType(); - return (contentType != null ? contentType.getCharset() : null); - } - } diff --git a/spring-web/src/main/java/org/springframework/web/client/ExtractingResponseErrorHandler.java b/spring-web/src/main/java/org/springframework/web/client/ExtractingResponseErrorHandler.java index c411b133771f..578a124abaa9 100644 --- a/spring-web/src/main/java/org/springframework/web/client/ExtractingResponseErrorHandler.java +++ b/spring-web/src/main/java/org/springframework/web/client/ExtractingResponseErrorHandler.java @@ -32,26 +32,27 @@ import org.springframework.util.CollectionUtils; /** - * Implementation of {@link ResponseErrorHandler} that uses {@link HttpMessageConverter - * HttpMessageConverters} to convert HTTP error responses to {@link RestClientException - * RestClientExceptions}. + * Implementation of {@link ResponseErrorHandler} that uses + * {@link HttpMessageConverter HttpMessageConverters} to convert HTTP error + * responses to {@link RestClientException RestClientExceptions}. * *

To use this error handler, you must specify a * {@linkplain #setStatusMapping(Map) status mapping} and/or a - * {@linkplain #setSeriesMapping(Map) series mapping}. If either of these mappings has a match - * for the {@linkplain ClientHttpResponse#getStatusCode() status code} of a given - * {@code ClientHttpResponse}, {@link #hasError(ClientHttpResponse)} will return - * {@code true}, and {@link #handleError(ClientHttpResponse)} will attempt to use the - * {@linkplain #setMessageConverters(List) configured message converters} to convert the response - * into the mapped subclass of {@link RestClientException}. Note that the - * {@linkplain #setStatusMapping(Map) status mapping} takes precedence over - * {@linkplain #setSeriesMapping(Map) series mapping}. + * {@linkplain #setSeriesMapping(Map) series mapping}. If either of these + * mappings has a match for the {@linkplain ClientHttpResponse#getStatusCode() + * status code} of a given {@code ClientHttpResponse}, + * {@link #hasError(ClientHttpResponse)} will return {@code true}, and + * {@link #handleError(ClientHttpResponse)} will attempt to use the + * {@linkplain #setMessageConverters(List) configured message converters} to + * convert the response into the mapped subclass of {@link RestClientException}. + * Note that the {@linkplain #setStatusMapping(Map) status mapping} takes + * precedence over {@linkplain #setSeriesMapping(Map) series mapping}. * *

If there is no match, this error handler will default to the behavior of - * {@link DefaultResponseErrorHandler}. Note that you can override this default behavior - * by specifying a {@linkplain #setSeriesMapping(Map) series mapping} from - * {@code HttpStatus.Series#CLIENT_ERROR} and/or {@code HttpStatus.Series#SERVER_ERROR} - * to {@code null}. + * {@link DefaultResponseErrorHandler}. Note that you can override this default + * behavior by specifying a {@linkplain #setSeriesMapping(Map) series mapping} + * from {@code HttpStatus.Series#CLIENT_ERROR} and/or + * {@code HttpStatus.Series#SERVER_ERROR} to {@code null}. * * @author Simon Galperin * @author Arjen Poutsma @@ -126,11 +127,11 @@ public void setSeriesMapping(Map exceptionClass, - ClientHttpResponse response) throws IOException { + private void extract( + @Nullable Class exceptionClass, ClientHttpResponse response) + throws IOException { if (exceptionClass == null) { return; @@ -165,6 +166,7 @@ private void extract(@Nullable Class exceptionCla HttpMessageConverterExtractor extractor = new HttpMessageConverterExtractor<>(exceptionClass, this.messageConverters); + RestClientException exception = extractor.extractData(response); if (exception != null) { throw exception; diff --git a/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerTests.java b/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerTests.java index 967b2c4fbe2d..576070361dd0 100644 --- a/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerTests.java @@ -19,7 +19,6 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; -import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; @@ -32,6 +31,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.StreamUtils; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.catchThrowable; @@ -72,7 +72,7 @@ void handleError() throws Exception { given(response.getStatusCode()).willReturn(HttpStatus.NOT_FOUND); given(response.getStatusText()).willReturn("Not Found"); given(response.getHeaders()).willReturn(headers); - given(response.getBody()).willReturn(new ByteArrayInputStream("Hello World".getBytes(StandardCharsets.UTF_8))); + given(response.getBody()).willReturn(new ByteArrayInputStream("Hello World".getBytes(UTF_8))); assertThatExceptionOfType(HttpClientErrorException.class) .isThrownBy(() -> handler.handleError(response)) @@ -90,18 +90,20 @@ void handleErrorWithUrlAndMethod() throws Exception { @Test void handleErrorWithUrlAndQueryParameters() throws Exception { + String url = "https://example.com/resource"; setupClientHttpResponse(HttpStatus.NOT_FOUND, "Hello World"); assertThatExceptionOfType(HttpClientErrorException.class) - .isThrownBy(() -> handler.handleError(URI.create("https://example.com/resource?access_token=123"), HttpMethod.GET, response)) - .withMessage("404 Not Found on GET request for \"https://example.com/resource\": \"Hello World\""); + .isThrownBy(() -> handler.handleError(URI.create(url + "?access_token=123"), HttpMethod.GET, response)) + .withMessage("404 Not Found on GET request for \"" + url + "\": \"Hello World\""); } @Test void handleErrorWithUrlAndNoBody() throws Exception { + String url = "https://example.com"; setupClientHttpResponse(HttpStatus.NOT_FOUND, null); assertThatExceptionOfType(HttpClientErrorException.class) - .isThrownBy(() -> handler.handleError(URI.create("https://example.com"), HttpMethod.GET, response)) - .withMessage("404 Not Found on GET request for \"https://example.com\": [no body]"); + .isThrownBy(() -> handler.handleError(URI.create(url), HttpMethod.GET, response)) + .withMessage("404 Not Found on GET request for \"" + url + "\": [no body]"); } private void setupClientHttpResponse(HttpStatus status, @Nullable String textBody) throws Exception { @@ -110,7 +112,7 @@ private void setupClientHttpResponse(HttpStatus status, @Nullable String textBod given(response.getStatusText()).willReturn(status.getReasonPhrase()); if (textBody != null) { headers.setContentType(MediaType.TEXT_PLAIN); - given(response.getBody()).willReturn(new ByteArrayInputStream(textBody.getBytes(StandardCharsets.UTF_8))); + given(response.getBody()).willReturn(new ByteArrayInputStream(textBody.getBytes(UTF_8))); } given(response.getHeaders()).willReturn(headers); } @@ -187,7 +189,7 @@ void handleErrorForCustomClientError() throws Exception { headers.setContentType(MediaType.TEXT_PLAIN); String responseBody = "Hello World"; - TestByteArrayInputStream body = new TestByteArrayInputStream(responseBody.getBytes(StandardCharsets.UTF_8)); + TestByteArrayInputStream body = new TestByteArrayInputStream(responseBody.getBytes(UTF_8)); given(response.getStatusCode()).willReturn(statusCode); given(response.getStatusText()).willReturn(statusText); @@ -227,7 +229,7 @@ void handleErrorForCustomServerError() throws Exception { headers.setContentType(MediaType.TEXT_PLAIN); String responseBody = "Hello World"; - TestByteArrayInputStream body = new TestByteArrayInputStream(responseBody.getBytes(StandardCharsets.UTF_8)); + TestByteArrayInputStream body = new TestByteArrayInputStream(responseBody.getBytes(UTF_8)); given(response.getStatusCode()).willReturn(statusCode); given(response.getStatusText()).willReturn(statusText); @@ -250,7 +252,7 @@ void handleErrorForCustomServerError() throws Exception { public void bodyAvailableAfterHasErrorForUnknownStatusCode() throws Exception { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.TEXT_PLAIN); - TestByteArrayInputStream body = new TestByteArrayInputStream("Hello World".getBytes(StandardCharsets.UTF_8)); + TestByteArrayInputStream body = new TestByteArrayInputStream("Hello World".getBytes(UTF_8)); given(response.getStatusCode()).willReturn(HttpStatusCode.valueOf(999)); given(response.getStatusText()).willReturn("Custom status code"); @@ -259,7 +261,7 @@ public void bodyAvailableAfterHasErrorForUnknownStatusCode() throws Exception { assertThat(handler.hasError(response)).isFalse(); assertThat(body.isClosed()).isFalse(); - assertThat(StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8)).isEqualTo("Hello World"); + assertThat(StreamUtils.copyToString(response.getBody(), UTF_8)).isEqualTo("Hello World"); } diff --git a/spring-web/src/test/java/org/springframework/web/client/ExtractingResponseErrorHandlerTests.java b/spring-web/src/test/java/org/springframework/web/client/ExtractingResponseErrorHandlerTests.java index e28b623cf5e4..a65951ea4f35 100644 --- a/spring-web/src/test/java/org/springframework/web/client/ExtractingResponseErrorHandlerTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/ExtractingResponseErrorHandlerTests.java @@ -19,6 +19,8 @@ import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import java.util.Collections; +import java.util.List; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -36,8 +38,11 @@ import static org.mockito.Mockito.mock; /** + * Unit tests for {@link ExtractingResponseErrorHandler}. + * * @author Arjen Poutsma */ +@SuppressWarnings("ALL") class ExtractingResponseErrorHandlerTests { private ExtractingResponseErrorHandler errorHandler; @@ -48,13 +53,10 @@ class ExtractingResponseErrorHandlerTests { @BeforeEach void setup() { HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); - this.errorHandler = new ExtractingResponseErrorHandler( - Collections.singletonList(converter)); + this.errorHandler = new ExtractingResponseErrorHandler(List.of(converter)); - this.errorHandler.setStatusMapping( - Collections.singletonMap(HttpStatus.I_AM_A_TEAPOT, MyRestClientException.class)); - this.errorHandler.setSeriesMapping(Collections - .singletonMap(HttpStatus.Series.SERVER_ERROR, MyRestClientException.class)); + this.errorHandler.setStatusMapping(Map.of(HttpStatus.I_AM_A_TEAPOT, MyRestClientException.class)); + this.errorHandler.setSeriesMapping(Map.of(HttpStatus.Series.SERVER_ERROR, MyRestClientException.class)); } @@ -72,8 +74,7 @@ void hasError() throws Exception { @Test void hasErrorOverride() throws Exception { - this.errorHandler.setSeriesMapping(Collections - .singletonMap(HttpStatus.Series.CLIENT_ERROR, null)); + this.errorHandler.setSeriesMapping(Collections.singletonMap(HttpStatus.Series.CLIENT_ERROR, null)); given(this.response.getStatusCode()).willReturn(HttpStatus.I_AM_A_TEAPOT); assertThat(this.errorHandler.hasError(this.response)).isTrue(); @@ -96,9 +97,9 @@ void handleErrorStatusMatch() throws Exception { responseHeaders.setContentLength(body.length); given(this.response.getBody()).willReturn(new ByteArrayInputStream(body)); - assertThatExceptionOfType(MyRestClientException.class).isThrownBy(() -> - this.errorHandler.handleError(this.response)) - .satisfies(ex -> assertThat(ex.getFoo()).isEqualTo("bar")); + assertThatExceptionOfType(MyRestClientException.class) + .isThrownBy(() -> this.errorHandler.handleError(this.response)) + .satisfies(ex -> assertThat(ex.getFoo()).isEqualTo("bar")); } @Test @@ -112,9 +113,9 @@ void handleErrorSeriesMatch() throws Exception { responseHeaders.setContentLength(body.length); given(this.response.getBody()).willReturn(new ByteArrayInputStream(body)); - assertThatExceptionOfType(MyRestClientException.class).isThrownBy(() -> - this.errorHandler.handleError(this.response)) - .satisfies(ex -> assertThat(ex.getFoo()).isEqualTo("bar")); + assertThatExceptionOfType(MyRestClientException.class) + .isThrownBy(() -> this.errorHandler.handleError(this.response)) + .satisfies(ex -> assertThat(ex.getFoo()).isEqualTo("bar")); } @Test @@ -128,18 +129,17 @@ void handleNoMatch() throws Exception { responseHeaders.setContentLength(body.length); given(this.response.getBody()).willReturn(new ByteArrayInputStream(body)); - assertThatExceptionOfType(HttpClientErrorException.class).isThrownBy(() -> - this.errorHandler.handleError(this.response)) - .satisfies(ex -> { - assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); - assertThat(ex.getResponseBodyAsByteArray()).isEqualTo(body); - }); + assertThatExceptionOfType(HttpClientErrorException.class) + .isThrownBy(() -> this.errorHandler.handleError(this.response)) + .satisfies(ex -> { + assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + assertThat(ex.getResponseBodyAsByteArray()).isEqualTo(body); + }); } @Test void handleNoMatchOverride() throws Exception { - this.errorHandler.setSeriesMapping(Collections - .singletonMap(HttpStatus.Series.CLIENT_ERROR, null)); + this.errorHandler.setSeriesMapping(Collections.singletonMap(HttpStatus.Series.CLIENT_ERROR, null)); given(this.response.getStatusCode()).willReturn(HttpStatus.NOT_FOUND); HttpHeaders responseHeaders = new HttpHeaders(); From 89b2a6500e9fcd5f996e3784111a975c89f1d2cf Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Thu, 28 Nov 2024 17:57:32 +0000 Subject: [PATCH 017/701] DefaultResponseErrorHandler updates Deprecate handleError(response), and ensure it continues to be invoked if overridden. Closes gh-33980 --- .../client/DefaultResponseErrorHandler.java | 36 +++++++------------ .../ExtractingResponseErrorHandler.java | 25 +++++++------ .../web/client/NoOpResponseErrorHandler.java | 5 --- .../web/client/ResponseErrorHandler.java | 21 ++++++----- ...ltResponseErrorHandlerHttpStatusTests.java | 5 ++- .../DefaultResponseErrorHandlerTests.java | 15 ++++---- .../ExtractingResponseErrorHandlerTests.java | 10 +++--- .../client/RestClientObservationTests.java | 4 +-- .../client/RestTemplateObservationTests.java | 4 +-- 9 files changed, 62 insertions(+), 63 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java b/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java index 11ecdb5795ff..e08ae70360de 100644 --- a/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java @@ -49,8 +49,8 @@ * {@link #hasError(HttpStatusCode)}. Unknown status codes will be ignored by * {@link #hasError(ClientHttpResponse)}. * - *

See {@link #handleError(ClientHttpResponse)} for more details on specific - * exception types. + *

See {@link #handleError(URI, HttpMethod, ClientHttpResponse)} for more + * details on specific exception types. * * @author Arjen Poutsma * @author Rossen Stoyanchev @@ -116,27 +116,6 @@ protected boolean hasError(int statusCode) { return (series == HttpStatus.Series.CLIENT_ERROR || series == HttpStatus.Series.SERVER_ERROR); } - /** - * Handle the error in the given response with the given resolved status code. - *

The default implementation throws: - *

    - *
  • {@link HttpClientErrorException} if the status code is in the 4xx - * series, or one of its sub-classes such as - * {@link HttpClientErrorException.BadRequest} and others. - *
  • {@link HttpServerErrorException} if the status code is in the 5xx - * series, or one of its sub-classes such as - * {@link HttpServerErrorException.InternalServerError} and others. - *
  • {@link UnknownHttpStatusCodeException} for error status codes not in the - * {@link HttpStatus} enum range. - *
- * @throws UnknownHttpStatusCodeException in case of an unresolvable status code - * @see #handleError(ClientHttpResponse, HttpStatusCode, URI, HttpMethod) - */ - @Override - public void handleError(ClientHttpResponse response) throws IOException { - handleError(response, response.getStatusCode(), null, null); - } - /** * Handle the error in the given response with the given resolved status code * and extra information providing access to the request URL and HTTP method. @@ -157,9 +136,20 @@ public void handleError(ClientHttpResponse response) throws IOException { */ @Override public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { + handleError(response); handleError(response, response.getStatusCode(), url, method); } + /** + * {@inheritDoc} + *

As of 6.2.1 this method is a no-op unless overridden. + */ + @SuppressWarnings("removal") + @Override + public void handleError(ClientHttpResponse response) throws IOException { + // no-op, but here for backwards compatibility + } + /** * Handle the error based on the resolved status code. *

The default implementation delegates to diff --git a/spring-web/src/main/java/org/springframework/web/client/ExtractingResponseErrorHandler.java b/spring-web/src/main/java/org/springframework/web/client/ExtractingResponseErrorHandler.java index 578a124abaa9..e2c95a992b29 100644 --- a/spring-web/src/main/java/org/springframework/web/client/ExtractingResponseErrorHandler.java +++ b/spring-web/src/main/java/org/springframework/web/client/ExtractingResponseErrorHandler.java @@ -42,11 +42,12 @@ * mappings has a match for the {@linkplain ClientHttpResponse#getStatusCode() * status code} of a given {@code ClientHttpResponse}, * {@link #hasError(ClientHttpResponse)} will return {@code true}, and - * {@link #handleError(ClientHttpResponse)} will attempt to use the - * {@linkplain #setMessageConverters(List) configured message converters} to - * convert the response into the mapped subclass of {@link RestClientException}. - * Note that the {@linkplain #setStatusMapping(Map) status mapping} takes - * precedence over {@linkplain #setSeriesMapping(Map) series mapping}. + * {@link #handleError(ClientHttpResponse, HttpStatusCode, URI, HttpMethod)} + * will attempt to use the {@linkplain #setMessageConverters(List) configured + * message converters} to convert the response into the mapped subclass of + * {@link RestClientException}. Note that the + * {@linkplain #setStatusMapping(Map) status mapping} takes precedence over + * {@linkplain #setSeriesMapping(Map) series mapping}. * *

If there is no match, this error handler will default to the behavior of * {@link DefaultResponseErrorHandler}. Note that you can override this default @@ -98,9 +99,10 @@ public void setMessageConverters(List> messageConverters * If this mapping has a match * for the {@linkplain ClientHttpResponse#getStatusCode() status code} of a given * {@code ClientHttpResponse}, {@link #hasError(ClientHttpResponse)} will return - * {@code true} and {@link #handleError(ClientHttpResponse)} will attempt to use the - * {@linkplain #setMessageConverters(List) configured message converters} to convert the - * response into the mapped subclass of {@link RestClientException}. + * {@code true} and {@link #handleError(ClientHttpResponse, HttpStatusCode, URI, HttpMethod)} + * will attempt to use the {@linkplain #setMessageConverters(List) configured + * message converters} to convert the response into the mapped subclass of + * {@link RestClientException}. */ public void setStatusMapping(Map> statusMapping) { if (!CollectionUtils.isEmpty(statusMapping)) { @@ -113,9 +115,10 @@ public void setStatusMapping(Map> seriesMapping) { if (!CollectionUtils.isEmpty(seriesMapping)) { diff --git a/spring-web/src/main/java/org/springframework/web/client/NoOpResponseErrorHandler.java b/spring-web/src/main/java/org/springframework/web/client/NoOpResponseErrorHandler.java index af0f22a52b12..4d1498b40304 100644 --- a/spring-web/src/main/java/org/springframework/web/client/NoOpResponseErrorHandler.java +++ b/spring-web/src/main/java/org/springframework/web/client/NoOpResponseErrorHandler.java @@ -43,9 +43,4 @@ public boolean hasError(ClientHttpResponse response) throws IOException { return false; } - @Override - public void handleError(ClientHttpResponse response) throws IOException { - // never actually called - } - } diff --git a/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java b/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java index db2329f8fef0..4ca64425f804 100644 --- a/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java +++ b/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java @@ -45,14 +45,6 @@ public interface ResponseErrorHandler { * Handle the error in the given response. *

This method is only called when {@link #hasError(ClientHttpResponse)} * has returned {@code true}. - * @param response the response with the error - * @throws IOException in case of I/O errors - */ - void handleError(ClientHttpResponse response) throws IOException; - - /** - * Alternative to {@link #handleError(ClientHttpResponse)} with extra - * information providing access to the request URL and HTTP method. * @param url the request URL * @param method the HTTP method * @param response the response with the error @@ -63,4 +55,17 @@ default void handleError(URI url, HttpMethod method, ClientHttpResponse response handleError(response); } + /** + * Handle the error in the given response. + *

This method is only called when {@link #hasError(ClientHttpResponse)} + * has returned {@code true}. + * @param response the response with the error + * @throws IOException in case of I/O errors + * @deprecated in favor of {@link #handleError(URI, HttpMethod, ClientHttpResponse)} + */ + @Deprecated(since = "6.2.1", forRemoval = true) + default void handleError(ClientHttpResponse response) throws IOException { + // no-op unless overridden + } + } diff --git a/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerHttpStatusTests.java b/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerHttpStatusTests.java index 328d0259ec4b..e7d6a94eacf0 100644 --- a/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerHttpStatusTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerHttpStatusTests.java @@ -16,6 +16,7 @@ package org.springframework.web.client; +import java.net.URI; import java.util.stream.Stream; import org.junit.jupiter.api.DisplayName; @@ -24,6 +25,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpResponse; @@ -80,7 +82,8 @@ void handleErrorException(HttpStatus httpStatus, Class expe given(this.response.getStatusCode()).willReturn(httpStatus); given(this.response.getHeaders()).willReturn(headers); - assertThatExceptionOfType(expectedExceptionClass).isThrownBy(() -> this.handler.handleError(this.response)); + assertThatExceptionOfType(expectedExceptionClass) + .isThrownBy(() -> this.handler.handleError(URI.create("/"), HttpMethod.GET, this.response)); } static Stream errorCodes() { diff --git a/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerTests.java b/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerTests.java index 576070361dd0..fbe2f9d318ca 100644 --- a/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/DefaultResponseErrorHandlerTests.java @@ -75,8 +75,8 @@ void handleError() throws Exception { given(response.getBody()).willReturn(new ByteArrayInputStream("Hello World".getBytes(UTF_8))); assertThatExceptionOfType(HttpClientErrorException.class) - .isThrownBy(() -> handler.handleError(response)) - .withMessage("404 Not Found: \"Hello World\"") + .isThrownBy(() -> handler.handleError(URI.create("/"), HttpMethod.GET, response)) + .withMessage("404 Not Found on GET request for \"/\": \"Hello World\"") .satisfies(ex -> assertThat(ex.getResponseHeaders()).isEqualTo(headers)); } @@ -127,7 +127,8 @@ void handleErrorIOException() throws Exception { given(response.getHeaders()).willReturn(headers); given(response.getBody()).willThrow(new IOException()); - assertThatExceptionOfType(HttpClientErrorException.class).isThrownBy(() -> handler.handleError(response)); + assertThatExceptionOfType(HttpClientErrorException.class) + .isThrownBy(() -> handler.handleError(URI.create("/"), HttpMethod.GET, response)); } @Test @@ -140,7 +141,7 @@ void handleErrorNullResponse() throws Exception { given(response.getHeaders()).willReturn(headers); assertThatExceptionOfType(HttpClientErrorException.class).isThrownBy(() -> - handler.handleError(response)); + handler.handleError(URI.create("/"), HttpMethod.GET, response)); } @Test // SPR-16108 @@ -165,7 +166,7 @@ public void handleErrorUnknownStatusCode() throws Exception { given(response.getHeaders()).willReturn(headers); assertThatExceptionOfType(UnknownHttpStatusCodeException.class).isThrownBy(() -> - handler.handleError(response)); + handler.handleError(URI.create("/"), HttpMethod.GET, response)); } @Test // SPR-17461 @@ -196,7 +197,7 @@ void handleErrorForCustomClientError() throws Exception { given(response.getHeaders()).willReturn(headers); given(response.getBody()).willReturn(body); - Throwable throwable = catchThrowable(() -> handler.handleError(response)); + Throwable throwable = catchThrowable(() -> handler.handleError(URI.create("/"), HttpMethod.GET, response)); // validate exception assertThat(throwable).isInstanceOf(HttpClientErrorException.class); @@ -236,7 +237,7 @@ void handleErrorForCustomServerError() throws Exception { given(response.getHeaders()).willReturn(headers); given(response.getBody()).willReturn(body); - Throwable throwable = catchThrowable(() -> handler.handleError(response)); + Throwable throwable = catchThrowable(() -> handler.handleError(URI.create("/"), HttpMethod.GET, response)); // validate exception assertThat(throwable).isInstanceOf(HttpServerErrorException.class); diff --git a/spring-web/src/test/java/org/springframework/web/client/ExtractingResponseErrorHandlerTests.java b/spring-web/src/test/java/org/springframework/web/client/ExtractingResponseErrorHandlerTests.java index a65951ea4f35..1af510711285 100644 --- a/spring-web/src/test/java/org/springframework/web/client/ExtractingResponseErrorHandlerTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/ExtractingResponseErrorHandlerTests.java @@ -17,6 +17,7 @@ package org.springframework.web.client; import java.io.ByteArrayInputStream; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; @@ -26,6 +27,7 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpResponse; @@ -98,7 +100,7 @@ void handleErrorStatusMatch() throws Exception { given(this.response.getBody()).willReturn(new ByteArrayInputStream(body)); assertThatExceptionOfType(MyRestClientException.class) - .isThrownBy(() -> this.errorHandler.handleError(this.response)) + .isThrownBy(() -> this.errorHandler.handleError(URI.create("/"), HttpMethod.GET, this.response)) .satisfies(ex -> assertThat(ex.getFoo()).isEqualTo("bar")); } @@ -114,7 +116,7 @@ void handleErrorSeriesMatch() throws Exception { given(this.response.getBody()).willReturn(new ByteArrayInputStream(body)); assertThatExceptionOfType(MyRestClientException.class) - .isThrownBy(() -> this.errorHandler.handleError(this.response)) + .isThrownBy(() -> this.errorHandler.handleError(URI.create("/"), HttpMethod.GET, this.response)) .satisfies(ex -> assertThat(ex.getFoo()).isEqualTo("bar")); } @@ -130,7 +132,7 @@ void handleNoMatch() throws Exception { given(this.response.getBody()).willReturn(new ByteArrayInputStream(body)); assertThatExceptionOfType(HttpClientErrorException.class) - .isThrownBy(() -> this.errorHandler.handleError(this.response)) + .isThrownBy(() -> this.errorHandler.handleError(URI.create("/"), HttpMethod.GET, this.response)) .satisfies(ex -> { assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); assertThat(ex.getResponseBodyAsByteArray()).isEqualTo(body); @@ -150,7 +152,7 @@ void handleNoMatchOverride() throws Exception { responseHeaders.setContentLength(body.length); given(this.response.getBody()).willReturn(new ByteArrayInputStream(body)); - this.errorHandler.handleError(this.response); + this.errorHandler.handleError(URI.create("/"), HttpMethod.GET, this.response); } diff --git a/spring-web/src/test/java/org/springframework/web/client/RestClientObservationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestClientObservationTests.java index 9a438ea866c4..1624f25719eb 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestClientObservationTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestClientObservationTests.java @@ -345,12 +345,12 @@ static class ObservationErrorHandler implements ResponseErrorHandler { } @Override - public boolean hasError(ClientHttpResponse response) throws IOException { + public boolean hasError(ClientHttpResponse response) { return true; } @Override - public void handleError(ClientHttpResponse response) throws IOException { + public void handleError(URI uri, HttpMethod httpMethod, ClientHttpResponse response) { assertThat(this.observationRegistry.getCurrentObservationScope()).isNotNull(); } } diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateObservationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateObservationTests.java index 9dff87051c17..b8a2ad99d313 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateObservationTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateObservationTests.java @@ -242,12 +242,12 @@ static class ObservationErrorHandler implements ResponseErrorHandler { } @Override - public boolean hasError(ClientHttpResponse response) throws IOException { + public boolean hasError(ClientHttpResponse response) { return true; } @Override - public void handleError(ClientHttpResponse response) throws IOException { + public void handleError(URI uri, HttpMethod httpMethod, ClientHttpResponse response) { currentObservation = this.observationRegistry.getCurrentObservation(); } } From 431d726dc6e2449cf012012ea3a2da0072a5387d Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 29 Nov 2024 09:44:27 +0100 Subject: [PATCH 018/701] Use Jakarta 11 javadoc link See gh-33918 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 07baddadf7bb..c14167deaa26 100644 --- a/build.gradle +++ b/build.gradle @@ -89,7 +89,7 @@ configure([rootProject] + javaProjects) { project -> ext.javadocLinks = [ "https://docs.oracle.com/en/java/javase/17/docs/api/", - "https://jakarta.ee/specifications/platform/9/apidocs/", + "https://jakarta.ee/specifications/platform/11/apidocs/", "https://docs.jboss.org/hibernate/orm/5.6/javadocs/", "https://eclipse.dev/aspectj/doc/released/aspectj5rt-api", "https://www.quartz-scheduler.org/api/2.3.0/", From fa01e9c566b4f212ce995ad3a67cc69830c83604 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Fri, 29 Nov 2024 10:30:29 +0000 Subject: [PATCH 019/701] Use response decorator to check if error handled Closes gh-33980 --- .../client/DefaultResponseErrorHandler.java | 41 ++++++++++++++++--- .../web/client/ResponseErrorHandler.java | 1 - 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java b/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java index e08ae70360de..1388224300b4 100644 --- a/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java @@ -136,18 +136,29 @@ protected boolean hasError(int statusCode) { */ @Override public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { - handleError(response); + + // For backwards compatibility try handle(response) first + HandleErrorResponseDecorator decorator = new HandleErrorResponseDecorator(response); + handleError(decorator); + if (decorator.isHandled()) { + return; + } + handleError(response, response.getStatusCode(), url, method); } - /** - * {@inheritDoc} - *

As of 6.2.1 this method is a no-op unless overridden. - */ @SuppressWarnings("removal") @Override public void handleError(ClientHttpResponse response) throws IOException { - // no-op, but here for backwards compatibility + + // Called via handleError(url, method, response) + if (response instanceof HandleErrorResponseDecorator decorator) { + decorator.setNotHandled(); + return; + } + + // Called directly, so do handle + handleError(response, response.getStatusCode(), null, null); } /** @@ -277,4 +288,22 @@ public InputStream getBody() { }; } + + private static class HandleErrorResponseDecorator extends ClientHttpResponseDecorator { + + private boolean handled = true; + + public HandleErrorResponseDecorator(ClientHttpResponse delegate) { + super(delegate); + } + + public void setNotHandled() { + this.handled = false; + } + + public boolean isHandled() { + return this.handled; + } + } + } diff --git a/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java b/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java index 4ca64425f804..be96fb29a11a 100644 --- a/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java +++ b/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java @@ -65,7 +65,6 @@ default void handleError(URI url, HttpMethod method, ClientHttpResponse response */ @Deprecated(since = "6.2.1", forRemoval = true) default void handleError(ClientHttpResponse response) throws IOException { - // no-op unless overridden } } From 989eb37fb7e03bea0bcb66da9bd7846dd3c12a21 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 29 Nov 2024 14:43:52 +0100 Subject: [PATCH 020/701] Update AOT resource hints with new GraalVM behavior Prior to this commit, the resource hints for AOT applications would generate JSON metadata with `java.util.regex.Pattern`, like: ``` { "resources": { "includes": [ { "pattern": "\\Qbanner.txt\\E" } ] } } ``` This regexp feature, as well as "includes" and "excludes" are not supported anymore with the new metadata format. This commit removes the pattern format which is now replaced by a "glob" format. "globs" should only contain "*" (zero or more characters in a path segment) or "**" (zero or more path segments). Some instances of resource hint registration should be migrated as a result. For example, "/files/*.ext" matched both "/files/a.ext" and "/files/folder/b.txt" in the past. The new behavior matches only the former, unless the "/files/**/*.ext" glob pattern is used. This commit also removes the "excludes" support, which was not widely used and very often could lead to subtle behavior. Closes gh-31340 --- .../aot/hint/ResourcePatternHint.java | 35 +++++++------- .../aot/hint/ResourcePatternHints.java | 39 +--------------- .../aot/hint/ResourcePatternHintTests.java | 46 +++++++++++-------- 3 files changed, 44 insertions(+), 76 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHint.java b/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHint.java index e686a86a3a1b..b403e7d4df40 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHint.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHint.java @@ -16,12 +16,10 @@ package org.springframework.aot.hint; -import java.util.Arrays; import java.util.Objects; -import java.util.regex.Pattern; -import java.util.stream.Collectors; import org.springframework.lang.Nullable; +import org.springframework.util.AntPathMatcher; import org.springframework.util.Assert; /** @@ -31,16 +29,18 @@ * resource on the classpath, or alternatively may contain the special * {@code *} character to indicate a wildcard match. For example: *

    - *
  • {@code file.properties}: matches just the {@code file.properties} + *
  • "file.properties": matches just the {@code file.properties} * file at the root of the classpath.
  • - *
  • {@code com/example/file.properties}: matches just the + *
  • "com/example/file.properties": matches just the * {@code file.properties} file in {@code com/example/}.
  • - *
  • {@code *.properties}: matches all the files with a {@code .properties} - * extension anywhere in the classpath.
  • - *
  • {@code com/example/*.properties}: matches all the files with a {@code .properties} - * extension in {@code com/example/} and its child directories at any depth.
  • - *
  • {@code com/example/*}: matches all the files in {@code com/example/} + *
  • "*.properties": matches all the files with a {@code .properties} + * extension at the root of the classpath.
  • + *
  • "com/example/*.properties": matches all the files with a {@code .properties} + * extension in {@code com/example/}.
  • + *
  • "com/example/{@literal **}": matches all the files in {@code com/example/} * and its child directories at any depth.
  • + *
  • "com/example/{@literal **}/*.properties": matches all the files with a {@code .properties} + * extension in {@code com/example/} and its child directories at any depth.
  • *
* *

A resource pattern must not start with a slash ({@code /}) unless it is the @@ -54,6 +54,8 @@ */ public final class ResourcePatternHint implements ConditionalHint { + private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher(); + private final String pattern; @Nullable @@ -77,16 +79,11 @@ public String getPattern() { } /** - * Return the regex {@link Pattern} to use for identifying the resources to match. + * Whether the given path matches the current glob pattern. + * @param path the path to match against */ - public Pattern toRegex() { - String prefix = (this.pattern.startsWith("*") ? ".*" : ""); - String suffix = (this.pattern.endsWith("*") ? ".*" : ""); - String regex = Arrays.stream(this.pattern.split("\\*")) - .filter(s -> !s.isEmpty()) - .map(Pattern::quote) - .collect(Collectors.joining(".*", prefix, suffix)); - return Pattern.compile(regex); + public boolean matches(String path) { + return PATH_MATCHER.match(this.pattern, path); } @Nullable diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHints.java b/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHints.java index 78c8ce9a6e55..952c441392c2 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHints.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ResourcePatternHints.java @@ -38,12 +38,9 @@ public final class ResourcePatternHints { private final List includes; - private final List excludes; - private ResourcePatternHints(Builder builder) { this.includes = new ArrayList<>(builder.includes); - this.excludes = new ArrayList<>(builder.excludes); } /** @@ -54,14 +51,6 @@ public List getIncludes() { return this.includes; } - /** - * Return the exclude patterns to use to identify the resources to match. - * @return the exclude patterns - */ - public List getExcludes() { - return this.excludes; - } - /** * Builder for {@link ResourcePatternHints}. @@ -70,13 +59,11 @@ public static class Builder { private final Set includes = new LinkedHashSet<>(); - private final Set excludes = new LinkedHashSet<>(); - Builder() { } /** - * Include resources matching the specified patterns. + * Include resources matching the specified glob patterns. * @param reachableType the type that should be reachable for this hint to apply * @param includes the include patterns (see {@link ResourcePatternHint} documentation) * @return {@code this}, to facilitate method chaining @@ -129,7 +116,7 @@ private List expandToIncludeDirectories(String includePattern) { } /** - * Include resources matching the specified patterns. + * Include resources matching the specified glob patterns. * @param includes the include patterns (see {@link ResourcePatternHint} documentation) * @return {@code this}, to facilitate method chaining */ @@ -137,28 +124,6 @@ public Builder includes(String... includes) { return includes(null, includes); } - /** - * Exclude resources matching the specified patterns. - * @param reachableType the type that should be reachable for this hint to apply - * @param excludes the exclude patterns (see {@link ResourcePatternHint} documentation) - * @return {@code this}, to facilitate method chaining - */ - public Builder excludes(@Nullable TypeReference reachableType, String... excludes) { - List newExcludes = Arrays.stream(excludes) - .map(include -> new ResourcePatternHint(include, reachableType)).toList(); - this.excludes.addAll(newExcludes); - return this; - } - - /** - * Exclude resources matching the specified patterns. - * @param excludes the exclude patterns (see {@link ResourcePatternHint} documentation) - * @return {@code this}, to facilitate method chaining - */ - public Builder excludes(String... excludes) { - return excludes(null, excludes); - } - /** * Create {@link ResourcePatternHints} based on the state of this * builder. diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ResourcePatternHintTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ResourcePatternHintTests.java index 303b243b2b7c..ecea7ee91574 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/ResourcePatternHintTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/ResourcePatternHintTests.java @@ -39,49 +39,55 @@ void patternWithLeadingSlashIsRejected() { @Test void rootDirectory() { ResourcePatternHint hint = new ResourcePatternHint("/", null); - assertThat(hint.toRegex().asMatchPredicate()) - .accepts("/") - .rejects("/com/example", "/file.txt"); + assertThat(hint.matches("/")).isTrue(); + assertThat(hint.matches("/com/example")).isFalse(); + assertThat(hint.matches("/file.txt")).isFalse(); } @Test void fileAtRoot() { ResourcePatternHint hint = new ResourcePatternHint("file.properties", null); - assertThat(hint.toRegex().asMatchPredicate()) - .accepts("file.properties") - .rejects("com/example/file.properties", "file.prop", "another-file.properties"); + assertThat(hint.matches("file.properties")).isTrue(); + assertThat(hint.matches("com/example/file.properties")).isFalse(); + assertThat(hint.matches("file.prop")).isFalse(); + assertThat(hint.matches("another-file.properties")).isFalse(); } @Test void fileInDirectory() { ResourcePatternHint hint = new ResourcePatternHint("com/example/file.properties", null); - assertThat(hint.toRegex().asMatchPredicate()) - .accepts("com/example/file.properties") - .rejects("file.properties", "com/file.properties", "com/example/another-file.properties"); + assertThat(hint.matches("com/example/file.properties")).isTrue(); + assertThat(hint.matches("file.properties")).isFalse(); + assertThat(hint.matches("com/file.properties")).isFalse(); + assertThat(hint.matches("com/example/another-file.properties")).isFalse(); } @Test void extension() { - ResourcePatternHint hint = new ResourcePatternHint("*.properties", null); - assertThat(hint.toRegex().asMatchPredicate()) - .accepts("file.properties", "com/example/file.properties") - .rejects("file.prop", "com/example/file.prop"); + ResourcePatternHint hint = new ResourcePatternHint("**/*.properties", null); + assertThat(hint.matches("file.properties")).isTrue(); + assertThat(hint.matches("com/example/file.properties")).isTrue(); + assertThat(hint.matches("file.prop")).isFalse(); + assertThat(hint.matches("com/example/file.prop")).isFalse(); } @Test void extensionInDirectoryAtAnyDepth() { ResourcePatternHint hint = new ResourcePatternHint("com/example/*.properties", null); - assertThat(hint.toRegex().asMatchPredicate()) - .accepts("com/example/file.properties", "com/example/another/file.properties") - .rejects("file.properties", "com/file.properties"); + assertThat(hint.matches("com/example/file.properties")).isTrue(); + assertThat(hint.matches("com/example/another/file.properties")).isFalse(); + assertThat(hint.matches("com/file.properties")).isFalse(); + assertThat(hint.matches("file.properties")).isFalse(); } @Test void anyFileInDirectoryAtAnyDepth() { - ResourcePatternHint hint = new ResourcePatternHint("com/example/*", null); - assertThat(hint.toRegex().asMatchPredicate()) - .accepts("com/example/file.properties", "com/example/another/file.properties", "com/example/another") - .rejects("file.properties", "com/file.properties"); + ResourcePatternHint hint = new ResourcePatternHint("com/example/**", null); + assertThat(hint.matches("com/example/file.properties")).isTrue(); + assertThat(hint.matches("com/example/another/file.properties")).isTrue(); + assertThat(hint.matches("com/example/another")).isTrue(); + assertThat(hint.matches("file.properties")).isFalse(); + assertThat(hint.matches("com/file.properties")).isFalse(); } } From fec2ed5540d075fc9364302fc82c9d7de3745ee6 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 29 Nov 2024 14:43:55 +0100 Subject: [PATCH 021/701] Implement new GraalVM reachability metadata format As of GraalVM 23, a new and simplified reachability metadata format is available. Metadata now consists of a single "reachability-metadata.json" file that contains all the information previously spread in multiple files. The new format does not include some introspection flags, as they're now automatically included when a hint is registered against a type. Also, "typeReachable" has been renamed as "typeReached" to highlight the fact that the event considered is the static initialization of the type, not when the static analysis performed during native compilation is reaching the type. This new format ships with a JSON schema, which this commit is tested against. See gh-33847 --- framework-platform/framework-platform.gradle | 1 + spring-core/spring-core.gradle | 1 + .../nativex/NativeConfigurationWriter.java | 61 +- .../aot/nativex/ProxyHintsWriter.java | 73 --- ...er.java => ReflectionHintsAttributes.java} | 90 +-- ...iter.java => ResourceHintsAttributes.java} | 38 +- .../aot/nativex/RuntimeHintsWriter.java | 66 ++ ...java => SerializationHintsAttributes.java} | 23 +- .../FileNativeConfigurationWriterTests.java | 127 ++-- .../aot/nativex/ProxyHintsWriterTests.java | 112 ---- .../nativex/ReflectionHintsWriterTests.java | 295 --------- .../aot/nativex/ResourceHintsWriterTests.java | 190 ------ .../aot/nativex/RuntimeHintsWriterTests.java | 594 ++++++++++++++++++ .../SerializationHintsWriterTests.java | 81 --- .../reachability-metadata-schema-v1.0.0.json | 362 +++++++++++ 15 files changed, 1170 insertions(+), 944 deletions(-) delete mode 100644 spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsWriter.java rename spring-core/src/main/java/org/springframework/aot/nativex/{ReflectionHintsWriter.java => ReflectionHintsAttributes.java} (57%) rename spring-core/src/main/java/org/springframework/aot/nativex/{ResourceHintsWriter.java => ResourceHintsAttributes.java} (68%) create mode 100644 spring-core/src/main/java/org/springframework/aot/nativex/RuntimeHintsWriter.java rename spring-core/src/main/java/org/springframework/aot/nativex/{SerializationHintsWriter.java => SerializationHintsAttributes.java} (70%) delete mode 100644 spring-core/src/test/java/org/springframework/aot/nativex/ProxyHintsWriterTests.java delete mode 100644 spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java delete mode 100644 spring-core/src/test/java/org/springframework/aot/nativex/ResourceHintsWriterTests.java create mode 100644 spring-core/src/test/java/org/springframework/aot/nativex/RuntimeHintsWriterTests.java delete mode 100644 spring-core/src/test/java/org/springframework/aot/nativex/SerializationHintsWriterTests.java create mode 100644 spring-core/src/test/resources/org/springframework/aot/nativex/reachability-metadata-schema-v1.0.0.json diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index 998c49185579..804c6fa58ab6 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -34,6 +34,7 @@ dependencies { api("com.google.protobuf:protobuf-java-util:4.28.3") api("com.h2database:h2:2.3.232") api("com.jayway.jsonpath:json-path:2.9.0") + api("com.networknt:json-schema-validator:1.5.3") api("com.oracle.database.jdbc:ojdbc11:21.9.0.0") api("com.rometools:rome:1.19.0") api("com.squareup.okhttp3:mockwebserver:3.14.9") diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index a08d0e166723..ebad4fa180a5 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -104,6 +104,7 @@ dependencies { testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") testImplementation("org.mockito:mockito-core") + testImplementation("com.networknt:json-schema-validator"); testImplementation("org.skyscreamer:jsonassert") testImplementation("org.xmlunit:xmlunit-assertj") testImplementation("org.xmlunit:xmlunit-matchers") diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/NativeConfigurationWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/NativeConfigurationWriter.java index 5ef7d21e2656..d0886760c47b 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/NativeConfigurationWriter.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/NativeConfigurationWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -18,11 +18,7 @@ import java.util.function.Consumer; -import org.springframework.aot.hint.ProxyHints; -import org.springframework.aot.hint.ReflectionHints; -import org.springframework.aot.hint.ResourceHints; import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.SerializationHints; /** * Write {@link RuntimeHints} as GraalVM native configuration. @@ -30,8 +26,9 @@ * @author Sebastien Deleuze * @author Stephane Nicoll * @author Janne Valkealahti + * @author Brian Clozel * @since 6.0 - * @see Native Image Build Configuration + * @see Native Image Build Configuration */ public abstract class NativeConfigurationWriter { @@ -40,24 +37,21 @@ public abstract class NativeConfigurationWriter { * @param hints the hints to handle */ public void write(RuntimeHints hints) { - if (hints.serialization().javaSerializationHints().findAny().isPresent()) { - writeSerializationHints(hints.serialization()); - } - if (hints.proxies().jdkProxyHints().findAny().isPresent()) { - writeProxyHints(hints.proxies()); - } - if (hints.reflection().typeHints().findAny().isPresent()) { - writeReflectionHints(hints.reflection()); - } - if (hints.resources().resourcePatternHints().findAny().isPresent() || - hints.resources().resourceBundleHints().findAny().isPresent()) { - writeResourceHints(hints.resources()); - } - if (hints.jni().typeHints().findAny().isPresent()) { - writeJniHints(hints.jni()); + if (hasAnyHint(hints)) { + writeTo("reachability-metadata.json", + writer -> new RuntimeHintsWriter().write(writer, hints)); } } + private boolean hasAnyHint(RuntimeHints hints) { + return (hints.serialization().javaSerializationHints().findAny().isPresent() + || hints.proxies().jdkProxyHints().findAny().isPresent() + || hints.reflection().typeHints().findAny().isPresent() + || hints.resources().resourcePatternHints().findAny().isPresent() + || hints.resources().resourceBundleHints().findAny().isPresent() + || hints.jni().typeHints().findAny().isPresent()); + } + /** * Write the specified GraalVM native configuration file, using the * provided {@link BasicJsonWriter}. @@ -66,29 +60,4 @@ public void write(RuntimeHints hints) { */ protected abstract void writeTo(String fileName, Consumer writer); - private void writeSerializationHints(SerializationHints hints) { - writeTo("serialization-config.json", writer -> - SerializationHintsWriter.INSTANCE.write(writer, hints)); - } - - private void writeProxyHints(ProxyHints hints) { - writeTo("proxy-config.json", writer -> - ProxyHintsWriter.INSTANCE.write(writer, hints)); - } - - private void writeReflectionHints(ReflectionHints hints) { - writeTo("reflect-config.json", writer -> - ReflectionHintsWriter.INSTANCE.write(writer, hints)); - } - - private void writeResourceHints(ResourceHints hints) { - writeTo("resource-config.json", writer -> - ResourceHintsWriter.INSTANCE.write(writer, hints)); - } - - private void writeJniHints(ReflectionHints hints) { - writeTo("jni-config.json", writer -> - ReflectionHintsWriter.INSTANCE.write(writer, hints)); - } - } diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsWriter.java deleted file mode 100644 index 51cd3132efac..000000000000 --- a/spring-core/src/main/java/org/springframework/aot/nativex/ProxyHintsWriter.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2002-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.aot.nativex; - -import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.Collectors; - -import org.springframework.aot.hint.JdkProxyHint; -import org.springframework.aot.hint.ProxyHints; -import org.springframework.aot.hint.TypeReference; - -/** - * Write {@link JdkProxyHint}s contained in a {@link ProxyHints} to the JSON - * output expected by the GraalVM {@code native-image} compiler, typically named - * {@code proxy-config.json}. - * - * @author Sebastien Deleuze - * @author Stephane Nicoll - * @author Brian Clozel - * @since 6.0 - * @see Dynamic Proxy in Native Image - * @see Native Image Build Configuration - */ -class ProxyHintsWriter { - - public static final ProxyHintsWriter INSTANCE = new ProxyHintsWriter(); - - private static final Comparator JDK_PROXY_HINT_COMPARATOR = - (left, right) -> { - String leftSignature = left.getProxiedInterfaces().stream() - .map(TypeReference::getCanonicalName).collect(Collectors.joining(",")); - String rightSignature = right.getProxiedInterfaces().stream() - .map(TypeReference::getCanonicalName).collect(Collectors.joining(",")); - return leftSignature.compareTo(rightSignature); - }; - - public void write(BasicJsonWriter writer, ProxyHints hints) { - writer.writeArray(hints.jdkProxyHints().sorted(JDK_PROXY_HINT_COMPARATOR) - .map(this::toAttributes).toList()); - } - - private Map toAttributes(JdkProxyHint hint) { - Map attributes = new LinkedHashMap<>(); - handleCondition(attributes, hint); - attributes.put("interfaces", hint.getProxiedInterfaces()); - return attributes; - } - - private void handleCondition(Map attributes, JdkProxyHint hint) { - if (hint.getReachableType() != null) { - Map conditionAttributes = new LinkedHashMap<>(); - conditionAttributes.put("typeReachable", hint.getReachableType()); - attributes.put("condition", conditionAttributes); - } - } - -} diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsAttributes.java similarity index 57% rename from spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsWriter.java rename to spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsAttributes.java index 3d678b0d85cf..4d7b2b27a469 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsWriter.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/ReflectionHintsAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -16,48 +16,72 @@ package org.springframework.aot.nativex; +import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; +import org.springframework.aot.hint.ConditionalHint; import org.springframework.aot.hint.ExecutableHint; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.FieldHint; +import org.springframework.aot.hint.JdkProxyHint; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.ReflectionHints; +import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.TypeHint; +import org.springframework.aot.hint.TypeReference; import org.springframework.lang.Nullable; /** - * Write {@link ReflectionHints} to the JSON output expected by the GraalVM - * {@code native-image} compiler, typically named {@code reflect-config.json} - * or {@code jni-config.json}. + * Collect {@link ReflectionHints} as map attributes ready for JSON serialization for the GraalVM + * {@code native-image} compiler. * * @author Sebastien Deleuze * @author Stephane Nicoll * @author Janne Valkealahti - * @since 6.0 - * @see Reflection Use in Native Images - * @see Java Native Interface (JNI) in Native Image - * @see Native Image Build Configuration + * @see Reflection Use in Native Images + * @see Java Native Interface (JNI) in Native Image + * @see Native Image Build Configuration */ -class ReflectionHintsWriter { +class ReflectionHintsAttributes { - public static final ReflectionHintsWriter INSTANCE = new ReflectionHintsWriter(); + private static final Comparator JDK_PROXY_HINT_COMPARATOR = + (left, right) -> { + String leftSignature = left.getProxiedInterfaces().stream() + .map(TypeReference::getCanonicalName).collect(Collectors.joining(",")); + String rightSignature = right.getProxiedInterfaces().stream() + .map(TypeReference::getCanonicalName).collect(Collectors.joining(",")); + return leftSignature.compareTo(rightSignature); + }; - public void write(BasicJsonWriter writer, ReflectionHints hints) { - writer.writeArray(hints.typeHints() + public List> reflection(RuntimeHints hints) { + List> reflectionHints = new ArrayList<>(); + reflectionHints.addAll(hints.reflection().typeHints() .sorted(Comparator.comparing(TypeHint::getType)) .map(this::toAttributes).toList()); + reflectionHints.addAll(hints.proxies().jdkProxyHints() + .sorted(JDK_PROXY_HINT_COMPARATOR) + .map(this::toAttributes).toList()); + return reflectionHints; + } + + public List> jni(RuntimeHints hints) { + List> jniHints = new ArrayList<>(); + jniHints.addAll(hints.jni().typeHints() + .sorted(Comparator.comparing(TypeHint::getType)) + .map(this::toAttributes).toList()); + return jniHints; } private Map toAttributes(TypeHint hint) { Map attributes = new LinkedHashMap<>(); - attributes.put("name", hint.getType()); + attributes.put("type", hint.getType()); handleCondition(attributes, hint); handleCategories(attributes, hint.getMemberCategories()); handleFields(attributes, hint.fields()); @@ -66,33 +90,23 @@ private Map toAttributes(TypeHint hint) { return attributes; } - private void handleCondition(Map attributes, TypeHint hint) { + private void handleCondition(Map attributes, ConditionalHint hint) { if (hint.getReachableType() != null) { - Map conditionAttributes = new LinkedHashMap<>(); - conditionAttributes.put("typeReachable", hint.getReachableType()); - attributes.put("condition", conditionAttributes); + attributes.put("condition", Map.of("typeReached", hint.getReachableType())); } } private void handleFields(Map attributes, Stream fields) { addIfNotEmpty(attributes, "fields", fields .sorted(Comparator.comparing(FieldHint::getName, String::compareToIgnoreCase)) - .map(this::toAttributes).toList()); - } - - private Map toAttributes(FieldHint hint) { - Map attributes = new LinkedHashMap<>(); - attributes.put("name", hint.getName()); - return attributes; + .map(fieldHint -> Map.of("name", fieldHint.getName())) + .toList()); } private void handleExecutables(Map attributes, List hints) { addIfNotEmpty(attributes, "methods", hints.stream() .filter(h -> h.getMode().equals(ExecutableMode.INVOKE)) .map(this::toAttributes).toList()); - addIfNotEmpty(attributes, "queriedMethods", hints.stream() - .filter(h -> h.getMode().equals(ExecutableMode.INTROSPECT)) - .map(this::toAttributes).toList()); } private Map toAttributes(ExecutableHint hint) { @@ -102,28 +116,19 @@ private Map toAttributes(ExecutableHint hint) { return attributes; } + @SuppressWarnings("removal") private void handleCategories(Map attributes, Set categories) { categories.stream().sorted().forEach(category -> { switch (category) { - case PUBLIC_FIELDS -> attributes.put("allPublicFields", true); - case DECLARED_FIELDS -> attributes.put("allDeclaredFields", true); - case INTROSPECT_PUBLIC_CONSTRUCTORS -> - attributes.put("queryAllPublicConstructors", true); - case INTROSPECT_DECLARED_CONSTRUCTORS -> - attributes.put("queryAllDeclaredConstructors", true); + case INVOKE_PUBLIC_FIELDS, PUBLIC_FIELDS -> attributes.put("allPublicFields", true); + case INVOKE_DECLARED_FIELDS, DECLARED_FIELDS -> attributes.put("allDeclaredFields", true); case INVOKE_PUBLIC_CONSTRUCTORS -> attributes.put("allPublicConstructors", true); case INVOKE_DECLARED_CONSTRUCTORS -> attributes.put("allDeclaredConstructors", true); - case INTROSPECT_PUBLIC_METHODS -> - attributes.put("queryAllPublicMethods", true); - case INTROSPECT_DECLARED_METHODS -> - attributes.put("queryAllDeclaredMethods", true); case INVOKE_PUBLIC_METHODS -> attributes.put("allPublicMethods", true); case INVOKE_DECLARED_METHODS -> attributes.put("allDeclaredMethods", true); - case PUBLIC_CLASSES -> attributes.put("allPublicClasses", true); - case DECLARED_CLASSES -> attributes.put("allDeclaredClasses", true); } } ); @@ -135,4 +140,11 @@ private void addIfNotEmpty(Map attributes, String name, @Nullabl } } + private Map toAttributes(JdkProxyHint hint) { + Map attributes = new LinkedHashMap<>(); + handleCondition(attributes, hint); + attributes.put("type", Map.of("proxy", hint.getProxiedInterfaces())); + return attributes; + } + } diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsAttributes.java similarity index 68% rename from spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsWriter.java rename to spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsAttributes.java index 6829006e9029..519a986fd93d 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsWriter.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsAttributes.java @@ -21,7 +21,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.stream.Stream; import org.springframework.aot.hint.ConditionalHint; import org.springframework.aot.hint.ResourceBundleHint; @@ -31,8 +30,8 @@ import org.springframework.lang.Nullable; /** - * Write a {@link ResourceHints} to the JSON output expected by the GraalVM - * {@code native-image} compiler, typically named {@code resource-config.json}. + * Collect {@link ResourceHints} as map attributes ready for JSON serialization for the GraalVM + * {@code native-image} compiler. * * @author Sebastien Deleuze * @author Stephane Nicoll @@ -41,9 +40,7 @@ * @see Accessing Resources in Native Images * @see Native Image Build Configuration */ -class ResourceHintsWriter { - - public static final ResourceHintsWriter INSTANCE = new ResourceHintsWriter(); +class ResourceHintsAttributes { private static final Comparator RESOURCE_PATTERN_HINT_COMPARATOR = Comparator.comparing(ResourcePatternHint::getPattern); @@ -52,30 +49,17 @@ class ResourceHintsWriter { Comparator.comparing(ResourceBundleHint::getBaseName); - public void write(BasicJsonWriter writer, ResourceHints hints) { - Map attributes = new LinkedHashMap<>(); - addIfNotEmpty(attributes, "resources", toAttributes(hints)); - handleResourceBundles(attributes, hints.resourceBundleHints()); - writer.writeObject(attributes); - } - - private Map toAttributes(ResourceHints hint) { - Map attributes = new LinkedHashMap<>(); - addIfNotEmpty(attributes, "includes", hint.resourcePatternHints() + public List> resources(ResourceHints hint) { + return hint.resourcePatternHints() .map(ResourcePatternHints::getIncludes).flatMap(List::stream).distinct() .sorted(RESOURCE_PATTERN_HINT_COMPARATOR) - .map(this::toAttributes).toList()); - addIfNotEmpty(attributes, "excludes", hint.resourcePatternHints() - .map(ResourcePatternHints::getExcludes).flatMap(List::stream).distinct() - .sorted(RESOURCE_PATTERN_HINT_COMPARATOR) - .map(this::toAttributes).toList()); - return attributes; + .map(this::toAttributes).toList(); } - private void handleResourceBundles(Map attributes, Stream resourceBundles) { - addIfNotEmpty(attributes, "bundles", resourceBundles + public List> resourceBundles(ResourceHints hint) { + return hint.resourceBundleHints() .sorted(RESOURCE_BUNDLE_HINT_COMPARATOR) - .map(this::toAttributes).toList()); + .map(this::toAttributes).toList(); } private Map toAttributes(ResourceBundleHint hint) { @@ -88,7 +72,7 @@ private Map toAttributes(ResourceBundleHint hint) { private Map toAttributes(ResourcePatternHint hint) { Map attributes = new LinkedHashMap<>(); handleCondition(attributes, hint); - attributes.put("pattern", hint.toRegex().toString()); + attributes.put("glob", hint.getPattern()); return attributes; } @@ -111,7 +95,7 @@ else if (value != null) { private void handleCondition(Map attributes, ConditionalHint hint) { if (hint.getReachableType() != null) { Map conditionAttributes = new LinkedHashMap<>(); - conditionAttributes.put("typeReachable", hint.getReachableType()); + conditionAttributes.put("typeReached", hint.getReachableType()); attributes.put("condition", conditionAttributes); } } diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/RuntimeHintsWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/RuntimeHintsWriter.java new file mode 100644 index 000000000000..782497dee8b4 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/nativex/RuntimeHintsWriter.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-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.aot.nativex; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.core.SpringVersion; + +/** + * Write a {@link RuntimeHints} instance to the JSON output expected by the + * GraalVM {@code native-image} compiler, typically named {@code reachability-metadata.json}. + * + * @author Brian Clozel + * @since 7.0 + * @see GraalVM Reachability Metadata + */ +class RuntimeHintsWriter { + + public void write(BasicJsonWriter writer, RuntimeHints hints) { + Map document = new LinkedHashMap<>(); + String springVersion = SpringVersion.getVersion(); + if (springVersion != null) { + document.put("comment", "Spring Framework " + springVersion); + } + List> reflection = new ReflectionHintsAttributes().reflection(hints); + if (!reflection.isEmpty()) { + document.put("reflection", reflection); + } + List> jni = new ReflectionHintsAttributes().jni(hints); + if (!jni.isEmpty()) { + document.put("jni", jni); + } + List> resourceHints = new ResourceHintsAttributes().resources(hints.resources()); + if (!resourceHints.isEmpty()) { + document.put("resources", resourceHints); + } + List> resourceBundles = new ResourceHintsAttributes().resourceBundles(hints.resources()); + if (!resourceBundles.isEmpty()) { + document.put("bundles", resourceBundles); + } + List> serialization = new SerializationHintsAttributes().toAttributes(hints.serialization()); + if (!serialization.isEmpty()) { + document.put("serialization", serialization); + } + + writer.writeObject(document); + } + +} diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/SerializationHintsWriter.java b/spring-core/src/main/java/org/springframework/aot/nativex/SerializationHintsAttributes.java similarity index 70% rename from spring-core/src/main/java/org/springframework/aot/nativex/SerializationHintsWriter.java rename to spring-core/src/main/java/org/springframework/aot/nativex/SerializationHintsAttributes.java index 73b25248519e..d9ca1d6d5fd3 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/SerializationHintsWriter.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/SerializationHintsAttributes.java @@ -18,6 +18,7 @@ import java.util.Comparator; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import org.springframework.aot.hint.ConditionalHint; @@ -25,40 +26,36 @@ import org.springframework.aot.hint.SerializationHints; /** - * Write a {@link SerializationHints} to the JSON output expected by the - * GraalVM {@code native-image} compiler, typically named - * {@code serialization-config.json}. + * Collect {@link SerializationHints} as map attributes ready for JSON serialization for the GraalVM + * {@code native-image} compiler. * * @author Sebastien Deleuze * @author Stephane Nicoll * @author Brian Clozel - * @since 6.0 - * @see Native Image Build Configuration + * @see Native Image Build Configuration */ -class SerializationHintsWriter { - - public static final SerializationHintsWriter INSTANCE = new SerializationHintsWriter(); +class SerializationHintsAttributes { private static final Comparator JAVA_SERIALIZATION_HINT_COMPARATOR = Comparator.comparing(JavaSerializationHint::getType); - public void write(BasicJsonWriter writer, SerializationHints hints) { - writer.writeArray(hints.javaSerializationHints() + public List> toAttributes(SerializationHints hints) { + return hints.javaSerializationHints() .sorted(JAVA_SERIALIZATION_HINT_COMPARATOR) - .map(this::toAttributes).toList()); + .map(this::toAttributes).toList(); } private Map toAttributes(JavaSerializationHint serializationHint) { LinkedHashMap attributes = new LinkedHashMap<>(); handleCondition(attributes, serializationHint); - attributes.put("name", serializationHint.getType()); + attributes.put("type", serializationHint.getType()); return attributes; } private void handleCondition(Map attributes, ConditionalHint hint) { if (hint.getReachableType() != null) { Map conditionAttributes = new LinkedHashMap<>(); - conditionAttributes.put("typeReachable", hint.getReachableType()); + conditionAttributes.put("typeReached", hint.getReachableType()); attributes.put("condition", conditionAttributes); } } diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java index db6d82a421af..3874d9f9675d 100644 --- a/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java +++ b/spring-core/src/test/java/org/springframework/aot/nativex/FileNativeConfigurationWriterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -20,8 +20,6 @@ import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.List; import java.util.function.Consumer; import java.util.function.Function; @@ -40,7 +38,6 @@ import org.springframework.aot.hint.SerializationHints; import org.springframework.aot.hint.TypeReference; import org.springframework.core.codec.StringDecoder; -import org.springframework.util.MimeType; import static org.assertj.core.api.Assertions.assertThat; @@ -74,10 +71,13 @@ void serializationConfig() throws IOException, JSONException { serializationHints.registerType(Long.class); generator.write(hints); assertEquals(""" - [ - { "name": "java.lang.Integer" }, - { "name": "java.lang.Long" } - ]""", "serialization-config.json"); + { + "serialization": [ + { "type": "java.lang.Integer" }, + { "type": "java.lang.Long" } + ] + } + """); } @Test @@ -89,10 +89,13 @@ void proxyConfig() throws IOException, JSONException { proxyHints.registerJdkProxy(Function.class, Consumer.class); generator.write(hints); assertEquals(""" - [ - { "interfaces": [ "java.util.function.Function" ] }, - { "interfaces": [ "java.util.function.Function", "java.util.function.Consumer" ] } - ]""", "proxy-config.json"); + { + "reflection": [ + { type: {"proxy": [ "java.util.function.Function" ] } }, + { type: {"proxy": [ "java.util.function.Function", "java.util.function.Consumer" ] } } + ] + } + """); } @Test @@ -102,48 +105,36 @@ void reflectionConfig() throws IOException, JSONException { ReflectionHints reflectionHints = hints.reflection(); reflectionHints.registerType(StringDecoder.class, builder -> builder .onReachableType(String.class) - .withMembers(MemberCategory.PUBLIC_FIELDS, MemberCategory.DECLARED_FIELDS, - MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS, MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, + .withMembers(MemberCategory.INVOKE_PUBLIC_FIELDS, MemberCategory.INVOKE_DECLARED_FIELDS, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS, - MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS, - MemberCategory.PUBLIC_CLASSES, MemberCategory.DECLARED_CLASSES) + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS) .withField("DEFAULT_CHARSET") .withField("defaultCharset") - .withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), ExecutableMode.INTROSPECT) - .withMethod("setDefaultCharset", TypeReference.listOf(Charset.class), ExecutableMode.INVOKE) - .withMethod("getDefaultCharset", Collections.emptyList(), ExecutableMode.INTROSPECT)); + .withMethod("setDefaultCharset", TypeReference.listOf(Charset.class), ExecutableMode.INVOKE)); generator.write(hints); assertEquals(""" - [ - { - "name": "org.springframework.core.codec.StringDecoder", - "condition": { "typeReachable": "java.lang.String" }, - "allPublicFields": true, - "allDeclaredFields": true, - "queryAllPublicConstructors": true, - "queryAllDeclaredConstructors": true, - "allPublicConstructors": true, - "allDeclaredConstructors": true, - "queryAllPublicMethods": true, - "queryAllDeclaredMethods": true, - "allPublicMethods": true, - "allDeclaredMethods": true, - "allPublicClasses": true, - "allDeclaredClasses": true, - "fields": [ - { "name": "DEFAULT_CHARSET" }, - { "name": "defaultCharset" } - ], - "methods": [ - { "name": "setDefaultCharset", "parameterTypes": [ "java.nio.charset.Charset" ] } - ], - "queriedMethods": [ - { "name": "", "parameterTypes": [ "java.util.List", "boolean", "org.springframework.util.MimeType" ] }, - { "name": "getDefaultCharset", "parameterTypes": [ ] } - ] - } - ]""", "reflect-config.json"); + { + "reflection": [ + { + "type": "org.springframework.core.codec.StringDecoder", + "condition": { "typeReached": "java.lang.String" }, + "allPublicFields": true, + "allDeclaredFields": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "fields": [ + { "name": "DEFAULT_CHARSET" }, + { "name": "defaultCharset" } + ], + "methods": [ + { "name": "setDefaultCharset", "parameterTypes": [ "java.nio.charset.Charset" ] } + ] + } + ] + } + """); } @Test @@ -155,12 +146,14 @@ void jniConfig() throws IOException, JSONException { jniHints.registerType(StringDecoder.class, builder -> builder.onReachableType(String.class)); generator.write(hints); assertEquals(""" - [ - { - "name": "org.springframework.core.codec.StringDecoder", - "condition": { "typeReachable": "java.lang.String" } - } - ]""", "jni-config.json"); + { + "jni": [ + { + "type": "org.springframework.core.codec.StringDecoder", + "condition": { "typeReached": "java.lang.String" } + } + ] + }"""); } @Test @@ -173,23 +166,21 @@ void resourceConfig() throws IOException, JSONException { generator.write(hints); assertEquals(""" { - "resources": { - "includes": [ - {"pattern": "\\\\Qcom/example/test.properties\\\\E"}, - {"pattern": "\\\\Q/\\\\E"}, - {"pattern": "\\\\Qcom\\\\E"}, - {"pattern": "\\\\Qcom/example\\\\E"}, - {"pattern": "\\\\Qcom/example/another.properties\\\\E"} - ] - } - }""", "resource-config.json"); + "resources": [ + {"glob": "com/example/test.properties"}, + {"glob": "/"}, + {"glob": "com"}, + {"glob": "com/example"}, + {"glob": "com/example/another.properties"} + ] + }"""); } @Test void namespace() { String groupId = "foo.bar"; String artifactId = "baz"; - String filename = "resource-config.json"; + String filename = "reachability-metadata.json"; FileNativeConfigurationWriter generator = new FileNativeConfigurationWriter(tempDir, groupId, artifactId); RuntimeHints hints = new RuntimeHints(); ResourceHints resourceHints = hints.resources(); @@ -199,8 +190,8 @@ void namespace() { assertThat(jsonFile.toFile()).exists(); } - private void assertEquals(String expectedString, String filename) throws IOException, JSONException { - Path jsonFile = tempDir.resolve("META-INF").resolve("native-image").resolve(filename); + private void assertEquals(String expectedString) throws IOException, JSONException { + Path jsonFile = tempDir.resolve("META-INF").resolve("native-image").resolve("reachability-metadata.json"); String content = Files.readString(jsonFile); JSONAssert.assertEquals(expectedString, content, JSONCompareMode.NON_EXTENSIBLE); } diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/ProxyHintsWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/ProxyHintsWriterTests.java deleted file mode 100644 index 6a65db7e9d10..000000000000 --- a/spring-core/src/test/java/org/springframework/aot/nativex/ProxyHintsWriterTests.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2002-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.aot.nativex; - -import java.io.StringWriter; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.json.JSONException; -import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.JSONAssert; -import org.skyscreamer.jsonassert.JSONCompareMode; - -import org.springframework.aot.hint.ProxyHints; -import org.springframework.aot.hint.TypeReference; - -/** - * Tests for {@link ProxyHintsWriter}. - * - * @author Sebastien Deleuze - * @author Stephane Nicoll - */ -class ProxyHintsWriterTests { - - @Test - void empty() throws JSONException { - ProxyHints hints = new ProxyHints(); - assertEquals("[]", hints); - } - - @Test - void shouldWriteOneEntry() throws JSONException { - ProxyHints hints = new ProxyHints(); - hints.registerJdkProxy(Function.class); - assertEquals(""" - [ - { "interfaces": [ "java.util.function.Function" ] } - ]""", hints); - } - - @Test - void shouldWriteMultipleEntries() throws JSONException { - ProxyHints hints = new ProxyHints(); - hints.registerJdkProxy(Function.class); - hints.registerJdkProxy(Function.class, Consumer.class); - assertEquals(""" - [ - { "interfaces": [ "java.util.function.Function" ] }, - { "interfaces": [ "java.util.function.Function", "java.util.function.Consumer" ] } - ]""", hints); - } - - @Test - void shouldWriteEntriesInNaturalOrder() throws JSONException { - ProxyHints hints = new ProxyHints(); - hints.registerJdkProxy(Supplier.class); - hints.registerJdkProxy(Function.class); - assertEquals(""" - [ - { "interfaces": [ "java.util.function.Function" ] }, - { "interfaces": [ "java.util.function.Supplier" ] } - ]""", hints); - } - - @Test - void shouldWriteInnerClass() throws JSONException { - ProxyHints hints = new ProxyHints(); - hints.registerJdkProxy(Inner.class); - assertEquals(""" - [ - { "interfaces": [ "org.springframework.aot.nativex.ProxyHintsWriterTests$Inner" ] } - ]""", hints); - } - - @Test - void shouldWriteCondition() throws JSONException { - ProxyHints hints = new ProxyHints(); - hints.registerJdkProxy(builder -> builder.proxiedInterfaces(Function.class) - .onReachableType(TypeReference.of("org.example.Test"))); - assertEquals(""" - [ - { "condition": { "typeReachable": "org.example.Test"}, "interfaces": [ "java.util.function.Function" ] } - ]""", hints); - } - - private void assertEquals(String expectedString, ProxyHints hints) throws JSONException { - StringWriter out = new StringWriter(); - BasicJsonWriter writer = new BasicJsonWriter(out, "\t"); - ProxyHintsWriter.INSTANCE.write(writer, hints); - JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.STRICT); - } - - interface Inner { - - } - -} diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java deleted file mode 100644 index c9fb6901d792..000000000000 --- a/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Copyright 2002-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.aot.nativex; - -import java.io.StringWriter; -import java.nio.charset.Charset; -import java.util.Collections; -import java.util.List; - -import org.json.JSONException; -import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.JSONAssert; -import org.skyscreamer.jsonassert.JSONCompareMode; - -import org.springframework.aot.hint.ExecutableMode; -import org.springframework.aot.hint.MemberCategory; -import org.springframework.aot.hint.ReflectionHints; -import org.springframework.aot.hint.TypeReference; -import org.springframework.core.codec.StringDecoder; -import org.springframework.util.MimeType; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ReflectionHintsWriter}. - * - * @author Sebastien Deleuze - * @author Stephane Nicoll - */ -class ReflectionHintsWriterTests { - - @Test - void empty() throws JSONException { - ReflectionHints hints = new ReflectionHints(); - assertEquals("[]", hints); - } - - @Test - void one() throws JSONException { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(StringDecoder.class, builder -> builder - .onReachableType(String.class) - .withMembers(MemberCategory.PUBLIC_FIELDS, MemberCategory.DECLARED_FIELDS, - MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS, MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, - MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS, - MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS, - MemberCategory.PUBLIC_CLASSES, MemberCategory.DECLARED_CLASSES) - .withField("DEFAULT_CHARSET") - .withField("defaultCharset") - .withField("aScore") - .withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), ExecutableMode.INTROSPECT) - .withMethod("setDefaultCharset", List.of(TypeReference.of(Charset.class)), ExecutableMode.INVOKE) - .withMethod("getDefaultCharset", Collections.emptyList(), ExecutableMode.INTROSPECT)); - assertEquals(""" - [ - { - "name": "org.springframework.core.codec.StringDecoder", - "condition": { "typeReachable": "java.lang.String" }, - "allPublicFields": true, - "allDeclaredFields": true, - "queryAllPublicConstructors": true, - "queryAllDeclaredConstructors": true, - "allPublicConstructors": true, - "allDeclaredConstructors": true, - "queryAllPublicMethods": true, - "queryAllDeclaredMethods": true, - "allPublicMethods": true, - "allDeclaredMethods": true, - "allPublicClasses": true, - "allDeclaredClasses": true, - "fields": [ - { "name": "aScore" }, - { "name": "DEFAULT_CHARSET" }, - { "name": "defaultCharset" } - ], - "methods": [ - { "name": "setDefaultCharset", "parameterTypes": [ "java.nio.charset.Charset" ] } - ], - "queriedMethods": [ - { "name": "", "parameterTypes": [ "java.util.List", "boolean", "org.springframework.util.MimeType" ] }, - { "name": "getDefaultCharset", "parameterTypes": [ ] } - ] - } - ]""", hints); - } - - @Test - void two() throws JSONException { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(Integer.class, builder -> { - }); - hints.registerType(Long.class, builder -> { - }); - - assertEquals(""" - [ - { "name": "java.lang.Integer" }, - { "name": "java.lang.Long" } - ]""", hints); - } - - @Test - void queriedMethods() throws JSONException { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(Integer.class, builder -> builder.withMethod("parseInt", - TypeReference.listOf(String.class), ExecutableMode.INTROSPECT)); - - assertEquals(""" - [ - { - "name": "java.lang.Integer", - "queriedMethods": [ - { - "name": "parseInt", - "parameterTypes": ["java.lang.String"] - } - ] - } - ] - """, hints); - } - - @Test - void methods() throws JSONException { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(Integer.class, builder -> builder.withMethod("parseInt", - TypeReference.listOf(String.class), ExecutableMode.INVOKE)); - - assertEquals(""" - [ - { - "name": "java.lang.Integer", - "methods": [ - { - "name": "parseInt", - "parameterTypes": ["java.lang.String"] - } - ] - } - ] - """, hints); - } - - @Test - void methodWithInnerClassParameter() throws JSONException { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(Integer.class, builder -> builder.withMethod("test", - TypeReference.listOf(Inner.class), ExecutableMode.INVOKE)); - - assertEquals(""" - [ - { - "name": "java.lang.Integer", - "methods": [ - { - "name": "test", - "parameterTypes": ["org.springframework.aot.nativex.ReflectionHintsWriterTests$Inner"] - } - ] - } - ] - """, hints); - } - - @Test - void methodAndQueriedMethods() throws JSONException { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(Integer.class, builder -> builder.withMethod("parseInt", - TypeReference.listOf(String.class), ExecutableMode.INVOKE)); - hints.registerType(Integer.class, builder -> builder.withMethod("parseInt", - TypeReference.listOf(String.class, int.class), ExecutableMode.INTROSPECT)); - - assertEquals(""" - [ - { - "name": "java.lang.Integer", - "queriedMethods": [ - { - "name": "parseInt", - "parameterTypes": ["java.lang.String", "int"] - } - ], - "methods": [ - { - "name": "parseInt", - "parameterTypes": ["java.lang.String"] - } - ] - } - ] - """, hints); - } - - @Test - void ignoreLambda() throws JSONException { - Runnable anonymousRunnable = () -> {}; - ReflectionHints hints = new ReflectionHints(); - hints.registerType(anonymousRunnable.getClass()); - assertEquals("[]", hints); - } - - @Test - void sortTypeHints() { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(Integer.class, builder -> {}); - hints.registerType(Long.class, builder -> {}); - - ReflectionHints hints2 = new ReflectionHints(); - hints2.registerType(Long.class, builder -> {}); - hints2.registerType(Integer.class, builder -> {}); - - assertThat(writeJson(hints)).isEqualTo(writeJson(hints2)); - } - - @Test - void sortFieldHints() { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(Integer.class, builder -> { - builder.withField("first"); - builder.withField("second"); - }); - ReflectionHints hints2 = new ReflectionHints(); - hints2.registerType(Integer.class, builder -> { - builder.withField("second"); - builder.withField("first"); - }); - assertThat(writeJson(hints)).isEqualTo(writeJson(hints2)); - } - - @Test - void sortConstructorHints() { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(Integer.class, builder -> { - builder.withConstructor(List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE); - builder.withConstructor(List.of(TypeReference.of(String.class), - TypeReference.of(Integer.class)), ExecutableMode.INVOKE); - }); - - ReflectionHints hints2 = new ReflectionHints(); - hints2.registerType(Integer.class, builder -> { - builder.withConstructor(List.of(TypeReference.of(String.class), - TypeReference.of(Integer.class)), ExecutableMode.INVOKE); - builder.withConstructor(List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE); - }); - assertThat(writeJson(hints)).isEqualTo(writeJson(hints2)); - } - - @Test - void sortMethodHints() { - ReflectionHints hints = new ReflectionHints(); - hints.registerType(Integer.class, builder -> { - builder.withMethod("test", Collections.emptyList(), ExecutableMode.INVOKE); - builder.withMethod("another", Collections.emptyList(), ExecutableMode.INVOKE); - }); - - ReflectionHints hints2 = new ReflectionHints(); - hints2.registerType(Integer.class, builder -> { - builder.withMethod("another", Collections.emptyList(), ExecutableMode.INVOKE); - builder.withMethod("test", Collections.emptyList(), ExecutableMode.INVOKE); - }); - assertThat(writeJson(hints)).isEqualTo(writeJson(hints2)); - } - - private void assertEquals(String expectedString, ReflectionHints hints) throws JSONException { - JSONAssert.assertEquals(expectedString, writeJson(hints), JSONCompareMode.STRICT); - } - - private String writeJson(ReflectionHints hints) { - StringWriter out = new StringWriter(); - BasicJsonWriter writer = new BasicJsonWriter(out, "\t"); - ReflectionHintsWriter.INSTANCE.write(writer, hints); - return out.toString(); - } - - - static class Inner { - - } - -} diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/ResourceHintsWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/ResourceHintsWriterTests.java deleted file mode 100644 index b3fef587efa1..000000000000 --- a/spring-core/src/test/java/org/springframework/aot/nativex/ResourceHintsWriterTests.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2002-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.aot.nativex; - -import java.io.StringWriter; - -import org.json.JSONException; -import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.JSONAssert; -import org.skyscreamer.jsonassert.JSONCompareMode; - -import org.springframework.aot.hint.ResourceHints; -import org.springframework.aot.hint.TypeReference; - -/** - * Tests for {@link ResourceHintsWriter}. - * - * @author Sebastien Deleuze - * @author Brian Clozel - */ -class ResourceHintsWriterTests { - - @Test - void empty() throws JSONException { - ResourceHints hints = new ResourceHints(); - assertEquals("{}", hints); - } - - @Test - void registerExactMatch() throws JSONException { - ResourceHints hints = new ResourceHints(); - hints.registerPattern("com/example/test.properties"); - hints.registerPattern("com/example/another.properties"); - assertEquals(""" - { - "resources": { - "includes": [ - { "pattern": "\\\\Q/\\\\E" }, - { "pattern": "\\\\Qcom\\\\E"}, - { "pattern": "\\\\Qcom/example\\\\E"}, - { "pattern": "\\\\Qcom/example/another.properties\\\\E"}, - { "pattern": "\\\\Qcom/example/test.properties\\\\E"} - ] - } - }""", hints); - } - - @Test - void registerWildcardAtTheBeginningPattern() throws JSONException { - ResourceHints hints = new ResourceHints(); - hints.registerPattern("*.properties"); - assertEquals(""" - { - "resources": { - "includes": [ - { "pattern": ".*\\\\Q.properties\\\\E"}, - { "pattern": "\\\\Q\\/\\\\E"} - ] - } - }""", hints); - } - - @Test - void registerWildcardInTheMiddlePattern() throws JSONException { - ResourceHints hints = new ResourceHints(); - hints.registerPattern("com/example/*.properties"); - assertEquals(""" - { - "resources": { - "includes": [ - { "pattern": "\\\\Q/\\\\E" }, - { "pattern": "\\\\Qcom\\\\E"}, - { "pattern": "\\\\Qcom/example\\\\E"}, - { "pattern": "\\\\Qcom/example/\\\\E.*\\\\Q.properties\\\\E"} - ] - } - }""", hints); - } - - @Test - void registerWildcardAtTheEndPattern() throws JSONException { - ResourceHints hints = new ResourceHints(); - hints.registerPattern("static/*"); - assertEquals(""" - { - "resources": { - "includes": [ - { "pattern": "\\\\Q/\\\\E" }, - { "pattern": "\\\\Qstatic\\\\E"}, - { "pattern": "\\\\Qstatic/\\\\E.*"} - ] - } - }""", hints); - } - - @Test - void registerPatternWithIncludesAndExcludes() throws JSONException { - ResourceHints hints = new ResourceHints(); - hints.registerPattern(hint -> hint.includes("com/example/*.properties").excludes("com/example/to-ignore.properties")); - hints.registerPattern(hint -> hint.includes("org/other/*.properties").excludes("org/other/to-ignore.properties")); - assertEquals(""" - { - "resources": { - "includes": [ - { "pattern": "\\\\Q/\\\\E"}, - { "pattern": "\\\\Qcom\\\\E"}, - { "pattern": "\\\\Qcom/example\\\\E"}, - { "pattern": "\\\\Qcom/example/\\\\E.*\\\\Q.properties\\\\E"}, - { "pattern": "\\\\Qorg\\\\E"}, - { "pattern": "\\\\Qorg/other\\\\E"}, - { "pattern": "\\\\Qorg/other/\\\\E.*\\\\Q.properties\\\\E"} - ], - "excludes": [ - { "pattern": "\\\\Qcom/example/to-ignore.properties\\\\E"}, - { "pattern": "\\\\Qorg/other/to-ignore.properties\\\\E"} - ] - } - }""", hints); - } - - @Test - void registerWithReachableTypeCondition() throws JSONException { - ResourceHints hints = new ResourceHints(); - hints.registerPattern(builder -> builder.includes(TypeReference.of("com.example.Test"), "com/example/test.properties")); - assertEquals(""" - { - "resources": { - "includes": [ - { "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Q/\\\\E"}, - { "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Qcom\\\\E"}, - { "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Qcom/example\\\\E"}, - { "condition": { "typeReachable": "com.example.Test"}, "pattern": "\\\\Qcom/example/test.properties\\\\E"} - ] - } - }""", hints); - } - - @Test - void registerType() throws JSONException { - ResourceHints hints = new ResourceHints(); - hints.registerType(String.class); - assertEquals(""" - { - "resources": { - "includes": [ - { "pattern": "\\\\Q/\\\\E" }, - { "pattern": "\\\\Qjava\\\\E" }, - { "pattern": "\\\\Qjava/lang\\\\E" }, - { "pattern": "\\\\Qjava/lang/String.class\\\\E" } - ] - } - }""", hints); - } - - @Test - void registerResourceBundle() throws JSONException { - ResourceHints hints = new ResourceHints(); - hints.registerResourceBundle("com.example.message2"); - hints.registerResourceBundle("com.example.message"); - assertEquals(""" - { - "bundles": [ - { "name": "com.example.message"}, - { "name": "com.example.message2"} - ] - }""", hints); - } - - private void assertEquals(String expectedString, ResourceHints hints) throws JSONException { - StringWriter out = new StringWriter(); - BasicJsonWriter writer = new BasicJsonWriter(out, "\t"); - ResourceHintsWriter.INSTANCE.write(writer, hints); - JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.STRICT); - } - -} diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/RuntimeHintsWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/RuntimeHintsWriterTests.java new file mode 100644 index 000000000000..e0f832d35ae5 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/aot/nativex/RuntimeHintsWriterTests.java @@ -0,0 +1,594 @@ +/* + * Copyright 2002-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.aot.nativex; + +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import com.networknt.schema.InputFormat; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaValidatorsConfig; +import com.networknt.schema.SpecVersion; +import com.networknt.schema.ValidationMessage; +import org.json.JSONException; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; + +import org.springframework.aot.hint.ExecutableMode; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeReference; +import org.springframework.core.codec.StringDecoder; +import org.springframework.core.env.Environment; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link RuntimeHintsWriter}. + * + * @author Brian Clozel + * @author Sebastien Deleuze + * @author Stephane Nicoll + */ +class RuntimeHintsWriterTests { + + private static JsonSchema JSON_SCHEMA; + + @BeforeAll + static void setupSchemaValidator() { + JsonSchemaFactory jsonSchemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V201909, builder -> + builder.schemaMappers(schemaMappers -> schemaMappers.mapPrefix("https://www.graalvm.org/", "classpath:org/springframework/aot/nativex/")) + ); + SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().build(); + JSON_SCHEMA = jsonSchemaFactory.getSchema(SchemaLocation.of("https://www.graalvm.org/reachability-metadata-schema-v1.0.0.json"), config); + } + + @Nested + class ReflectionHintsTests { + + @Test + void empty() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + assertEquals("{}", hints); + } + + @Test + void one() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(StringDecoder.class, builder -> builder + .onReachableType(String.class) + .withMembers(MemberCategory.INVOKE_PUBLIC_FIELDS, MemberCategory.INVOKE_DECLARED_FIELDS, + MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS) + .withField("DEFAULT_CHARSET") + .withField("defaultCharset") + .withField("aScore") + .withMethod("setDefaultCharset", List.of(TypeReference.of(Charset.class)), ExecutableMode.INVOKE)); + assertEquals(""" + { + "reflection": [ + { + "type": "org.springframework.core.codec.StringDecoder", + "condition": { "typeReached": "java.lang.String" }, + "allPublicFields": true, + "allDeclaredFields": true, + "allPublicConstructors": true, + "allDeclaredConstructors": true, + "allPublicMethods": true, + "allDeclaredMethods": true, + "fields": [ + { "name": "aScore" }, + { "name": "DEFAULT_CHARSET" }, + { "name": "defaultCharset" } + ], + "methods": [ + { "name": "setDefaultCharset", "parameterTypes": [ "java.nio.charset.Charset" ] } + ] + } + ] + } + """, hints); + } + + @Test + void two() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(Integer.class, builder -> { + }); + hints.reflection().registerType(Long.class, builder -> { + }); + + assertEquals(""" + { + "reflection": [ + { "type": "java.lang.Integer" }, + { "type": "java.lang.Long" } + ] + } + """, hints); + } + + @Test + void methods() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(Integer.class, builder -> builder.withMethod("parseInt", + TypeReference.listOf(String.class), ExecutableMode.INVOKE)); + + assertEquals(""" + { + "reflection": [ + { + "type": "java.lang.Integer", + "methods": [ + { + "name": "parseInt", + "parameterTypes": ["java.lang.String"] + } + ] + } + ] + } + """, hints); + } + + @Test + void methodWithInnerClassParameter() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(Integer.class, builder -> builder.withMethod("test", + TypeReference.listOf(InnerClass.class), ExecutableMode.INVOKE)); + + assertEquals(""" + { + "reflection": [ + { + "type": "java.lang.Integer", + "methods": [ + { + "name": "test", + "parameterTypes": ["org.springframework.aot.nativex.RuntimeHintsWriterTests$InnerClass"] + } + ] + } + ] + } + """, hints); + } + + @Test + void methodAndQueriedMethods() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(Integer.class, builder -> builder.withMethod("parseInt", + TypeReference.listOf(String.class), ExecutableMode.INVOKE)); + + assertEquals(""" + { + "reflection": [ + { + "type": "java.lang.Integer", + "methods": [ + { + "name": "parseInt", + "parameterTypes": ["java.lang.String"] + } + ] + } + ] + } + """, hints); + } + + @Test + void ignoreLambda() throws JSONException { + Runnable anonymousRunnable = () -> {}; + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(anonymousRunnable.getClass()); + assertEquals("{}", hints); + } + + @Test + void sortTypeHints() { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(Integer.class, builder -> {}); + hints.reflection().registerType(Long.class, builder -> {}); + + RuntimeHints hints2 = new RuntimeHints(); + hints2.reflection().registerType(Long.class, builder -> {}); + hints2.reflection().registerType(Integer.class, builder -> {}); + + assertThat(writeJson(hints)).isEqualTo(writeJson(hints2)); + } + + @Test + void sortFieldHints() { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(Integer.class, builder -> { + builder.withField("first"); + builder.withField("second"); + }); + RuntimeHints hints2 = new RuntimeHints(); + hints2.reflection().registerType(Integer.class, builder -> { + builder.withField("second"); + builder.withField("first"); + }); + assertThat(writeJson(hints)).isEqualTo(writeJson(hints2)); + } + + @Test + void sortConstructorHints() { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(Integer.class, builder -> { + builder.withConstructor(List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE); + builder.withConstructor(List.of(TypeReference.of(String.class), + TypeReference.of(Integer.class)), ExecutableMode.INVOKE); + }); + + RuntimeHints hints2 = new RuntimeHints(); + hints2.reflection().registerType(Integer.class, builder -> { + builder.withConstructor(List.of(TypeReference.of(String.class), + TypeReference.of(Integer.class)), ExecutableMode.INVOKE); + builder.withConstructor(List.of(TypeReference.of(String.class)), ExecutableMode.INVOKE); + }); + assertThat(writeJson(hints)).isEqualTo(writeJson(hints2)); + } + + @Test + void sortMethodHints() { + RuntimeHints hints = new RuntimeHints(); + hints.reflection().registerType(Integer.class, builder -> { + builder.withMethod("test", Collections.emptyList(), ExecutableMode.INVOKE); + builder.withMethod("another", Collections.emptyList(), ExecutableMode.INVOKE); + }); + + RuntimeHints hints2 = new RuntimeHints(); + hints2.reflection().registerType(Integer.class, builder -> { + builder.withMethod("another", Collections.emptyList(), ExecutableMode.INVOKE); + builder.withMethod("test", Collections.emptyList(), ExecutableMode.INVOKE); + }); + assertThat(writeJson(hints)).isEqualTo(writeJson(hints2)); + } + + } + + + @Nested + class JniHints { + + // TODO + + } + + + @Nested + class ResourceHintsTests { + + @Test + void empty() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + assertEquals("{}", hints); + } + + @Test + void registerExactMatch() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.resources().registerPattern("com/example/test.properties"); + hints.resources().registerPattern("com/example/another.properties"); + assertEquals(""" + { + "resources": [ + { "glob": "/" }, + { "glob": "com"}, + { "glob": "com/example"}, + { "glob": "com/example/another.properties"}, + { "glob": "com/example/test.properties"} + ] + }""", hints); + } + + @Test + void registerWildcardAtTheBeginningPattern() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.resources().registerPattern("*.properties"); + assertEquals(""" + { + "resources": [ + { "glob": "*.properties"}, + { "glob": "/"} + ] + }""", hints); + } + + @Test + void registerWildcardInTheMiddlePattern() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.resources().registerPattern("com/example/*.properties"); + assertEquals(""" + { + "resources": [ + { "glob": "/" }, + { "glob": "com"}, + { "glob": "com/example"}, + { "glob": "com/example/*.properties"} + ] + }""", hints); + } + + @Test + void registerWildcardAtTheEndPattern() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.resources().registerPattern("static/*"); + assertEquals(""" + { + "resources": [ + { "glob": "/" }, + { "glob": "static"}, + { "glob": "static/*"} + ] + }""", hints); + } + + @Test + void registerPatternWithIncludesAndExcludes() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.resources().registerPattern(hint -> hint.includes("com/example/*.properties")); + hints.resources().registerPattern(hint -> hint.includes("org/other/*.properties")); + assertEquals(""" + { + "resources": [ + { "glob": "/"}, + { "glob": "com"}, + { "glob": "com/example"}, + { "glob": "com/example/*.properties"}, + { "glob": "org"}, + { "glob": "org/other"}, + { "glob": "org/other/*.properties"} + ] + }""", hints); + } + + @Test + void registerWithReachableTypeCondition() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.resources().registerPattern(builder -> builder.includes(TypeReference.of("com.example.Test"), "com/example/test.properties")); + assertEquals(""" + { + "resources": [ + { "condition": { "typeReached": "com.example.Test"}, "glob": "/"}, + { "condition": { "typeReached": "com.example.Test"}, "glob": "com"}, + { "condition": { "typeReached": "com.example.Test"}, "glob": "com/example"}, + { "condition": { "typeReached": "com.example.Test"}, "glob": "com/example/test.properties"} + ] + }""", hints); + } + + @Test + void registerType() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.resources().registerType(String.class); + assertEquals(""" + { + "resources": [ + { "glob": "/" }, + { "glob": "java" }, + { "glob": "java/lang" }, + { "glob": "java/lang/String.class" } + ] + }""", hints); + } + + @Test + void registerResourceBundle() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.resources().registerResourceBundle("com.example.message2"); + hints.resources().registerResourceBundle("com.example.message"); + assertEquals(""" + { + "bundles": [ + { "name": "com.example.message"}, + { "name": "com.example.message2"} + ] + }""", hints); + } + } + + @Nested + class SerializationHintsTests { + + @Test + void shouldWriteEmptyHint() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + assertEquals("{}", hints); + } + + @Test + void shouldWriteSingleHint() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.serialization().registerType(TypeReference.of(String.class)); + assertEquals(""" + { + "serialization": [ + { "type": "java.lang.String" } + ] + } + """, hints); + } + + @Test + void shouldWriteMultipleHints() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.serialization() + .registerType(TypeReference.of(Environment.class)) + .registerType(TypeReference.of(String.class)); + assertEquals(""" + { + "serialization": [ + { "type": "java.lang.String" }, + { "type": "org.springframework.core.env.Environment" } + ] + } + """, hints); + } + + @Test + void shouldWriteSingleHintWithCondition() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.serialization().registerType(TypeReference.of(String.class), + builder -> builder.onReachableType(TypeReference.of("org.example.Test"))); + assertEquals(""" + { + "serialization": [ + { "condition": { "typeReached": "org.example.Test" }, "type": "java.lang.String" } + ] + } + """, hints); + } + + } + + @Nested + class ProxyHintsTests { + + @Test + void empty() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + assertEquals("{}", hints); + } + + @Test + void shouldWriteOneEntry() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.proxies().registerJdkProxy(Function.class); + assertEquals(""" + { + "reflection": [ + { + "type": { + "proxy": ["java.util.function.Function"] + } + } + ] + } + """, hints); + } + + @Test + void shouldWriteMultipleEntries() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.proxies().registerJdkProxy(Function.class) + .registerJdkProxy(Function.class, Consumer.class); + assertEquals(""" + { + "reflection": [ + { + "type": { "proxy": ["java.util.function.Function"] } + }, + { + "type": { "proxy": ["java.util.function.Function", "java.util.function.Consumer"] } + } + ] + } + """, hints); + } + + @Test + void shouldWriteEntriesInNaturalOrder() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.proxies().registerJdkProxy(Supplier.class) + .registerJdkProxy(Function.class); + assertEquals(""" + { + "reflection": [ + { + "type": { "proxy": ["java.util.function.Function"] } + }, + { + "type": { "proxy": ["java.util.function.Supplier"] } + } + ] + } + """, hints); + } + + @Test + void shouldWriteInnerClass() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.proxies().registerJdkProxy(InnerInterface.class); + assertEquals(""" + { + "reflection": [ + { + "type": { "proxy": ["org.springframework.aot.nativex.RuntimeHintsWriterTests$InnerInterface"] } + } + ] + } + """, hints); + } + + @Test + void shouldWriteCondition() throws JSONException { + RuntimeHints hints = new RuntimeHints(); + hints.proxies().registerJdkProxy(builder -> builder.proxiedInterfaces(Function.class) + .onReachableType(TypeReference.of("org.example.Test"))); + assertEquals(""" + { + "reflection": [ + { + "type": { "proxy": ["java.util.function.Function"] }, + "condition": { "typeReached": "org.example.Test" } + } + ] + } + """, hints); + } + + } + + private void assertEquals(String expectedString, RuntimeHints hints) throws JSONException { + String json = writeJson(hints); + JSONAssert.assertEquals(expectedString, json, JSONCompareMode.LENIENT); + Set validationMessages = JSON_SCHEMA.validate(json, InputFormat.JSON, executionContext -> + executionContext.getExecutionConfig().setFormatAssertionsEnabled(true)); + assertThat(validationMessages).isEmpty(); + } + + private String writeJson(RuntimeHints hints) { + StringWriter out = new StringWriter(); + BasicJsonWriter writer = new BasicJsonWriter(out, "\t"); + new RuntimeHintsWriter().write(writer, hints); + return out.toString(); + } + + + static class InnerClass { + + } + + interface InnerInterface { + + } + +} diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/SerializationHintsWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/SerializationHintsWriterTests.java deleted file mode 100644 index bef492224894..000000000000 --- a/spring-core/src/test/java/org/springframework/aot/nativex/SerializationHintsWriterTests.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2002-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.aot.nativex; - -import java.io.StringWriter; - -import org.json.JSONException; -import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.JSONAssert; -import org.skyscreamer.jsonassert.JSONCompareMode; - -import org.springframework.aot.hint.SerializationHints; -import org.springframework.aot.hint.TypeReference; -import org.springframework.core.env.Environment; - -/** - * Tests for {@link SerializationHintsWriter}. - * - * @author Sebastien Deleuze - */ -class SerializationHintsWriterTests { - - @Test - void shouldWriteEmptyHint() throws JSONException { - SerializationHints hints = new SerializationHints(); - assertEquals("[]", hints); - } - - @Test - void shouldWriteSingleHint() throws JSONException { - SerializationHints hints = new SerializationHints().registerType(TypeReference.of(String.class)); - assertEquals(""" - [ - { "name": "java.lang.String" } - ]""", hints); - } - - @Test - void shouldWriteMultipleHints() throws JSONException { - SerializationHints hints = new SerializationHints() - .registerType(TypeReference.of(Environment.class)) - .registerType(TypeReference.of(String.class)); - assertEquals(""" - [ - { "name": "java.lang.String" }, - { "name": "org.springframework.core.env.Environment" } - ]""", hints); - } - - @Test - void shouldWriteSingleHintWithCondition() throws JSONException { - SerializationHints hints = new SerializationHints().registerType(TypeReference.of(String.class), - builder -> builder.onReachableType(TypeReference.of("org.example.Test"))); - assertEquals(""" - [ - { "condition": { "typeReachable": "org.example.Test" }, "name": "java.lang.String" } - ]""", hints); - } - - private void assertEquals(String expectedString, SerializationHints hints) throws JSONException { - StringWriter out = new StringWriter(); - BasicJsonWriter writer = new BasicJsonWriter(out, "\t"); - SerializationHintsWriter.INSTANCE.write(writer, hints); - JSONAssert.assertEquals(expectedString, out.toString(), JSONCompareMode.STRICT); - } - -} diff --git a/spring-core/src/test/resources/org/springframework/aot/nativex/reachability-metadata-schema-v1.0.0.json b/spring-core/src/test/resources/org/springframework/aot/nativex/reachability-metadata-schema-v1.0.0.json new file mode 100644 index 000000000000..bb434fb22e2d --- /dev/null +++ b/spring-core/src/test/resources/org/springframework/aot/nativex/reachability-metadata-schema-v1.0.0.json @@ -0,0 +1,362 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://www.graalvm.org/reachability-metadata-schema-v1.0.0.json", + "title": "JSON schema for the reachability metadata used by GraalVM Native Image", + "type": "object", + "default": {}, + "properties": { + "comment": { + "title": "A comment applying to the whole file (e.g., generation date, author, etc.)", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "default": "" + }, + "reflection": { + "title": "Metadata to ensure elements are reachable through reflection", + "$ref": "#/$defs/reflection" + }, + "jni": { + "title": "Metadata to ensure elements are reachable through JNI", + "$ref": "#/$defs/reflection" + }, + "serialization": { + "title": "Metadata for types that are serialized or deserialized at run time. The types must extend 'java.io.Serializable'.", + "type": "array", + "default": [], + "items": { + "title": "Enables serializing and deserializing objects of the class specified by ", + "type": "object", + "properties": { + "reason": { + "title": "Reason for the type's inclusion in the serialization metadata", + "$ref": "#/$defs/reason" + }, + "condition": { + "title": "Condition under which the class should be registered for serialization", + "$ref": "#/$defs/condition" + }, + "type": { + "title": "Type descriptor of the class that should be registered for serialization", + "$ref": "#/$defs/type" + }, + "customTargetConstructorClass": { + "title": "Fully qualified name of the class whose constructor should be used to serialize the class specified by ", + "type": "string" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + }, + "resources": { + "title": "Metadata to ensure resources are available", + "type": "array", + "default": [], + "items": { + "title": "Resource that should be available", + "type": "object", + "properties": { + "reason": { + "title": "Reason for the resource's inclusion in the metadata", + "$ref": "#/$defs/reason" + }, + "condition": { + "title": "Condition under which the resource should be registered for runtime access", + "$ref": "#/$defs/condition" + }, + "module": { + "title": "Module containing the resource", + "type": "string", + "default": "" + }, + "glob": { + "title": "Resource name or pattern matching multiple resources (accepts * and ** wildcards)", + "type": "string" + } + }, + "required": [ + "glob" + ], + "additionalProperties": false + } + }, + "bundles": { + "title": "Metadata to ensure resource bundles are available", + "type": "array", + "default": [], + "items": { + "title": "Resource bundle that should be available", + "type": "object", + "properties": { + "reason": { + "title": "Reason for the resource bundle's inclusion in the metadata", + "$ref": "#/$defs/reason" + }, + "condition": { + "title": "Condition under which the resource bundle should be registered for runtime access", + "$ref": "#/$defs/condition" + }, + "name": { + "title": "Name of the resource bundle", + "type": "string" + }, + "locales": { + "title": "List of locales that should be registered for this resource bundle", + "type": "array", + "default": [], + "items": { + "type": "string" + } + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + } + }, + "required": [], + "additionalProperties": false, + + "$defs": { + "reflection": { + "type": "array", + "default": [], + "items": { + "title": "Elements that should be registered for reflection for a specified type", + "type": "object", + "properties": { + "reason": { + "title": "Reason for the element's inclusion", + "$ref": "#/$defs/reason" + }, + "condition": { + "title": "Condition under which the class should be registered for reflection", + "$ref": "#/$defs/condition" + }, + "type": { + "title": "Type descriptor of the class that should be registered for reflection", + "$ref": "#/$defs/type" + }, + "methods": { + "title": "List of methods that should be registered for the type declared in ", + "type": "array", + "default": [], + "items": { + "title": "Method descriptor of the method that should be registered for reflection", + "$ref": "#/$defs/method" + } + }, + "fields": { + "title": "List of class fields that can be read or written to for the type declared in ", + "type": "array", + "default": [], + "items": { + "title": "Field descriptor of the field that should be registered for reflection", + "$ref": "#/$defs/field" + } + }, + "allDeclaredMethods": { + "title": "Register all declared methods from the type for reflective invocation", + "type": "boolean", + "default": false + }, + "allDeclaredFields": { + "title": "Register all declared fields from the type for reflective access", + "type": "boolean", + "default": false + }, + "allDeclaredConstructors": { + "title": "Register all declared constructors from the type for reflective invocation", + "type": "boolean", + "default": false + }, + "allPublicMethods": { + "title": "Register all public methods from the type for reflective invocation", + "type": "boolean", + "default": false + }, + "allPublicFields": { + "title": "Register all public fields from the type for reflective access", + "type": "boolean", + "default": false + }, + "allPublicConstructors": { + "title": "Register all public constructors from the type for reflective invocation", + "type": "boolean", + "default": false + }, + "unsafeAllocated": { + "title": "Allow objects of this class to be instantiated with a call to jdk.internal.misc.Unsafe#allocateInstance or JNI's AllocObject", + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + } + }, + "jni": { + "type": "array", + "default": [], + "items": { + "title": "Elements that should be registered for JNI for a specified type", + "type": "object", + "properties": { + "reason": { + "title": "Reason for the element's inclusion", + "$ref": "#/$defs/reason" + }, + "condition": { + "title": "Condition under which the class should be registered for JNI", + "$ref": "#/$defs/condition" + }, + "type": { + "title": "Type descriptor of the class that should be registered for JNI", + "$ref": "#/$defs/type" + }, + "methods": { + "title": "List of methods that should be registered for the type declared in ", + "type": "array", + "default": [], + "items": { + "title": "Method descriptor of the method that should be registered for JNI", + "$ref": "#/$defs/method" + } + }, + "fields": { + "title": "List of class fields that can be read or written to for the type declared in ", + "type": "array", + "default": [], + "items": { + "title": "Field descriptor of the field that should be registered for JNI", + "$ref": "#/$defs/field" + } + }, + "allDeclaredMethods": { + "title": "Register all declared methods from the type for JNI access", + "type": "boolean", + "default": false + }, + "allDeclaredFields": { + "title": "Register all declared fields from the type for JNI access", + "type": "boolean", + "default": false + }, + "allDeclaredConstructors": { + "title": "Register all declared constructors from the type for JNI access", + "type": "boolean", + "default": false + }, + "allPublicMethods": { + "title": "Register all public methods from the type for JNI access", + "type": "boolean", + "default": false + }, + "allPublicFields": { + "title": "Register all public fields from the type for JNI access", + "type": "boolean", + "default": false + }, + "allPublicConstructors": { + "title": "Register all public constructors from the type for JNI access", + "type": "boolean", + "default": false + } + }, + "additionalProperties": false + } + }, + "reason": { + "type": "string", + "default": [] + }, + "condition": { + "title": "Condition used by GraalVM Native Image metadata files", + "type": "object", + "properties": { + "typeReached": { + "title": "Type descriptor of a class that must be reached in order to enable the corresponding registration", + "$ref": "#/$defs/type" + } + }, + "required": [ + "typeReached" + ], + "additionalProperties": false + }, + "type": { + "title": "Type descriptors used by GraalVM Native Image metadata files", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "proxy": { + "title": "List of interfaces defining the proxy class", + "type": "array", + "default": [], + "items": { + "title": "Fully qualified name of the interface defining the proxy class", + "type": "string" + } + } + }, + "required": [ + "proxy" + ], + "additionalProperties": false + } + ] + }, + "method": { + "title": "Method descriptors used by GraalVM Native Image metadata files", + "type": "object", + "properties": { + "name": { + "title": "Method name that should be registered for this class", + "type": "string" + }, + "parameterTypes": { + "default": [], + "items": { + "title": "List of the method's parameter types", + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + }, + "field": { + "title": "Field descriptors used by GraalVM Native Image metadata files", + "type": "object", + "properties": { + "name": { + "title": "Name of the field that should be registered for reflection", + "type": "string" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + } +} \ No newline at end of file From 71362c953c48de7dd428d84ddf660ee03d906a42 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 29 Nov 2024 14:43:59 +0100 Subject: [PATCH 022/701] Revisit RuntimeHints API The `RuntimeHints` API mainly reflects what is needed to write GraalVM reachability metadata. The latest GraalVM version simplified its format. This commit applies relevant simplifications as parts of it are not needed anymore. The new metadata format implies methods, constructors and fields introspection as soon as a reflection hint is registered for a type. As a result, `ExecutableMode.INTROSPECT`, and all `MemberCategory` values except `MemberCategory.INVOKE_*` are being deprecated. They have no replacement, as registering a type hint is enough. In practice, it is enough to replace this: ``` hints.reflection().registerType(MyType.class, MemberCategory.DECLARED_FIELDS); ``` By this: ``` hints.reflection().registerType(MyType.class); ``` As for `MemberCategory.PUBLIC_FIELDS` and `MemberCategory.DECLARED_FIELDS`, values were replaced by `INVOKE_PUBLIC_FIELDS` and `INVOKE_DECLARED_FIELDS` to make their original intent clearer and align with the rest of the API. Note, if you were using those values for reflection only, you can safely remove those hints in favor of a simple type hint. See gh-33847 --- .../aot/hint/ExecutableMode.java | 5 +- .../aot/hint/MemberCategory.java | 48 +++++++- .../predicate/ReflectionHintsPredicates.java | 104 ++++++++--------- .../predicate/ResourceHintsPredicates.java | 23 ++-- .../aot/hint/ExecutableHintTests.java | 1 + .../aot/hint/ExecutableModeTests.java | 3 +- .../aot/hint/ReflectionHintsTests.java | 1 + .../aot/hint/ResourceHintsTests.java | 7 +- .../aot/hint/RuntimeHintsTests.java | 8 +- .../aot/hint/TypeHintTests.java | 6 +- .../ReflectionHintsPredicatesTests.java | 105 ++++++++++-------- ...ilePatternResourceHintsRegistrarTests.java | 7 +- 12 files changed, 172 insertions(+), 146 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ExecutableMode.java b/spring-core/src/main/java/org/springframework/aot/hint/ExecutableMode.java index 7082859e7501..2de9e84c4423 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ExecutableMode.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ExecutableMode.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -30,7 +30,10 @@ public enum ExecutableMode { /** * Only retrieving the {@link Executable} and its metadata is required. + * @deprecated with no replacement since introspection is included + * when {@link ReflectionHints#registerType(Class, MemberCategory...) adding a reflection hint for a type}. */ + @Deprecated(since= "7.0", forRemoval = true) INTROSPECT, /** diff --git a/spring-core/src/main/java/org/springframework/aot/hint/MemberCategory.java b/spring-core/src/main/java/org/springframework/aot/hint/MemberCategory.java index 2d09bb0e4594..05b60208a6fd 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/MemberCategory.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/MemberCategory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -32,32 +32,62 @@ public enum MemberCategory { /** - * A category that represents public {@linkplain Field fields}. + * A category that represents introspection on public {@linkplain Field fields}. + * @deprecated with no replacement since introspection is included + * when {@link ReflectionHints#registerType(Class, MemberCategory...) adding a reflection hint for a type}. + * Use {@link #INVOKE_PUBLIC_FIELDS} if getting/setting field values is required. * @see Class#getFields() */ + @Deprecated(since = "7.0", forRemoval = true) PUBLIC_FIELDS, /** - * A category that represents {@linkplain Class#getDeclaredFields() declared + * A category that represents introspection on {@linkplain Class#getDeclaredFields() declared * fields}: all fields defined by the class but not inherited fields. + * @deprecated with no replacement since introspection is included + * when {@link ReflectionHints#registerType(Class, MemberCategory...) adding a reflection hint for a type}. + * Use {@link #INVOKE_DECLARED_FIELDS} if getting/setting field values is required. * @see Class#getDeclaredFields() */ + @Deprecated(since = "7.0", forRemoval = true) DECLARED_FIELDS, + /** + * A category that represents getting/setting values on public {@linkplain Field fields}. + * @see Field#get(Object) + * @see Field#set(Object, Object) + * @since 7.0 + */ + INVOKE_PUBLIC_FIELDS, + + /** + * A category that represents getting/setting values on declared {@linkplain Field fields}. + * @see Field#get(Object) + * @see Field#set(Object, Object) + * @since 7.0 + */ + INVOKE_DECLARED_FIELDS, + /** * A category that defines public {@linkplain Constructor constructors} can * be introspected but not invoked. + * @deprecated with no replacement since introspection is included + * when {@link ReflectionHints#registerType(Class, MemberCategory...) adding a reflection hint for a type}. * @see Class#getConstructors() * @see ExecutableMode#INTROSPECT */ + @Deprecated(since = "7.0", forRemoval = true) INTROSPECT_PUBLIC_CONSTRUCTORS, /** * A category that defines {@linkplain Class#getDeclaredConstructors() all * constructors} can be introspected but not invoked. + * @deprecated with no replacement since introspection is included + * when {@link ReflectionHints#registerType(Class, MemberCategory...) adding a reflection hint for a type}. * @see Class#getDeclaredConstructors() * @see ExecutableMode#INTROSPECT */ + @Deprecated(since = "7.0", forRemoval = true) INTROSPECT_DECLARED_CONSTRUCTORS, /** @@ -79,17 +109,23 @@ public enum MemberCategory { /** * A category that defines public {@linkplain Method methods}, including * inherited ones, can be introspected but not invoked. + * @deprecated with no replacement since introspection is added by default + * when {@link ReflectionHints#registerType(Class, MemberCategory...) adding a reflection hint for a type}. * @see Class#getMethods() * @see ExecutableMode#INTROSPECT */ + @Deprecated(since = "7.0", forRemoval = true) INTROSPECT_PUBLIC_METHODS, /** * A category that defines {@linkplain Class#getDeclaredMethods() all * methods}, excluding inherited ones, can be introspected but not invoked. + * @deprecated with no replacement since introspection is added by default + * when {@link ReflectionHints#registerType(Class, MemberCategory...) adding a reflection hint for a type}. * @see Class#getDeclaredMethods() * @see ExecutableMode#INTROSPECT */ + @Deprecated(since = "7.0", forRemoval = true) INTROSPECT_DECLARED_METHODS, /** @@ -114,7 +150,10 @@ public enum MemberCategory { *

Contrary to other categories, this does not register any particular * reflection for inner classes but rather makes sure they are available * via a call to {@link Class#getClasses}. + * @deprecated with no replacement since introspection is included + * when {@link ReflectionHints#registerType(Class, MemberCategory...) adding a reflection hint for a type}. */ + @Deprecated(since = "7.0", forRemoval = true) PUBLIC_CLASSES, /** @@ -123,7 +162,10 @@ public enum MemberCategory { *

Contrary to other categories, this does not register any particular * reflection for inner classes but rather makes sure they are available * via a call to {@link Class#getDeclaredClasses}. + * @deprecated with no replacement since introspection is included + * when {@link ReflectionHints#registerType(Class, MemberCategory...) adding a reflection hint for a type}. */ + @Deprecated(since = "7.0", forRemoval = true) DECLARED_CLASSES } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java b/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java index a9f87de77ac1..69ba572e3503 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicates.java @@ -255,6 +255,7 @@ public Predicate withAnyMemberCategory(MemberCategory... memberCat } + @SuppressWarnings("removal") public abstract static class ExecutableHintPredicate implements Predicate { protected final T executable; @@ -299,6 +300,7 @@ static boolean includes(ExecutableHint hint, String name, } + @SuppressWarnings("removal") public static class ConstructorHintPredicate extends ExecutableHintPredicate> { ConstructorHintPredicate(Constructor constructor) { @@ -308,28 +310,17 @@ public static class ConstructorHintPredicate extends ExecutableHintPredicate Modifier.isPublic(this.executable.getModifiers()))) - .or(new TypeHintPredicate(TypeReference.of(this.executable.getDeclaringClass())).withAnyMemberCategory(getDeclaredMemberCategories())) + .and(hints -> this.executableMode == ExecutableMode.INTROSPECT)) + .or(new TypeHintPredicate(TypeReference.of(this.executable.getDeclaringClass())) + .withMemberCategory(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS) + .and(hints -> Modifier.isPublic(this.executable.getModifiers())) + .and(hints -> this.executableMode == ExecutableMode.INVOKE)) + .or(new TypeHintPredicate(TypeReference.of(this.executable.getDeclaringClass())) + .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS) + .and(hints -> this.executableMode == ExecutableMode.INVOKE)) .or(exactMatch()).test(runtimeHints); } - MemberCategory[] getPublicMemberCategories() { - if (this.executableMode == ExecutableMode.INTROSPECT) { - return new MemberCategory[] { MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS, - MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS }; - } - return new MemberCategory[] { MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS }; - } - - MemberCategory[] getDeclaredMemberCategories() { - if (this.executableMode == ExecutableMode.INTROSPECT) { - return new MemberCategory[] { MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, - MemberCategory.INVOKE_DECLARED_CONSTRUCTORS }; - } - return new MemberCategory[] { MemberCategory.INVOKE_DECLARED_CONSTRUCTORS }; - } - @Override Predicate exactMatch() { return hints -> { @@ -343,6 +334,7 @@ Predicate exactMatch() { } + @SuppressWarnings("removal") public static class MethodHintPredicate extends ExecutableHintPredicate { MethodHintPredicate(Method method) { @@ -352,31 +344,18 @@ public static class MethodHintPredicate extends ExecutableHintPredicate @Override public boolean test(RuntimeHints runtimeHints) { return (new TypeHintPredicate(TypeReference.of(this.executable.getDeclaringClass())) - .withAnyMemberCategory(getPublicMemberCategories()) - .and(hints -> Modifier.isPublic(this.executable.getModifiers()))) - .or(new TypeHintPredicate(TypeReference.of(this.executable.getDeclaringClass())) - .withAnyMemberCategory(getDeclaredMemberCategories()) - .and(hints -> !Modifier.isPublic(this.executable.getModifiers()))) + .and(hints -> this.executableMode == ExecutableMode.INTROSPECT)) + .or((new TypeHintPredicate(TypeReference.of(this.executable.getDeclaringClass())) + .withMemberCategory(MemberCategory.INVOKE_PUBLIC_METHODS) + .and(hints -> Modifier.isPublic(this.executable.getModifiers())) + .and(hints -> this.executableMode == ExecutableMode.INVOKE))) + .or((new TypeHintPredicate(TypeReference.of(this.executable.getDeclaringClass())) + .withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS) + .and(hints -> !Modifier.isPublic(this.executable.getModifiers())) + .and(hints -> this.executableMode == ExecutableMode.INVOKE))) .or(exactMatch()).test(runtimeHints); } - MemberCategory[] getPublicMemberCategories() { - if (this.executableMode == ExecutableMode.INTROSPECT) { - return new MemberCategory[] { MemberCategory.INTROSPECT_PUBLIC_METHODS, - MemberCategory.INVOKE_PUBLIC_METHODS }; - } - return new MemberCategory[] { MemberCategory.INVOKE_PUBLIC_METHODS }; - } - - MemberCategory[] getDeclaredMemberCategories() { - - if (this.executableMode == ExecutableMode.INTROSPECT) { - return new MemberCategory[] { MemberCategory.INTROSPECT_DECLARED_METHODS, - MemberCategory.INVOKE_DECLARED_METHODS }; - } - return new MemberCategory[] { MemberCategory.INVOKE_DECLARED_METHODS }; - } - @Override Predicate exactMatch() { return hints -> { @@ -394,31 +373,40 @@ public static class FieldHintPredicate implements Predicate { private final Field field; + @Nullable + private ExecutableMode executableMode; + FieldHintPredicate(Field field) { this.field = field; } + /** + * Refine the current predicate to only match if an invocation hint is registered for this field. + * @return the refined {@link RuntimeHints} predicate + * @since 7.0 + */ + public FieldHintPredicate invocation() { + this.executableMode = ExecutableMode.INVOKE; + return this; + } + @Override public boolean test(RuntimeHints runtimeHints) { TypeHint typeHint = runtimeHints.reflection().getTypeHint(this.field.getDeclaringClass()); - if (typeHint == null) { - return false; - } - return memberCategoryMatch(typeHint) || exactMatch(typeHint); - } - - private boolean memberCategoryMatch(TypeHint typeHint) { - if (Modifier.isPublic(this.field.getModifiers())) { - return typeHint.getMemberCategories().contains(MemberCategory.PUBLIC_FIELDS); + if (typeHint != null) { + if (this.executableMode == ExecutableMode.INVOKE) { + if (Modifier.isPublic(this.field.getModifiers())) { + return typeHint.getMemberCategories().contains(MemberCategory.INVOKE_PUBLIC_FIELDS); + } + else { + return typeHint.getMemberCategories().contains(MemberCategory.INVOKE_DECLARED_FIELDS); + } + } + else { + return true; + } } - else { - return typeHint.getMemberCategories().contains(MemberCategory.DECLARED_FIELDS); - } - } - - private boolean exactMatch(TypeHint typeHint) { - return typeHint.fields().anyMatch(fieldHint -> - this.field.getName().equals(fieldHint.getName())); + return false; } } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/predicate/ResourceHintsPredicates.java b/spring-core/src/main/java/org/springframework/aot/hint/predicate/ResourceHintsPredicates.java index d59d48af6cf0..302354694360 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/predicate/ResourceHintsPredicates.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/predicate/ResourceHintsPredicates.java @@ -19,14 +19,13 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; -import java.util.regex.Pattern; import org.springframework.aot.hint.ResourceHints; import org.springframework.aot.hint.ResourcePatternHint; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.TypeReference; +import org.springframework.util.AntPathMatcher; import org.springframework.util.Assert; -import org.springframework.util.ConcurrentLruCache; /** * Generator of {@link ResourceHints} predicates, testing whether the given hints @@ -39,7 +38,7 @@ */ public class ResourceHintsPredicates { - private static final ConcurrentLruCache CACHED_RESOURCE_PATTERNS = new ConcurrentLruCache<>(32, ResourcePatternHint::toRegex); + private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher(); ResourceHintsPredicates() { } @@ -100,26 +99,18 @@ public Predicate forResource(String resourceName) { return hints -> { AggregatedResourcePatternHints aggregatedResourcePatternHints = AggregatedResourcePatternHints.of( hints.resources()); - boolean isExcluded = aggregatedResourcePatternHints.excludes().stream().anyMatch(excluded -> - CACHED_RESOURCE_PATTERNS.get(excluded).matcher(resourceNameToUse).matches()); - if (isExcluded) { - return false; - } return aggregatedResourcePatternHints.includes().stream().anyMatch(included -> - CACHED_RESOURCE_PATTERNS.get(included).matcher(resourceNameToUse).matches()); + PATH_MATCHER.match(included.getPattern(), resourceNameToUse)); }; } - private record AggregatedResourcePatternHints(List includes, List excludes) { + private record AggregatedResourcePatternHints(List includes) { static AggregatedResourcePatternHints of(ResourceHints resourceHints) { List includes = new ArrayList<>(); - List excludes = new ArrayList<>(); - resourceHints.resourcePatternHints().forEach(resourcePatternHint -> { - includes.addAll(resourcePatternHint.getIncludes()); - excludes.addAll(resourcePatternHint.getExcludes()); - }); - return new AggregatedResourcePatternHints(includes, excludes); + resourceHints.resourcePatternHints().forEach(resourcePatternHint -> + includes.addAll(resourcePatternHint.getIncludes())); + return new AggregatedResourcePatternHints(includes); } } diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ExecutableHintTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ExecutableHintTests.java index f817ba56b82c..978934af799b 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/ExecutableHintTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/ExecutableHintTests.java @@ -28,6 +28,7 @@ * @author Phillip Webb * @since 6.0 */ +@SuppressWarnings("removal") class ExecutableHintTests { @Test diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ExecutableModeTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ExecutableModeTests.java index d19df791de25..ec16727bb9c3 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/ExecutableModeTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/ExecutableModeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -25,6 +25,7 @@ * * @author Stephane Nicoll */ +@SuppressWarnings("removal") class ExecutableModeTests { @Test diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java index 5e4c3cc14f8a..7756980bdb11 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java @@ -37,6 +37,7 @@ * @author Stephane Nicoll * @author Sebastien Deleuze */ +@SuppressWarnings("removal") class ReflectionHintsTests { private final ReflectionHints reflectionHints = new ReflectionHints(); diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ResourceHintsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ResourceHintsTests.java index b8393dd52ba3..19e57e873b7b 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/ResourceHintsTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/ResourceHintsTests.java @@ -117,7 +117,7 @@ void registerPattern() { @Test void registerPatternWithIncludesAndExcludes() { this.resourceHints.registerPattern(resourceHint -> - resourceHint.includes("com/example/*.properties").excludes("com/example/to-ignore.properties")); + resourceHint.includes("com/example/*.properties")); assertThat(this.resourceHints.resourcePatternHints()).singleElement().satisfies(patternOf( List.of("/", "com", "com/example", "com/example/*.properties"), List.of("com/example/to-ignore.properties"))); @@ -198,10 +198,7 @@ private Consumer resourceBundle(String baseName) { } private Consumer patternOf(List includes, List excludes) { - return pattern -> { - assertThat(pattern.getIncludes()).map(ResourcePatternHint::getPattern).containsExactlyInAnyOrderElementsOf(includes); - assertThat(pattern.getExcludes()).map(ResourcePatternHint::getPattern).containsExactlyElementsOf(excludes); - }; + return pattern -> assertThat(pattern.getIncludes()).map(ResourcePatternHint::getPattern).containsExactlyInAnyOrderElementsOf(includes); } static class Nested { diff --git a/spring-core/src/test/java/org/springframework/aot/hint/RuntimeHintsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/RuntimeHintsTests.java index f88170d28708..1778a82f0398 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/RuntimeHintsTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/RuntimeHintsTests.java @@ -47,11 +47,9 @@ void reflectionHintWithClass() { @Test void resourceHintWithClass() { this.hints.resources().registerType(String.class); - assertThat(this.hints.resources().resourcePatternHints()).singleElement().satisfies(resourceHint -> { - assertThat(resourceHint.getIncludes()).map(ResourcePatternHint::getPattern) - .containsExactlyInAnyOrder("/", "java", "java/lang", "java/lang/String.class"); - assertThat(resourceHint.getExcludes()).isEmpty(); - }); + assertThat(this.hints.resources().resourcePatternHints()).singleElement().satisfies(resourceHint -> + assertThat(resourceHint.getIncludes()).map(ResourcePatternHint::getPattern) + .containsExactlyInAnyOrder("/", "java", "java/lang", "java/lang/String.class")); } @Test diff --git a/spring-core/src/test/java/org/springframework/aot/hint/TypeHintTests.java b/spring-core/src/test/java/org/springframework/aot/hint/TypeHintTests.java index 5c84ec93bbc8..4aa0f6b97a04 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/TypeHintTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/TypeHintTests.java @@ -31,6 +31,7 @@ * * @author Stephane Nicoll */ +@SuppressWarnings("removal") class TypeHintTests { @Test @@ -169,9 +170,8 @@ void typeHintHasAppropriateToString() { void builtWithAppliesMemberCategories() { TypeHint.Builder builder = new TypeHint.Builder(TypeReference.of(String.class)); assertThat(builder.build().getMemberCategories()).isEmpty(); - TypeHint.builtWith(MemberCategory.DECLARED_CLASSES, MemberCategory.DECLARED_FIELDS).accept(builder); - assertThat(builder.build().getMemberCategories()).containsExactlyInAnyOrder(MemberCategory.DECLARED_CLASSES, - MemberCategory.DECLARED_FIELDS); + TypeHint.builtWith(MemberCategory.DECLARED_FIELDS).accept(builder); + assertThat(builder.build().getMemberCategories()).containsExactly(MemberCategory.DECLARED_FIELDS); } } diff --git a/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java b/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java index 896da82a60dd..3ba1db988ab6 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/predicate/ReflectionHintsPredicatesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -40,6 +40,7 @@ * * @author Brian Clozel */ +@SuppressWarnings("removal") class ReflectionHintsPredicatesTests { private static Constructor privateConstructor; @@ -160,6 +161,12 @@ void constructorIntrospectionDoesNotMatchMissingHint() { assertPredicateDoesNotMatch(reflection.onConstructor(publicConstructor).introspect()); } + @Test + void constructorIntrospectionMatchesTypeHint() { + runtimeHints.reflection().registerType(SampleClass.class); + assertPredicateMatches(reflection.onConstructor(publicConstructor).introspect()); + } + @Test void constructorIntrospectionMatchesConstructorHint() { runtimeHints.reflection().registerType(SampleClass.class, typeHint -> @@ -230,22 +237,16 @@ void constructorInvocationMatchesInvokeDeclaredConstructors() { } @Test - void privateConstructorIntrospectionMatchesConstructorHint() { - runtimeHints.reflection().registerType(SampleClass.class, typeHint -> - typeHint.withConstructor(TypeReference.listOf(String.class), ExecutableMode.INTROSPECT)); + void privateConstructorIntrospectionMatchesTypeHint() { + runtimeHints.reflection().registerType(SampleClass.class); assertPredicateMatches(reflection.onConstructor(privateConstructor).introspect()); } @Test - void privateConstructorIntrospectionDoesNotMatchIntrospectPublicConstructors() { - runtimeHints.reflection().registerType(SampleClass.class, MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS); - assertPredicateDoesNotMatch(reflection.onConstructor(privateConstructor).introspect()); - } - - @Test - void privateConstructorIntrospectionDoesNotMatchInvokePublicConstructors() { - runtimeHints.reflection().registerType(SampleClass.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); - assertPredicateDoesNotMatch(reflection.onConstructor(privateConstructor).introspect()); + void privateConstructorIntrospectionMatchesConstructorHint() { + runtimeHints.reflection().registerType(SampleClass.class, typeHint -> + typeHint.withConstructor(TypeReference.listOf(String.class), ExecutableMode.INTROSPECT)); + assertPredicateMatches(reflection.onConstructor(privateConstructor).introspect()); } @Test @@ -303,6 +304,12 @@ void privateConstructorInvocationMatchesInvokeDeclaredConstructors() { @Nested class ReflectionOnMethod { + @Test + void methodIntrospectionMatchesTypeHint() { + runtimeHints.reflection().registerType(SampleClass.class); + assertPredicateMatches(reflection.onMethod(SampleClass.class, "publicMethod").introspect()); + } + @Test void methodIntrospectionMatchesMethodHint() { runtimeHints.reflection().registerType(SampleClass.class, typeHint -> @@ -328,18 +335,6 @@ void methodIntrospectionMatchesInvokePublicMethods() { assertPredicateMatches(reflection.onMethod(SampleClass.class, "publicMethod").introspect()); } - @Test - void methodIntrospectionDoesNotMatchIntrospectDeclaredMethods() { - runtimeHints.reflection().registerType(SampleClass.class, MemberCategory.INTROSPECT_DECLARED_METHODS); - assertPredicateDoesNotMatch(reflection.onMethod(SampleClass.class, "publicMethod").introspect()); - } - - @Test - void methodIntrospectionDoesNotMatchInvokeDeclaredMethods() { - runtimeHints.reflection().registerType(SampleClass.class, MemberCategory.INVOKE_DECLARED_METHODS); - assertPredicateDoesNotMatch(reflection.onMethod(SampleClass.class, "publicMethod").introspect()); - } - @Test void methodInvocationDoesNotMatchMethodHint() { runtimeHints.reflection().registerType(SampleClass.class, typeHint -> @@ -379,22 +374,16 @@ void methodInvocationDoesNotMatchInvokeDeclaredMethods() { } @Test - void privateMethodIntrospectionMatchesMethodHint() { - runtimeHints.reflection().registerType(SampleClass.class, typeHint -> - typeHint.withMethod("privateMethod", Collections.emptyList(), ExecutableMode.INTROSPECT)); + void privateMethodIntrospectionMatchesTypeHint() { + runtimeHints.reflection().registerType(SampleClass.class); assertPredicateMatches(reflection.onMethod(SampleClass.class, "privateMethod").introspect()); } @Test - void privateMethodIntrospectionDoesNotMatchIntrospectPublicMethods() { - runtimeHints.reflection().registerType(SampleClass.class, MemberCategory.INTROSPECT_PUBLIC_METHODS); - assertPredicateDoesNotMatch(reflection.onMethod(SampleClass.class, "privateMethod").introspect()); - } - - @Test - void privateMethodIntrospectionDoesNotMatchInvokePublicMethods() { - runtimeHints.reflection().registerType(SampleClass.class, MemberCategory.INVOKE_PUBLIC_METHODS); - assertPredicateDoesNotMatch(reflection.onMethod(SampleClass.class, "privateMethod").introspect()); + void privateMethodIntrospectionMatchesMethodHint() { + runtimeHints.reflection().registerType(SampleClass.class, typeHint -> + typeHint.withMethod("privateMethod", Collections.emptyList(), ExecutableMode.INTROSPECT)); + assertPredicateMatches(reflection.onMethod(SampleClass.class, "privateMethod").introspect()); } @Test @@ -464,15 +453,15 @@ void shouldFailForUnknownClass() { } @Test - void fieldReflectionMatchesFieldHint() { - runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withField("publicField")); + void fieldReflectionMatchesTypeHint() { + runtimeHints.reflection().registerType(SampleClass.class); assertPredicateMatches(reflection.onField(SampleClass.class, "publicField")); } @Test - void fieldReflectionDoesNotMatchNonRegisteredFielddHint() { + void fieldReflectionMatchesFieldHint() { runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withField("publicField")); - assertPredicateDoesNotMatch(reflection.onField(SampleClass.class, "privateField")); + assertPredicateMatches(reflection.onField(SampleClass.class, "publicField")); } @Test @@ -482,21 +471,27 @@ void fieldReflectionMatchesPublicFieldsHint() { } @Test - void fieldReflectionDoesNotMatchDeclaredFieldsHint() { - runtimeHints.reflection().registerType(SampleClass.class, MemberCategory.DECLARED_FIELDS); - assertPredicateDoesNotMatch(reflection.onField(SampleClass.class, "publicField")); + void fieldInvocationMatchesPublicFieldsHint() { + runtimeHints.reflection().registerType(SampleClass.class, MemberCategory.INVOKE_PUBLIC_FIELDS); + assertPredicateMatches(reflection.onField(SampleClass.class, "publicField").invocation()); } @Test - void privateFieldReflectionMatchesFieldHint() { - runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withField("privateField")); + void fieldInvocationDoesNotMatchTypeHint() { + runtimeHints.reflection().registerType(SampleClass.class); + assertPredicateDoesNotMatch(reflection.onField(SampleClass.class, "publicField").invocation()); + } + + @Test + void privateFieldReflectionMatchesTypeHint() { + runtimeHints.reflection().registerType(SampleClass.class); assertPredicateMatches(reflection.onField(SampleClass.class, "privateField")); } @Test - void privateFieldReflectionDoesNotMatchPublicFieldsHint() { - runtimeHints.reflection().registerType(SampleClass.class, MemberCategory.PUBLIC_FIELDS); - assertPredicateDoesNotMatch(reflection.onField(SampleClass.class, "privateField")); + void privateFieldReflectionMatchesFieldHint() { + runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withField("privateField")); + assertPredicateMatches(reflection.onField(SampleClass.class, "privateField")); } @Test @@ -505,6 +500,18 @@ void privateFieldReflectionMatchesDeclaredFieldsHint() { assertPredicateMatches(reflection.onField(SampleClass.class, "privateField")); } + @Test + void privateFieldInvocationMatchesDeclaredFieldsHint() { + runtimeHints.reflection().registerType(SampleClass.class, MemberCategory.INVOKE_DECLARED_FIELDS); + assertPredicateMatches(reflection.onField(SampleClass.class, "privateField").invocation()); + } + + @Test + void privateFieldInvocationDoesNotMatchTypeHint() { + runtimeHints.reflection().registerType(SampleClass.class); + assertPredicateDoesNotMatch(reflection.onField(SampleClass.class, "privateField").invocation()); + } + } private void assertPredicateMatches(Predicate predicate) { diff --git a/spring-core/src/test/java/org/springframework/aot/hint/support/FilePatternResourceHintsRegistrarTests.java b/spring-core/src/test/java/org/springframework/aot/hint/support/FilePatternResourceHintsRegistrarTests.java index c34af3867126..a8c93b1ec9da 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/support/FilePatternResourceHintsRegistrarTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/support/FilePatternResourceHintsRegistrarTests.java @@ -140,11 +140,8 @@ void registerWithNonExistingLocationDoesNotRegisterHint() { } private Consumer includes(String... patterns) { - return hint -> { - assertThat(hint.getIncludes().stream().map(ResourcePatternHint::getPattern)) - .containsExactlyInAnyOrder(patterns); - assertThat(hint.getExcludes()).isEmpty(); - }; + return hint -> assertThat(hint.getIncludes().stream().map(ResourcePatternHint::getPattern)) + .containsExactlyInAnyOrder(patterns); } } From 0759129c14788f733fa7f5b20a0028fb638d2f49 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 29 Nov 2024 14:44:02 +0100 Subject: [PATCH 023/701] Deprecate RuntimeHintsAgent Java agent This Java agent has been designed to instrument specific JDK calls for reflective introspection and invocations. This is useful for testing runtime hints in integration tests. As of GraalVM 23, there is a new VM option that does this in a much more efficient and precise fashion. Developers can use the `-XX:MissingRegistrationReportingMode` with the "Warn" or "Exit" value. The option will look for reachability metadata and emit logs in "Warn" mode or immediately exit the application with in "Exit" mode. This commit deprecates the `RuntimeHintsAgent` in favor of this new, more reliable option. See gh-33847 --- .../aot/agent/InstrumentedMethod.java | 55 +--- .../aot/agent/RuntimeHintsAgent.java | 3 + .../aot/test/agent/RuntimeHintsRecorder.java | 9 +- .../aot/agent/InstrumentedMethodTests.java | 296 +++--------------- 4 files changed, 76 insertions(+), 287 deletions(-) diff --git a/spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedMethod.java b/spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedMethod.java index 2c320d67e5e6..153a16e99708 100644 --- a/spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedMethod.java +++ b/spring-core-test/src/main/java/org/springframework/aot/agent/InstrumentedMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -20,13 +20,11 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.util.ResourceBundle; import java.util.function.Function; import java.util.function.Predicate; -import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.TypeReference; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; @@ -67,8 +65,7 @@ enum InstrumentedMethod { CLASS_GETCLASSES(Class.class, "getClasses", HintType.REFLECTION, invocation -> { Class thisClass = invocation.getInstance(); - return reflection().onType(TypeReference.of(thisClass)) - .withAnyMemberCategory(MemberCategory.DECLARED_CLASSES, MemberCategory.PUBLIC_CLASSES); + return reflection().onType(TypeReference.of(thisClass)); } ), @@ -81,7 +78,7 @@ enum InstrumentedMethod { if (constructor == null) { return runtimeHints -> false; } - return reflection().onConstructor(constructor).introspect(); + return reflection().onType(constructor.getDeclaringClass()); } ), @@ -91,9 +88,7 @@ enum InstrumentedMethod { CLASS_GETCONSTRUCTORS(Class.class, "getConstructors", HintType.REFLECTION, invocation -> { Class thisClass = invocation.getInstance(); - return reflection().onType(TypeReference.of(thisClass)).withAnyMemberCategory( - MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS, MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, - MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); + return reflection().onType(TypeReference.of(thisClass)); } ), @@ -103,7 +98,7 @@ enum InstrumentedMethod { CLASS_GETDECLAREDCLASSES(Class.class, "getDeclaredClasses", HintType.REFLECTION, invocation -> { Class thisClass = invocation.getInstance(); - return reflection().onType(TypeReference.of(thisClass)).withMemberCategory(MemberCategory.DECLARED_CLASSES); + return reflection().onType(TypeReference.of(thisClass)); } ), @@ -116,9 +111,7 @@ enum InstrumentedMethod { if (constructor == null) { return runtimeHints -> false; } - TypeReference thisType = invocation.getInstanceTypeReference(); - return reflection().onType(thisType).withMemberCategory(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS) - .or(reflection().onConstructor(constructor).introspect()); + return reflection().onType(constructor.getDeclaringClass()); } ), @@ -128,8 +121,7 @@ enum InstrumentedMethod { CLASS_GETDECLAREDCONSTRUCTORS(Class.class, "getDeclaredConstructors", HintType.REFLECTION, invocation -> { Class thisClass = invocation.getInstance(); - return reflection().onType(TypeReference.of(thisClass)) - .withAnyMemberCategory(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); + return reflection().onType(TypeReference.of(thisClass)); }), /** @@ -141,9 +133,7 @@ enum InstrumentedMethod { if (field == null) { return runtimeHints -> false; } - TypeReference thisType = invocation.getInstanceTypeReference(); - return reflection().onType(thisType).withMemberCategory(MemberCategory.DECLARED_FIELDS) - .or(reflection().onField(field)); + return reflection().onType(field.getDeclaringClass()); } ), @@ -153,7 +143,7 @@ enum InstrumentedMethod { CLASS_GETDECLAREDFIELDS(Class.class, "getDeclaredFields", HintType.REFLECTION, invocation -> { Class thisClass = invocation.getInstance(); - return reflection().onType(TypeReference.of(thisClass)).withMemberCategory(MemberCategory.DECLARED_FIELDS); + return reflection().onType(TypeReference.of(thisClass)); } ), @@ -166,10 +156,7 @@ enum InstrumentedMethod { if (method == null) { return runtimeHints -> false; } - TypeReference thisType = invocation.getInstanceTypeReference(); - return reflection().onType(thisType) - .withAnyMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_METHODS) - .or(reflection().onMethod(method).introspect()); + return reflection().onType(method.getDeclaringClass()); } ), @@ -179,8 +166,7 @@ enum InstrumentedMethod { CLASS_GETDECLAREDMETHODS(Class.class, "getDeclaredMethods", HintType.REFLECTION, invocation -> { Class thisClass = invocation.getInstance(); - return reflection().onType(TypeReference.of(thisClass)) - .withAnyMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_METHODS); + return reflection().onType(TypeReference.of(thisClass)); } ), @@ -194,10 +180,7 @@ enum InstrumentedMethod { if (field == null) { return runtimeHints -> false; } - TypeReference thisType = invocation.getInstanceTypeReference(); - return reflection().onType(thisType).withMemberCategory(MemberCategory.PUBLIC_FIELDS) - .and(runtimeHints -> Modifier.isPublic(field.getModifiers())) - .or(reflection().onType(thisType).withMemberCategory(MemberCategory.DECLARED_FIELDS)) + return reflection().onType(field.getDeclaringClass()) .or(reflection().onField(invocation.getReturnValue())); }), @@ -207,8 +190,7 @@ enum InstrumentedMethod { CLASS_GETFIELDS(Class.class, "getFields", HintType.REFLECTION, invocation -> { Class thisClass = invocation.getInstance(); - return reflection().onType(TypeReference.of(thisClass)) - .withAnyMemberCategory(MemberCategory.PUBLIC_FIELDS, MemberCategory.DECLARED_FIELDS); + return reflection().onType(TypeReference.of(thisClass)); } ), @@ -221,12 +203,7 @@ enum InstrumentedMethod { if (method == null) { return runtimeHints -> false; } - TypeReference thisType = invocation.getInstanceTypeReference(); - return reflection().onType(thisType).withAnyMemberCategory(MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS) - .and(runtimeHints -> Modifier.isPublic(method.getModifiers())) - .or(reflection().onType(thisType).withAnyMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_METHODS)) - .or(reflection().onMethod(method).introspect()) - .or(reflection().onMethod(method).invoke()); + return reflection().onType(method.getDeclaringClass()); } ), @@ -236,9 +213,7 @@ enum InstrumentedMethod { CLASS_GETMETHODS(Class.class, "getMethods", HintType.REFLECTION, invocation -> { Class thisClass = invocation.getInstance(); - return reflection().onType(TypeReference.of(thisClass)).withAnyMemberCategory( - MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS, - MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS); + return reflection().onType(TypeReference.of(thisClass)); } ), diff --git a/spring-core-test/src/main/java/org/springframework/aot/agent/RuntimeHintsAgent.java b/spring-core-test/src/main/java/org/springframework/aot/agent/RuntimeHintsAgent.java index 375fdb2dbac5..e03f71de77f1 100644 --- a/spring-core-test/src/main/java/org/springframework/aot/agent/RuntimeHintsAgent.java +++ b/spring-core-test/src/main/java/org/springframework/aot/agent/RuntimeHintsAgent.java @@ -39,7 +39,10 @@ * @author Brian Clozel * @since 6.0 * @see InvocationsRecorderClassTransformer + * @deprecated as of 7.0 in favor of the {@code -XX:MissingRegistrationReportingMode=Warn} and + * {@code -XX:MissingRegistrationReportingMode=Exit} JVM flags with GraalVM. */ +@Deprecated(forRemoval = true) public final class RuntimeHintsAgent { private static boolean loaded = false; diff --git a/spring-core-test/src/main/java/org/springframework/aot/test/agent/RuntimeHintsRecorder.java b/spring-core-test/src/main/java/org/springframework/aot/test/agent/RuntimeHintsRecorder.java index 50faf662d444..b6471174ffce 100644 --- a/spring-core-test/src/main/java/org/springframework/aot/test/agent/RuntimeHintsRecorder.java +++ b/spring-core-test/src/main/java/org/springframework/aot/test/agent/RuntimeHintsRecorder.java @@ -22,17 +22,20 @@ import org.springframework.aot.agent.RecordedInvocation; import org.springframework.aot.agent.RecordedInvocationsListener; import org.springframework.aot.agent.RecordedInvocationsPublisher; -import org.springframework.aot.agent.RuntimeHintsAgent; import org.springframework.aot.hint.RuntimeHints; import org.springframework.util.Assert; /** * Invocations relevant to {@link RuntimeHints} recorded during the execution of a block - * of code instrumented by the {@link RuntimeHintsAgent}. + * of code instrumented by the {@link org.springframework.aot.agent.RuntimeHintsAgent}. * * @author Brian Clozel * @since 6.0 + * @deprecated as of 7.0 in favor of the {@code -XX:MissingRegistrationReportingMode=Warn} and + * {@code -XX:MissingRegistrationReportingMode=Exit} JVM flags with GraalVM. */ +@Deprecated(forRemoval = true) +@SuppressWarnings("removal") public final class RuntimeHintsRecorder { private final RuntimeHintsInvocationsListener listener; @@ -49,7 +52,7 @@ private RuntimeHintsRecorder() { */ public static synchronized RuntimeHintsInvocations record(Runnable action) { Assert.notNull(action, "Runnable action must not be null"); - Assert.state(RuntimeHintsAgent.isLoaded(), "RuntimeHintsAgent must be loaded in the current JVM"); + Assert.state(org.springframework.aot.agent.RuntimeHintsAgent.isLoaded(), "RuntimeHintsAgent must be loaded in the current JVM"); RuntimeHintsRecorder recorder = new RuntimeHintsRecorder(); RecordedInvocationsPublisher.addListener(recorder.listener); try { diff --git a/spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java b/spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java index 48a9b68652e6..70a86c6f0dc8 100644 --- a/spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java +++ b/spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java @@ -61,32 +61,26 @@ void classForNameShouldMatchReflectionOnType() { } @Test - void classGetClassesShouldNotMatchReflectionOnType() { - hints.reflection().registerType(String.class); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETCLASSES, this.stringGetClasses); - } - - @Test - void classGetClassesShouldMatchPublicClasses() { - hints.reflection().registerType(String.class, MemberCategory.PUBLIC_CLASSES); - assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCLASSES, this.stringGetClasses); + void classForNameShouldNotMatchWhenMissingReflectionOnType() { + RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_FORNAME) + .withArgument("java.lang.String").returnValue(String.class).build(); + assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_FORNAME, invocation); } @Test - void classGetClassesShouldMatchDeclaredClasses() { - hints.reflection().registerType(String.class, MemberCategory.DECLARED_CLASSES); + void classGetClassesShouldMatchReflectionOnType() { + hints.reflection().registerType(String.class); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCLASSES, this.stringGetClasses); } @Test - void classGetDeclaredClassesShouldMatchDeclaredClassesHint() { - hints.reflection().registerType(String.class, MemberCategory.DECLARED_CLASSES); + void classGetDeclaredClassesShouldMatchReflectionOnType() { + hints.reflection().registerType(String.class); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCLASSES, this.stringGetDeclaredClasses); } @Test - void classGetDeclaredClassesShouldNotMatchPublicClassesHint() { - hints.reflection().registerType(String.class, MemberCategory.PUBLIC_CLASSES); + void classGetDeclaredClassesShouldNotMatchWhenMissingReflectionOnType() { assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDCLASSES, this.stringGetDeclaredClasses); } @@ -124,20 +118,19 @@ public void setup() throws Exception { } @Test - void classGetConstructorShouldMatchIntrospectPublicConstructorsHint() { - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS); + void classGetConstructorShouldMatchTypeHint() { + hints.reflection().registerType(String.class); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor); } @Test - void classGetConstructorShouldMatchInvokePublicConstructorsHint() { - hints.reflection().registerType(String.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); - assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor); + void classGetConstructorShouldNotMatchWhenMissingTypeHint() { + assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor); } @Test - void classGetConstructorShouldMatchIntrospectDeclaredConstructorsHint() { - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS); + void classGetConstructorShouldMatchInvokePublicConstructorsHint() { + hints.reflection().registerType(String.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor); } @@ -147,13 +140,6 @@ void classGetConstructorShouldMatchInvokeDeclaredConstructorsHint() { assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor); } - @Test - void classGetConstructorShouldMatchIntrospectConstructorHint() { - hints.reflection().registerType(String.class,typeHint -> - typeHint.withConstructor(Collections.emptyList(), ExecutableMode.INTROSPECT)); - assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor); - } - @Test void classGetConstructorShouldMatchInvokeConstructorHint() { hints.reflection().registerType(String.class, typeHint -> @@ -162,20 +148,19 @@ void classGetConstructorShouldMatchInvokeConstructorHint() { } @Test - void classGetConstructorsShouldMatchIntrospectPublicConstructorsHint() { - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS); + void classGetConstructorsShouldMatchTypeHint() { + hints.reflection().registerType(String.class); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTORS, this.stringGetConstructors); } @Test - void classGetConstructorsShouldMatchInvokePublicConstructorsHint() { - hints.reflection().registerType(String.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); - assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTORS, this.stringGetConstructors); + void classGetConstructorsShouldNotMatchWhemMissingTypeHint() { + assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETCONSTRUCTORS, this.stringGetConstructors); } @Test - void classGetConstructorsShouldMatchIntrospectDeclaredConstructorsHint() { - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS); + void classGetConstructorsShouldMatchInvokePublicConstructorsHint() { + hints.reflection().registerType(String.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTORS, this.stringGetConstructors); } @@ -186,36 +171,16 @@ void classGetConstructorsShouldMatchInvokeDeclaredConstructorsHint() { } @Test - void classGetConstructorsShouldNotMatchTypeReflectionHint() { + void classGetDeclaredConstructorShouldMatchTypeHint() { hints.reflection().registerType(String.class); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETCONSTRUCTORS, this.stringGetConstructors); - } - - @Test - void classGetConstructorsShouldNotMatchConstructorReflectionHint() throws Exception { - hints.reflection().registerConstructor(String.class.getConstructor(), ExecutableMode.INVOKE); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETCONSTRUCTORS, this.stringGetConstructors); - } - - @Test - void classGetDeclaredConstructorShouldMatchIntrospectDeclaredConstructorsHint() { - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTOR, this.stringGetDeclaredConstructor); } @Test - void classGetDeclaredConstructorShouldNotMatchIntrospectPublicConstructorsHint() { - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS); + void classGetDeclaredConstructorShouldNotMatchWhenMissingTypeHint() { assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTOR, this.stringGetDeclaredConstructor); } - @Test - void classGetDeclaredConstructorShouldMatchIntrospectConstructorHint() { - hints.reflection().registerType(String.class, typeHint -> - typeHint.withConstructor(TypeReference.listOf(byte[].class, byte.class), ExecutableMode.INTROSPECT)); - assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTOR, this.stringGetDeclaredConstructor); - } - @Test void classGetDeclaredConstructorShouldMatchInvokeConstructorHint() { hints.reflection().registerType(String.class, typeHint -> @@ -223,12 +188,6 @@ void classGetDeclaredConstructorShouldMatchInvokeConstructorHint() { assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTOR, this.stringGetDeclaredConstructor); } - @Test - void classGetDeclaredConstructorsShouldMatchIntrospectDeclaredConstructorsHint() { - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS); - assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTORS, this.stringGetDeclaredConstructors); - } - @Test void classGetDeclaredConstructorsShouldMatchInvokeDeclaredConstructorsHint() { hints.reflection().registerType(String.class, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); @@ -236,20 +195,13 @@ void classGetDeclaredConstructorsShouldMatchInvokeDeclaredConstructorsHint() { } @Test - void classGetDeclaredConstructorsShouldNotMatchIntrospectPublicConstructorsHint() { - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTORS, this.stringGetDeclaredConstructors); - } - - @Test - void classGetDeclaredConstructorsShouldNotMatchTypeReflectionHint() { + void classGetDeclaredConstructorsShouldMatchTypeReflectionHint() { hints.reflection().registerType(String.class); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTORS, this.stringGetDeclaredConstructors); + assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTORS, this.stringGetDeclaredConstructors); } @Test - void classGetDeclaredConstructorsShouldNotMatchConstructorReflectionHint() throws Exception { - hints.reflection().registerConstructor(String.class.getConstructor(), ExecutableMode.INVOKE); + void classGetDeclaredConstructorsShouldNotMatchWhenMissingTypeReflectionHint() { assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTORS, this.stringGetDeclaredConstructors); } @@ -262,15 +214,6 @@ void constructorNewInstanceShouldMatchInvokeHintOnConstructor() throws NoSuchMet assertThatInvocationMatches(InstrumentedMethod.CONSTRUCTOR_NEWINSTANCE, invocation); } - @Test - void constructorNewInstanceShouldNotMatchIntrospectHintOnConstructor() throws NoSuchMethodException { - RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CONSTRUCTOR_NEWINSTANCE) - .onInstance(String.class.getConstructor()).returnValue("").build(); - hints.reflection().registerType(String.class, typeHint -> - typeHint.withConstructor(Collections.emptyList(), ExecutableMode.INTROSPECT)); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CONSTRUCTOR_NEWINSTANCE, invocation); - } - } @Nested @@ -295,22 +238,8 @@ void setup() throws Exception { } @Test - void classGetDeclaredMethodShouldMatchIntrospectDeclaredMethodsHint() { - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_DECLARED_METHODS); - assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDMETHOD, this.stringGetScaleMethod); - } - - @Test - void classGetDeclaredMethodShouldNotMatchIntrospectPublicMethodsHint() { - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDMETHOD, this.stringGetScaleMethod); - } - - @Test - void classGetDeclaredMethodShouldMatchIntrospectMethodHint() { - List parameterTypes = TypeReference.listOf(int.class, float.class); - hints.reflection().registerType(String.class, typeHint -> - typeHint.withMethod("scale", parameterTypes, ExecutableMode.INTROSPECT)); + void classGetDeclaredMethodShouldMatchTypeReflectionHint() { + hints.reflection().registerType(String.class); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDMETHOD, this.stringGetScaleMethod); } @@ -322,12 +251,6 @@ void classGetDeclaredMethodShouldMatchInvokeMethodHint() { assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDMETHOD, this.stringGetScaleMethod); } - @Test - void classGetDeclaredMethodsShouldMatchIntrospectDeclaredMethodsHint() { - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_DECLARED_METHODS); - assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDMETHODS, this.stringGetScaleMethod); - } - @Test void classGetDeclaredMethodsShouldMatchInvokeDeclaredMethodsHint() { RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETDECLAREDMETHODS).onInstance(String.class).build(); @@ -336,32 +259,8 @@ void classGetDeclaredMethodsShouldMatchInvokeDeclaredMethodsHint() { } @Test - void classGetDeclaredMethodsShouldNotMatchIntrospectPublicMethodsHint() { - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDMETHODS, this.stringGetScaleMethod); - } - - @Test - void classGetDeclaredMethodsShouldNotMatchTypeReflectionHint() { + void classGetMethodsShouldMatchTypeReflectionHint() { hints.reflection().registerType(String.class); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDMETHODS, this.stringGetScaleMethod); - } - - @Test - void classGetDeclaredMethodsShouldNotMatchMethodReflectionHint() throws Exception { - hints.reflection().registerMethod(String.class.getMethod("toString"), ExecutableMode.INVOKE); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDMETHODS, this.stringGetScaleMethod); - } - - @Test - void classGetMethodsShouldMatchIntrospectDeclaredMethodsHint() { - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_DECLARED_METHODS); - assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods); - } - - @Test - void classGetMethodsShouldMatchIntrospectPublicMethodsHint() { - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods); } @@ -379,25 +278,13 @@ void classGetMethodsShouldMatchInvokePublicMethodsHint() { @Test void classGetMethodsShouldNotMatchForWrongType() { - hints.reflection().registerType(Integer.class, MemberCategory.INTROSPECT_PUBLIC_METHODS); + hints.reflection().registerType(Integer.class); assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods); } @Test - void classGetMethodsShouldNotMatchTypeReflectionHint() { + void classGetMethodShouldMatchReflectionTypeHint() { hints.reflection().registerType(String.class); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods); - } - - @Test - void classGetMethodsShouldNotMatchMethodReflectionHint() throws Exception { - hints.reflection().registerMethod(String.class.getMethod("toString"), ExecutableMode.INVOKE); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETMETHODS, this.stringGetMethods); - } - - @Test - void classGetMethodShouldMatchIntrospectPublicMethodsHint() { - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod); } @@ -407,25 +294,6 @@ void classGetMethodShouldMatchInvokePublicMethodsHint() { assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod); } - @Test - void classGetMethodShouldNotMatchIntrospectDeclaredMethodsHint() { - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_DECLARED_METHODS); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod); - } - - @Test - void classGetMethodShouldNotMatchInvokeDeclaredMethodsHint() { - hints.reflection().registerType(String.class, MemberCategory.INVOKE_DECLARED_METHODS); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod); - } - - @Test - void classGetMethodShouldMatchIntrospectMethodHint() { - hints.reflection().registerType(String.class, typeHint -> - typeHint.withMethod("toString", Collections.emptyList(), ExecutableMode.INTROSPECT)); - assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod); - } - @Test void classGetMethodShouldMatchInvokeMethodHint() { hints.reflection().registerType(String.class, typeHint -> @@ -433,21 +301,9 @@ void classGetMethodShouldMatchInvokeMethodHint() { assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod); } - @Test - void classGetMethodShouldNotMatchIntrospectPublicMethodsHintWhenPrivate() { - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetScaleMethod); - } - - @Test - void classGetMethodShouldMatchIntrospectDeclaredMethodsHintWhenPrivate() { - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_DECLARED_METHODS); - assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetScaleMethod); - } - @Test void classGetMethodShouldNotMatchForWrongType() { - hints.reflection().registerType(Integer.class, MemberCategory.INTROSPECT_PUBLIC_METHODS); + hints.reflection().registerType(Integer.class); assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod); } @@ -461,11 +317,10 @@ void methodInvokeShouldMatchInvokeHintOnMethod() throws NoSuchMethodException { } @Test - void methodInvokeShouldNotMatchIntrospectHintOnMethod() throws NoSuchMethodException { + void methodInvokeShouldNotMatchReflectionTypeHint() throws NoSuchMethodException { RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.METHOD_INVOKE) .onInstance(String.class.getMethod("toString")).withArguments("", new Object[0]).build(); - hints.reflection().registerType(String.class, typeHint -> - typeHint.withMethod("toString", Collections.emptyList(), ExecutableMode.INTROSPECT)); + hints.reflection().registerType(String.class); assertThatInvocationDoesNotMatch(InstrumentedMethod.METHOD_INVOKE, invocation); } @@ -496,17 +351,11 @@ void setup() throws Exception { } @Test - void classGetDeclaredFieldShouldMatchDeclaredFieldsHint() { - hints.reflection().registerType(String.class, MemberCategory.DECLARED_FIELDS); + void classGetDeclaredFieldShouldMatchTypeReflectionHint() { + hints.reflection().registerType(String.class); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDFIELD, this.stringGetDeclaredField); } - @Test - void classGetDeclaredFieldShouldNotMatchPublicFieldsHint() { - hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.PUBLIC_FIELDS)); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDFIELD, this.stringGetDeclaredField); - } - @Test void classGetDeclaredFieldShouldMatchFieldHint() { hints.reflection().registerType(String.class, typeHint -> typeHint.withField("value")); @@ -514,41 +363,23 @@ void classGetDeclaredFieldShouldMatchFieldHint() { } @Test - void classGetDeclaredFieldsShouldMatchDeclaredFieldsHint() { - hints.reflection().registerType(String.class, MemberCategory.DECLARED_FIELDS); - assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDFIELDS, this.stringGetDeclaredFields); - } - - @Test - void classGetDeclaredFieldsShouldNotMatchPublicFieldsHint() { - hints.reflection().registerType(String.class, MemberCategory.PUBLIC_FIELDS); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDFIELDS, this.stringGetDeclaredFields); - } - - @Test - void classGetDeclaredFieldsShouldNotMatchTypeHint() { + void classGetDeclaredFieldsShouldMatchTypeReflectionHint() { hints.reflection().registerType(String.class); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDFIELDS, this.stringGetDeclaredFields); + assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDFIELDS, this.stringGetDeclaredFields); } @Test - void classGetDeclaredFieldsShouldNotMatchFieldHint() throws Exception { + void classGetDeclaredFieldsShouldMatchFieldHint() throws Exception { hints.reflection().registerField(String.class.getDeclaredField("value")); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDFIELDS, this.stringGetDeclaredFields); + assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDFIELDS, this.stringGetDeclaredFields); } @Test - void classGetFieldShouldMatchPublicFieldsHint() { - hints.reflection().registerType(PublicField.class, MemberCategory.PUBLIC_FIELDS); + void classGetFieldShouldMatchTypeReflectionHint() { + hints.reflection().registerType(PublicField.class); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETFIELD, this.getPublicField); } - @Test - void classGetFieldShouldNotMatchDeclaredFieldsHint() { - hints.reflection().registerType(PublicField.class, MemberCategory.DECLARED_FIELDS); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETFIELD, this.getPublicField); - } - @Test void classGetFieldShouldMatchFieldHint() { hints.reflection().registerType(PublicField.class, typeHint -> typeHint.withField("field")); @@ -559,53 +390,30 @@ void classGetFieldShouldMatchFieldHint() { void classGetFieldShouldNotMatchPublicFieldsHintWhenPrivate() { RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETFIELD) .onInstance(String.class).withArgument("value").returnValue(null).build(); - hints.reflection().registerType(String.class, MemberCategory.PUBLIC_FIELDS); + hints.reflection().registerType(String.class); assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETFIELD, invocation); } - @Test - void classGetFieldShouldMatchDeclaredFieldsHintWhenPrivate() throws NoSuchFieldException { - RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETFIELD) - .onInstance(String.class).withArgument("value").returnValue(String.class.getDeclaredField("value")).build(); - hints.reflection().registerType(String.class, MemberCategory.DECLARED_FIELDS); - assertThatInvocationMatches(InstrumentedMethod.CLASS_GETFIELD, invocation); - } - @Test void classGetFieldShouldNotMatchForWrongType() { RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETFIELD) .onInstance(String.class).withArgument("value").returnValue(null).build(); - hints.reflection().registerType(Integer.class, MemberCategory.DECLARED_FIELDS); + hints.reflection().registerType(Integer.class); assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETFIELD, invocation); } @Test - void classGetFieldsShouldMatchPublicFieldsHint() { - RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETFIELDS) - .onInstance(PublicField.class).build(); - hints.reflection().registerType(PublicField.class, MemberCategory.PUBLIC_FIELDS); - assertThatInvocationMatches(InstrumentedMethod.CLASS_GETFIELDS, invocation); - } - - @Test - void classGetFieldsShouldMatchDeclaredFieldsHint() { + void classGetFieldsShouldMatchReflectionHint() { RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETFIELDS) .onInstance(PublicField.class).build(); - hints.reflection().registerType(PublicField.class, MemberCategory.DECLARED_FIELDS); + hints.reflection().registerType(PublicField.class); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETFIELDS, invocation); } - @Test - void classGetFieldsShouldNotMatchTypeHint() { + void classGetFieldsShouldMatchTypeHint() { hints.reflection().registerType(String.class); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETFIELDS, this.stringGetFields); - } - - @Test - void classGetFieldsShouldNotMatchFieldHint() throws Exception { - hints.reflection().registerField(String.class.getDeclaredField("value")); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETFIELDS, this.stringGetFields); + assertThatInvocationMatches(InstrumentedMethod.CLASS_GETFIELDS, this.stringGetFields); } } @@ -634,7 +442,7 @@ void resourceBundleGetBundleShouldNotMatchBundleNameHintWhenWrongName() { void classGetResourceShouldMatchResourcePatternWhenAbsolute() { RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE) .onInstance(InstrumentedMethodTests.class).withArgument("/some/path/resource.txt").build(); - hints.resources().registerPattern("some/*"); + hints.resources().registerPattern("some/**"); assertThatInvocationMatches(InstrumentedMethod.CLASS_GETRESOURCE, invocation); } @@ -655,11 +463,11 @@ void classGetResourceShouldNotMatchResourcePatternWhenInvalid() { } @Test - void classGetResourceShouldNotMatchResourcePatternWhenExcluded() { + void classGetResourceShouldMatchWhenGlobPattern() { RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE) .onInstance(InstrumentedMethodTests.class).withArgument("/some/path/resource.txt").build(); - hints.resources().registerPattern(resourceHint -> resourceHint.includes("some/*").excludes("some/path/*")); - assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETRESOURCE, invocation); + hints.resources().registerPattern(resourceHint -> resourceHint.includes("some/**")); + assertThatInvocationMatches(InstrumentedMethod.CLASS_GETRESOURCE, invocation); } } From ba312f6c7cf5a069323c2c732d0be7aa19910067 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 29 Nov 2024 14:44:05 +0100 Subject: [PATCH 024/701] Update AOT support after RuntimeHints changes This commit adapts AOT support in various modules after the RuntimeHints and related deprecation changes. `MemberCategory.INTROSPECT_*` hints are now removed and `MemberCategory.*_FIELDS` are replaced with `MemberCategory.INVOKE*_FIELDS` when invocation is needed. Usage of `RuntimeHintsAgent` are also deprecated. Closes gh-33847 --- .../SampleReflectionRuntimeHintsTests.java | 6 ++-- .../aot/test/ReflectionInvocationsTests.java | 15 +++++----- ...tJAdvisorBeanRegistrationAotProcessor.java | 2 +- ...isorBeanRegistrationAotProcessorTests.java | 2 +- .../AutowiredAnnotationBeanPostProcessor.java | 2 +- ...BeanDefinitionPropertiesCodeGenerator.java | 4 +-- .../aot/BeanRegistrationsAotContribution.java | 6 ++-- .../aot/InstanceSupplierCodeGenerator.java | 5 ++-- ...efinitionPropertiesCodeGeneratorTests.java | 2 +- ...BeanRegistrationsAotContributionTests.java | 10 ++----- .../InstanceSupplierCodeGeneratorTests.java | 30 +++++++------------ ...nstanceSupplierCodeGeneratorKotlinTests.kt | 6 ++-- .../CommonAnnotationBeanPostProcessor.java | 2 +- .../ConfigurationClassPostProcessor.java | 3 +- ...eflectionBeanRegistrationAotProcessor.java | 3 +- ...alidationBeanRegistrationAotProcessor.java | 2 +- ...notationConfigApplicationContextTests.java | 2 +- .../context/aot/ContextAotProcessorTests.java | 4 +-- ...nContextAotGeneratorRuntimeHintsTests.java | 4 +-- ...tionBeanRegistrationAotProcessorTests.java | 14 ++++----- ...ectionBeanRegistrationAotProcessorTests.kt | 14 +++------ .../hint/BindingReflectionHintsRegistrar.java | 4 +-- .../aot/hint/support/ClassHintUtils.java | 2 +- .../BindingReflectionHintsRegistrarTests.java | 8 ++--- ...dingReflectionHintsRegistrarKotlinTests.kt | 3 +- ...essageMappingReflectiveProcessorTests.java | 4 +-- ...ypesBeanRegistrationAotProcessorTests.java | 10 +++---- ...ergedContextConfigurationRuntimeHints.java | 4 +-- .../context/aot/TestAotProcessorTests.java | 3 +- .../annotation/TransactionRuntimeHints.java | 2 +- ...rollerMappingReflectiveProcessorTests.java | 14 ++++----- 31 files changed, 81 insertions(+), 111 deletions(-) diff --git a/framework-docs/src/main/java/org/springframework/docs/core/aot/hints/testing/SampleReflectionRuntimeHintsTests.java b/framework-docs/src/main/java/org/springframework/docs/core/aot/hints/testing/SampleReflectionRuntimeHintsTests.java index 5d666cc9283b..44bb21e30cd9 100644 --- a/framework-docs/src/main/java/org/springframework/docs/core/aot/hints/testing/SampleReflectionRuntimeHintsTests.java +++ b/framework-docs/src/main/java/org/springframework/docs/core/aot/hints/testing/SampleReflectionRuntimeHintsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -24,7 +24,6 @@ import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.test.agent.EnabledIfRuntimeHintsAgent; import org.springframework.aot.test.agent.RuntimeHintsInvocations; -import org.springframework.aot.test.agent.RuntimeHintsRecorder; import org.springframework.core.SpringVersion; import static org.assertj.core.api.Assertions.assertThat; @@ -33,6 +32,7 @@ // method is only enabled if the RuntimeHintsAgent is loaded on the current JVM. // It also tags tests with the "RuntimeHints" JUnit tag. @EnabledIfRuntimeHintsAgent +@SuppressWarnings("removal") class SampleReflectionRuntimeHintsTests { @Test @@ -43,7 +43,7 @@ void shouldRegisterReflectionHints() { typeHint.withMethod("getVersion", List.of(), ExecutableMode.INVOKE)); // Invoke the relevant piece of code we want to test within a recording lambda - RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> { + RuntimeHintsInvocations invocations = org.springframework.aot.test.agent.RuntimeHintsRecorder.record(() -> { SampleReflection sample = new SampleReflection(); sample.performReflection(); }); diff --git a/integration-tests/src/test/java/org/springframework/aot/test/ReflectionInvocationsTests.java b/integration-tests/src/test/java/org/springframework/aot/test/ReflectionInvocationsTests.java index 541025c19c17..1a1123c22670 100644 --- a/integration-tests/src/test/java/org/springframework/aot/test/ReflectionInvocationsTests.java +++ b/integration-tests/src/test/java/org/springframework/aot/test/ReflectionInvocationsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -18,24 +18,23 @@ import org.junit.jupiter.api.Test; -import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.test.agent.EnabledIfRuntimeHintsAgent; import org.springframework.aot.test.agent.RuntimeHintsInvocations; -import org.springframework.aot.test.agent.RuntimeHintsRecorder; import static org.assertj.core.api.Assertions.assertThat; @EnabledIfRuntimeHintsAgent +@SuppressWarnings("removal") class ReflectionInvocationsTests { @Test void sampleTest() { RuntimeHints hints = new RuntimeHints(); - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS); + hints.reflection().registerType(String.class); - RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> { + RuntimeHintsInvocations invocations = org.springframework.aot.test.agent.RuntimeHintsRecorder.record(() -> { SampleReflection sample = new SampleReflection(); sample.sample(); // does Method[] methods = String.class.getMethods(); }); @@ -45,9 +44,9 @@ void sampleTest() { @Test void multipleCallsTest() { RuntimeHints hints = new RuntimeHints(); - hints.reflection().registerType(String.class, MemberCategory.INTROSPECT_PUBLIC_METHODS); - hints.reflection().registerType(Integer.class,MemberCategory.INTROSPECT_PUBLIC_METHODS); - RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> { + hints.reflection().registerType(String.class); + hints.reflection().registerType(Integer.class); + RuntimeHintsInvocations invocations = org.springframework.aot.test.agent.RuntimeHintsRecorder.record(() -> { SampleReflection sample = new SampleReflection(); sample.multipleCalls(); // does Method[] methods = String.class.getMethods(); methods = Integer.class.getMethods(); }); diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJAdvisorBeanRegistrationAotProcessor.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJAdvisorBeanRegistrationAotProcessor.java index 7149816f5742..cfc88564776a 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJAdvisorBeanRegistrationAotProcessor.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/AspectJAdvisorBeanRegistrationAotProcessor.java @@ -74,7 +74,7 @@ public AspectJAdvisorContribution(Class beanClass) { @Override public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) { - generationContext.getRuntimeHints().reflection().registerType(this.beanClass, MemberCategory.DECLARED_FIELDS); + generationContext.getRuntimeHints().reflection().registerType(this.beanClass, MemberCategory.INVOKE_DECLARED_FIELDS); } } diff --git a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectJAdvisorBeanRegistrationAotProcessorTests.java b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectJAdvisorBeanRegistrationAotProcessorTests.java index 124b1612acd5..e6b8fe34cccb 100644 --- a/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectJAdvisorBeanRegistrationAotProcessorTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/aspectj/annotation/AspectJAdvisorBeanRegistrationAotProcessorTests.java @@ -48,7 +48,7 @@ class AspectJAdvisorBeanRegistrationAotProcessorTests { @Test void shouldProcessAspectJClass() { process(AspectJClass.class); - assertThat(reflection().onType(AspectJClass.class).withMemberCategory(MemberCategory.DECLARED_FIELDS)) + assertThat(reflection().onType(AspectJClass.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_FIELDS)) .accepts(this.runtimeHints); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index f711222c0f11..d0ba827163e3 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -1065,7 +1065,7 @@ private CodeBlock generateMethodStatementForMethod(CodeWarnings codeWarnings, } else { codeWarnings.detectDeprecation(method); - hints.reflection().registerMethod(method, ExecutableMode.INTROSPECT); + hints.reflection().registerType(method.getDeclaringClass()); CodeBlock arguments = new AutowiredArgumentsCodeGenerator(this.target, method).generateCode(method.getParameterTypes()); CodeBlock injectionCode = CodeBlock.of("args -> $L.$L($L)", diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java index 3fb42523b144..29240a2c576d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java @@ -252,10 +252,10 @@ private void registerReflectionHints(RootBeanDefinition beanDefinition, Method w // ReflectionUtils#findField searches recursively in the type hierarchy Class searchType = beanDefinition.getTargetType(); while (searchType != null && searchType != writeMethod.getDeclaringClass()) { - this.hints.reflection().registerType(searchType, MemberCategory.DECLARED_FIELDS); + this.hints.reflection().registerType(searchType, MemberCategory.INVOKE_DECLARED_FIELDS); searchType = searchType.getSuperclass(); } - this.hints.reflection().registerType(writeMethod.getDeclaringClass(), MemberCategory.DECLARED_FIELDS); + this.hints.reflection().registerType(writeMethod.getDeclaringClass(), MemberCategory.INVOKE_DECLARED_FIELDS); } private void addQualifiers(CodeBlock.Builder code, RootBeanDefinition beanDefinition) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java index ddb3ba60cdf6..67ac2db28744 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java @@ -26,9 +26,9 @@ import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.generate.MethodReference; import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator; -import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.ReflectionHints; import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeHint; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.javapoet.ClassName; @@ -100,8 +100,8 @@ private void generateRegisterHints(RuntimeHints runtimeHints, List registrations.forEach(registration -> { ReflectionHints hints = runtimeHints.reflection(); Class beanClass = registration.registeredBean.getBeanClass(); - hints.registerType(beanClass, MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS); - hints.registerForInterfaces(beanClass, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS)); + hints.registerType(beanClass); + hints.registerForInterfaces(beanClass, TypeHint.Builder::withMembers); }); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java index e9a4f40bca66..14fcb39ddadc 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java @@ -172,8 +172,7 @@ private CodeBlock generateCodeForConstructor(RegisteredBean registeredBean, Cons } private CodeBlock generateCodeForAccessibleConstructor(String beanName, Constructor constructor) { - this.generationContext.getRuntimeHints().reflection().registerConstructor( - constructor, ExecutableMode.INTROSPECT); + this.generationContext.getRuntimeHints().reflection().registerType(constructor.getDeclaringClass()); if (constructor.getParameterCount() == 0) { if (!this.allowDirectSupplierShortcut) { @@ -265,7 +264,7 @@ private CodeBlock generateCodeForFactoryMethod( private CodeBlock generateCodeForAccessibleFactoryMethod(String beanName, Method factoryMethod, Class targetClass, @Nullable String factoryBeanName) { - this.generationContext.getRuntimeHints().reflection().registerMethod(factoryMethod, ExecutableMode.INTROSPECT); + this.generationContext.getRuntimeHints().reflection().registerType(factoryMethod.getDeclaringClass()); if (factoryBeanName == null && factoryMethod.getParameterCount() == 0) { Class suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType()); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java index 2fc485a5abd7..0613281ed822 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java @@ -574,7 +574,7 @@ private void assertHasMethodInvokeHints(Class beanType, String... methodNames private void assertHasDeclaredFieldsHint(Class beanType) { assertThat(RuntimeHintsPredicates.reflection() - .onType(beanType).withMemberCategory(MemberCategory.DECLARED_FIELDS)) + .onType(beanType).withMemberCategory(MemberCategory.INVOKE_DECLARED_FIELDS)) .accepts(this.generationContext.getRuntimeHints()); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java index 2cccc827f476..494a809cc629 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java @@ -31,7 +31,6 @@ import org.springframework.aot.generate.MethodReference; import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator; import org.springframework.aot.generate.ValueCodeGenerationException; -import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.test.generate.TestGenerationContext; import org.springframework.beans.factory.aot.BeanRegistrationsAotContribution.Registration; import org.springframework.beans.factory.support.DefaultListableBeanFactory; @@ -149,14 +148,11 @@ void applyToRegisterReflectionHints() { registeredBean, null, List.of()); BeanRegistrationsAotContribution contribution = createContribution(registeredBean, generator); contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode); - assertThat(reflection().onType(Employee.class) - .withMemberCategories(MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS)) + assertThat(reflection().onType(Employee.class)) .accepts(this.generationContext.getRuntimeHints()); - assertThat(reflection().onType(ITestBean.class) - .withMemberCategory(MemberCategory.INTROSPECT_PUBLIC_METHODS)) + assertThat(reflection().onType(ITestBean.class)) .accepts(this.generationContext.getRuntimeHints()); - assertThat(reflection().onType(AgeHolder.class) - .withMemberCategory(MemberCategory.INTROSPECT_PUBLIC_METHODS)) + assertThat(reflection().onType(AgeHolder.class)) .accepts(this.generationContext.getRuntimeHints()); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java index f18a55c02c0b..0c4baa84a017 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorTests.java @@ -100,8 +100,7 @@ void generateWhenHasDefaultConstructor() { assertThat(compiled.getSourceFile()) .contains("InstanceSupplier.using(TestBean::new)"); }); - assertThat(getReflectionHints().getTypeHint(TestBean.class)) - .satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT)); + assertThat(getReflectionHints().getTypeHint(TestBean.class)).isNotNull(); } @Test @@ -112,8 +111,7 @@ void generateWhenHasConstructorWithParameter() { InjectionComponent bean = getBean(beanDefinition, instanceSupplier); assertThat(bean).isInstanceOf(InjectionComponent.class).extracting("bean").isEqualTo("injected"); }); - assertThat(getReflectionHints().getTypeHint(InjectionComponent.class)) - .satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT)); + assertThat(getReflectionHints().getTypeHint(InjectionComponent.class)).isNotNull(); } @Test @@ -126,8 +124,7 @@ void generateWhenHasConstructorWithInnerClassAndDefaultConstructor() { assertThat(compiled.getSourceFile()).contains( "getBeanFactory().getBean(InnerComponentConfiguration.class).new NoDependencyComponent()"); }); - assertThat(getReflectionHints().getTypeHint(NoDependencyComponent.class)) - .satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT)); + assertThat(getReflectionHints().getTypeHint(NoDependencyComponent.class)).isNotNull(); } @Test @@ -141,8 +138,7 @@ void generateWhenHasConstructorWithInnerClassAndParameter() { assertThat(compiled.getSourceFile()).contains( "getBeanFactory().getBean(InnerComponentConfiguration.class).new EnvironmentAwareComponent("); }); - assertThat(getReflectionHints().getTypeHint(EnvironmentAwareComponent.class)) - .satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT)); + assertThat(getReflectionHints().getTypeHint(EnvironmentAwareComponent.class)).isNotNull(); } @Test @@ -184,8 +180,7 @@ void generateWhenHasConstructorWithGeneric() { assertThat(bean).extracting("number").isNull(); // No property actually set assertThat(compiled.getSourceFile()).contains("NumberHolderFactoryBean::new"); }); - assertThat(getReflectionHints().getTypeHint(NumberHolderFactoryBean.class)) - .satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT)); + assertThat(getReflectionHints().getTypeHint(NumberHolderFactoryBean.class)).isNotNull(); } @Test @@ -215,8 +210,7 @@ void generateWhenHasFactoryMethodWithNoArg() { assertThat(compiled.getSourceFile()).contains( "getBeanFactory().getBean(\"config\", SimpleConfiguration.class).stringBean()"); }); - assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class)) - .satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT)); + assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class)).isNotNull(); } @Test @@ -232,8 +226,7 @@ void generateWhenHasFactoryMethodOnInterface() { assertThat(compiled.getSourceFile()).contains( "getBeanFactory().getBean(\"config\", DefaultSimpleBeanContract.class).simpleBean()"); }); - assertThat(getReflectionHints().getTypeHint(SimpleBeanContract.class)) - .satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT)); + assertThat(getReflectionHints().getTypeHint(SimpleBeanContract.class)).isNotNull(); } @Test @@ -268,8 +261,7 @@ void generateWhenHasStaticFactoryMethodWithNoArg() { assertThat(compiled.getSourceFile()) .contains("(registeredBean) -> SimpleConfiguration.integerBean()"); }); - assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class)) - .satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT)); + assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class)).isNotNull(); } @Test @@ -287,8 +279,7 @@ void generateWhenHasStaticFactoryMethodWithArg() { assertThat(bean).isEqualTo("42test"); assertThat(compiled.getSourceFile()).contains("SampleFactory.create("); }); - assertThat(getReflectionHints().getTypeHint(SampleFactory.class)) - .satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT)); + assertThat(getReflectionHints().getTypeHint(SampleFactory.class)).isNotNull(); } @Test @@ -305,8 +296,7 @@ void generateWhenHasFactoryMethodCheckedException() { assertThat(bean).isEqualTo(42); assertThat(compiled.getSourceFile()).doesNotContain(") throws Exception {"); }); - assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class)) - .satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT)); + assertThat(getReflectionHints().getTypeHint(SimpleConfiguration.class)).isNotNull(); } diff --git a/spring-beans/src/test/kotlin/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorKotlinTests.kt b/spring-beans/src/test/kotlin/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorKotlinTests.kt index b0d28ada8c2e..90b446c8a4bf 100644 --- a/spring-beans/src/test/kotlin/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorKotlinTests.kt +++ b/spring-beans/src/test/kotlin/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorKotlinTests.kt @@ -56,8 +56,7 @@ class InstanceSupplierCodeGeneratorKotlinTests { Assertions.assertThat(bean).isInstanceOf(KotlinTestBean::class.java) Assertions.assertThat(compiled.sourceFile).contains("InstanceSupplier.using(KotlinTestBean::new)") } - Assertions.assertThat(getReflectionHints().getTypeHint(KotlinTestBean::class.java)) - .satisfies(hasConstructorWithMode(ExecutableMode.INTROSPECT)) + Assertions.assertThat(getReflectionHints().getTypeHint(KotlinTestBean::class.java)).isNotNull } @Test @@ -90,8 +89,7 @@ class InstanceSupplierCodeGeneratorKotlinTests { "getBeanFactory().getBean(\"config\", KotlinConfiguration.class).stringBean()" ) } - Assertions.assertThat(getReflectionHints().getTypeHint(KotlinConfiguration::class.java)) - .satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT)) + Assertions.assertThat(getReflectionHints().getTypeHint(KotlinConfiguration::class.java)).isNotNull } @Test diff --git a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java index 2d684f9875b3..a5ffc7858f8d 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java @@ -960,7 +960,7 @@ private CodeBlock generateMethodStatementForMethod(ClassName targetClassName, return CodeBlock.of("$L.resolveAndSet($L, $L)", resolver, REGISTERED_BEAN_PARAMETER, INSTANCE_PARAMETER); } - hints.reflection().registerMethod(method, ExecutableMode.INTROSPECT); + hints.reflection().registerType(method.getDeclaringClass()); return CodeBlock.of("$L.$L($L.resolve($L))", INSTANCE_PARAMETER, method.getName(), resolver, REGISTERED_BEAN_PARAMETER); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 52823f67fa30..7988295f333d 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -40,7 +40,6 @@ import org.springframework.aop.framework.autoproxy.AutoProxyUtils; import org.springframework.aot.generate.GeneratedMethod; import org.springframework.aot.generate.GenerationContext; -import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.ResourceHints; import org.springframework.aot.hint.RuntimeHints; @@ -812,7 +811,7 @@ private InstantiationDescriptor proxyInstantiationDescriptor( Executable userExecutable = instantiationDescriptor.executable(); if (userExecutable instanceof Constructor userConstructor) { try { - runtimeHints.reflection().registerConstructor(userConstructor, ExecutableMode.INTROSPECT); + runtimeHints.reflection().registerType(userConstructor.getDeclaringClass()); Constructor constructor = this.proxyClass.getConstructor(userExecutable.getParameterTypes()); return new InstantiationDescriptor(constructor); } diff --git a/spring-context/src/main/java/org/springframework/context/aot/KotlinReflectionBeanRegistrationAotProcessor.java b/spring-context/src/main/java/org/springframework/context/aot/KotlinReflectionBeanRegistrationAotProcessor.java index e8804416eeeb..e8bcb7696385 100644 --- a/spring-context/src/main/java/org/springframework/context/aot/KotlinReflectionBeanRegistrationAotProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/aot/KotlinReflectionBeanRegistrationAotProcessor.java @@ -17,7 +17,6 @@ package org.springframework.context.aot; import org.springframework.aot.generate.GenerationContext; -import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor; @@ -61,7 +60,7 @@ public void applyTo(GenerationContext generationContext, BeanRegistrationCode be private void registerHints(Class type, RuntimeHints runtimeHints) { if (KotlinDetector.isKotlinType(type)) { - runtimeHints.reflection().registerType(type, MemberCategory.INTROSPECT_DECLARED_METHODS); + runtimeHints.reflection().registerType(type); } Class superClass = type.getSuperclass(); if (superClass != null) { diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessor.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessor.java index 57cddf3f5620..50d66dcf4981 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessor.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessor.java @@ -237,7 +237,7 @@ public AotContribution(Collection> validatedClasses, public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) { ReflectionHints hints = generationContext.getRuntimeHints().reflection(); for (Class validatedClass : this.validatedClasses) { - hints.registerType(validatedClass, MemberCategory.DECLARED_FIELDS); + hints.registerType(validatedClass, MemberCategory.INVOKE_DECLARED_FIELDS); } for (Class> constraintValidatorClass : this.constraintValidatorClasses) { hints.registerType(constraintValidatorClass, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java index 80f174db288f..c7ccf72758f2 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java @@ -534,7 +534,7 @@ void refreshForAotRegisterHintsForCglibProxy() { TypeReference cglibType = TypeReference.of(CglibConfiguration.class.getName() + "$$SpringCGLIB$$0"); assertThat(RuntimeHintsPredicates.reflection().onType(cglibType) .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS)) + MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_FIELDS)) .accepts(runtimeHints); assertThat(RuntimeHintsPredicates.reflection().onType(CglibConfiguration.class) .withMemberCategories(MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS)) diff --git a/spring-context/src/test/java/org/springframework/context/aot/ContextAotProcessorTests.java b/spring-context/src/test/java/org/springframework/context/aot/ContextAotProcessorTests.java index 384f54d59fcc..28901e94d5c7 100644 --- a/spring-context/src/test/java/org/springframework/context/aot/ContextAotProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/aot/ContextAotProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -102,7 +102,7 @@ private Consumer hasGeneratedAssetsForSampleApplication() { assertThat(directory.resolve( "source/org/springframework/context/aot/ContextAotProcessorTests_SampleApplication__BeanFactoryRegistrations.java")) .exists().isRegularFile(); - assertThat(directory.resolve("resource/META-INF/native-image/com.example/example/reflect-config.json")) + assertThat(directory.resolve("resource/META-INF/native-image/com.example/example/reachability-metadata.json")) .exists().isRegularFile(); Path nativeImagePropertiesFile = directory .resolve("resource/META-INF/native-image/com.example/example/native-image.properties"); diff --git a/spring-context/src/test/java/org/springframework/context/generator/ApplicationContextAotGeneratorRuntimeHintsTests.java b/spring-context/src/test/java/org/springframework/context/generator/ApplicationContextAotGeneratorRuntimeHintsTests.java index f15f3589ee7a..4ab27ca122dc 100644 --- a/spring-context/src/test/java/org/springframework/context/generator/ApplicationContextAotGeneratorRuntimeHintsTests.java +++ b/spring-context/src/test/java/org/springframework/context/generator/ApplicationContextAotGeneratorRuntimeHintsTests.java @@ -24,7 +24,6 @@ import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.test.agent.EnabledIfRuntimeHintsAgent; import org.springframework.aot.test.agent.RuntimeHintsInvocations; -import org.springframework.aot.test.agent.RuntimeHintsRecorder; import org.springframework.aot.test.generate.TestGenerationContext; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.RootBeanDefinition; @@ -46,6 +45,7 @@ * @author Stephane Nicoll */ @EnabledIfRuntimeHintsAgent +@SuppressWarnings("removal") class ApplicationContextAotGeneratorRuntimeHintsTests { @Test @@ -100,7 +100,7 @@ private void compile(GenericApplicationContext applicationContext, TestCompiler.forSystem().with(generationContext).compile(compiled -> { ApplicationContextInitializer instance = compiled.getInstance(ApplicationContextInitializer.class); GenericApplicationContext freshContext = new GenericApplicationContext(); - RuntimeHintsInvocations recordedInvocations = RuntimeHintsRecorder.record(() -> { + RuntimeHintsInvocations recordedInvocations = org.springframework.aot.test.agent.RuntimeHintsRecorder.record(() -> { instance.initialize(freshContext); freshContext.refresh(); freshContext.close(); diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessorTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessorTests.java index 27be1d5fbe2f..3147fd8555b6 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationBeanRegistrationAotProcessorTests.java @@ -79,7 +79,7 @@ void shouldProcessMethodParameterLevelConstraint() { process(MethodParameterLevelConstraint.class); assertThat(this.generationContext.getRuntimeHints().reflection().typeHints()).hasSize(2); assertThat(RuntimeHintsPredicates.reflection().onType(MethodParameterLevelConstraint.class) - .withMemberCategory(MemberCategory.DECLARED_FIELDS)).accepts(this.generationContext.getRuntimeHints()); + .withMemberCategory(MemberCategory.INVOKE_DECLARED_FIELDS)).accepts(this.generationContext.getRuntimeHints()); assertThat(RuntimeHintsPredicates.reflection().onType(ExistsValidator.class) .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.generationContext.getRuntimeHints()); } @@ -89,7 +89,7 @@ void shouldProcessConstructorParameterLevelConstraint() { process(ConstructorParameterLevelConstraint.class); assertThat(this.generationContext.getRuntimeHints().reflection().typeHints()).hasSize(2); assertThat(RuntimeHintsPredicates.reflection().onType(ConstructorParameterLevelConstraint.class) - .withMemberCategory(MemberCategory.DECLARED_FIELDS)).accepts(this.generationContext.getRuntimeHints()); + .withMemberCategory(MemberCategory.INVOKE_DECLARED_FIELDS)).accepts(this.generationContext.getRuntimeHints()); assertThat(RuntimeHintsPredicates.reflection().onType(ExistsValidator.class) .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.generationContext.getRuntimeHints()); } @@ -99,7 +99,7 @@ void shouldProcessPropertyLevelConstraint() { process(PropertyLevelConstraint.class); assertThat(this.generationContext.getRuntimeHints().reflection().typeHints()).hasSize(2); assertThat(RuntimeHintsPredicates.reflection().onType(PropertyLevelConstraint.class) - .withMemberCategory(MemberCategory.DECLARED_FIELDS)).accepts(this.generationContext.getRuntimeHints()); + .withMemberCategory(MemberCategory.INVOKE_DECLARED_FIELDS)).accepts(this.generationContext.getRuntimeHints()); assertThat(RuntimeHintsPredicates.reflection().onType(ExistsValidator.class) .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.generationContext.getRuntimeHints()); } @@ -109,7 +109,7 @@ void shouldProcessGenericTypeLevelConstraint() { process(GenericTypeLevelConstraint.class); assertThat(this.generationContext.getRuntimeHints().reflection().typeHints()).hasSize(2); assertThat(RuntimeHintsPredicates.reflection().onType(GenericTypeLevelConstraint.class) - .withMemberCategory(MemberCategory.DECLARED_FIELDS)).accepts(this.generationContext.getRuntimeHints()); + .withMemberCategory(MemberCategory.INVOKE_DECLARED_FIELDS)).accepts(this.generationContext.getRuntimeHints()); assertThat(RuntimeHintsPredicates.reflection().onType(PatternValidator.class) .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.generationContext.getRuntimeHints()); } @@ -119,9 +119,9 @@ void shouldProcessTransitiveGenericTypeLevelConstraint() { process(TransitiveGenericTypeLevelConstraint.class); assertThat(this.generationContext.getRuntimeHints().reflection().typeHints()).hasSize(3); assertThat(RuntimeHintsPredicates.reflection().onType(TransitiveGenericTypeLevelConstraint.class) - .withMemberCategory(MemberCategory.DECLARED_FIELDS)).accepts(this.generationContext.getRuntimeHints()); + .withMemberCategory(MemberCategory.INVOKE_DECLARED_FIELDS)).accepts(this.generationContext.getRuntimeHints()); assertThat(RuntimeHintsPredicates.reflection().onType(Exclude.class) - .withMemberCategory(MemberCategory.DECLARED_FIELDS)).accepts(this.generationContext.getRuntimeHints()); + .withMemberCategory(MemberCategory.INVOKE_DECLARED_FIELDS)).accepts(this.generationContext.getRuntimeHints()); assertThat(RuntimeHintsPredicates.reflection().onType(PatternValidator.class) .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.generationContext.getRuntimeHints()); } @@ -132,7 +132,7 @@ void shouldProcessRecursiveGenericsWithoutInfiniteRecursion(Class beanClass) process(beanClass); assertThat(this.generationContext.getRuntimeHints().reflection().typeHints()).hasSize(1); assertThat(RuntimeHintsPredicates.reflection().onType(beanClass) - .withMemberCategory(MemberCategory.DECLARED_FIELDS)).accepts(this.generationContext.getRuntimeHints()); + .withMemberCategory(MemberCategory.INVOKE_DECLARED_FIELDS)).accepts(this.generationContext.getRuntimeHints()); } @Test // gh-33940 diff --git a/spring-context/src/test/kotlin/org/springframework/context/aot/KotlinReflectionBeanRegistrationAotProcessorTests.kt b/spring-context/src/test/kotlin/org/springframework/context/aot/KotlinReflectionBeanRegistrationAotProcessorTests.kt index 4252607b5868..1895bbb75724 100644 --- a/spring-context/src/test/kotlin/org/springframework/context/aot/KotlinReflectionBeanRegistrationAotProcessorTests.kt +++ b/spring-context/src/test/kotlin/org/springframework/context/aot/KotlinReflectionBeanRegistrationAotProcessorTests.kt @@ -48,16 +48,11 @@ class KotlinReflectionBeanRegistrationAotProcessorTests { @Test fun shouldProcessKotlinBean() { process(SampleKotlinBean::class.java) + assertThat(RuntimeHintsPredicates.reflection().onType(SampleKotlinBean::class.java)) + .accepts(generationContext.runtimeHints) assertThat( - RuntimeHintsPredicates.reflection() - .onType(SampleKotlinBean::class.java) - .withMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS) - ).accepts(generationContext.runtimeHints) - assertThat( - RuntimeHintsPredicates.reflection() - .onType(BaseKotlinBean::class.java) - .withMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS) - ).accepts(generationContext.runtimeHints) + RuntimeHintsPredicates.reflection().onType(BaseKotlinBean::class.java)) + .accepts(generationContext.runtimeHints) } @Test @@ -72,7 +67,6 @@ class KotlinReflectionBeanRegistrationAotProcessorTests { assertThat( RuntimeHintsPredicates.reflection() .onType(OuterBean.NestedBean::class.java) - .withMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS) .and(RuntimeHintsPredicates.reflection().onType(OuterBean::class.java)) ).accepts(generationContext.runtimeHints) } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java b/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java index 280e02a290fd..00d258cc2314 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java @@ -100,7 +100,7 @@ private void registerReflectionHints(ReflectionHints hints, Set seen, Type typeHint.withMembers(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS); } - typeHint.withMembers(MemberCategory.DECLARED_FIELDS, + typeHint.withMembers(MemberCategory.INVOKE_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); for (Method method : clazz.getMethods()) { String methodName = method.getName(); @@ -120,8 +120,6 @@ else if ((methodName.startsWith("get") && method.getParameterCount() == 0 && met if (KotlinDetector.isKotlinType(clazz)) { KotlinDelegate.registerComponentHints(hints, clazz); registerKotlinSerializationHints(hints, clazz); - // For Kotlin reflection - typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_METHODS); } }); } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/support/ClassHintUtils.java b/spring-core/src/main/java/org/springframework/aot/hint/support/ClassHintUtils.java index ffe6c247c87e..f4d50f599942 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/support/ClassHintUtils.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/support/ClassHintUtils.java @@ -44,7 +44,7 @@ public abstract class ClassHintUtils { private static final Consumer asClassBasedProxy = hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS, - MemberCategory.DECLARED_FIELDS); + MemberCategory.INVOKE_DECLARED_FIELDS); private static final Consumer asProxiedUserClass = hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS, diff --git a/spring-core/src/test/java/org/springframework/aot/hint/BindingReflectionHintsRegistrarTests.java b/spring-core/src/test/java/org/springframework/aot/hint/BindingReflectionHintsRegistrarTests.java index 270b1e9b042b..24498581ecd2 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/BindingReflectionHintsRegistrarTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/BindingReflectionHintsRegistrarTests.java @@ -54,7 +54,7 @@ void registerTypeForSerializationWithEmptyClass() { .satisfies(typeHint -> { assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleEmptyClass.class)); assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder( - MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); + MemberCategory.INVOKE_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); assertThat(typeHint.constructors()).isEmpty(); assertThat(typeHint.fields()).isEmpty(); assertThat(typeHint.methods()).isEmpty(); @@ -68,7 +68,7 @@ void registerTypeForSerializationWithExtendingClass() { typeHint -> { assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleEmptyClass.class)); assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder( - MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); + MemberCategory.INVOKE_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); assertThat(typeHint.constructors()).isEmpty(); assertThat(typeHint.fields()).isEmpty(); assertThat(typeHint.methods()).isEmpty(); @@ -76,7 +76,7 @@ void registerTypeForSerializationWithExtendingClass() { typeHint -> { assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleExtendingClass.class)); assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder( - MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); + MemberCategory.INVOKE_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); assertThat(typeHint.constructors()).isEmpty(); assertThat(typeHint.fields()).isEmpty(); assertThat(typeHint.methods()).isEmpty(); @@ -198,7 +198,7 @@ void registerTypeForSerializationWithResolvableType() { typeHint -> { assertThat(typeHint.getType()).isEqualTo(TypeReference.of(ResolvableType.class)); assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder( - MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); + MemberCategory.INVOKE_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); assertThat(typeHint.constructors()).isEmpty(); assertThat(typeHint.fields()).isEmpty(); assertThat(typeHint.methods()).hasSizeGreaterThan(1); diff --git a/spring-core/src/test/kotlin/org/springframework/aot/hint/BindingReflectionHintsRegistrarKotlinTests.kt b/spring-core/src/test/kotlin/org/springframework/aot/hint/BindingReflectionHintsRegistrarKotlinTests.kt index 861f34325559..000df8e09b18 100644 --- a/spring-core/src/test/kotlin/org/springframework/aot/hint/BindingReflectionHintsRegistrarKotlinTests.kt +++ b/spring-core/src/test/kotlin/org/springframework/aot/hint/BindingReflectionHintsRegistrarKotlinTests.kt @@ -78,8 +78,7 @@ class BindingReflectionHintsRegistrarKotlinTests { @Test fun `Register reflection hints on declared methods for Kotlin class`() { bindingRegistrar.registerReflectionHints(hints.reflection(), SampleClass::class.java) - assertThat(RuntimeHintsPredicates.reflection().onType(SampleClass::class.java) - .withMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS)).accepts(hints) + assertThat(RuntimeHintsPredicates.reflection().onType(SampleClass::class.java)).accepts(hints) } } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessorTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessorTests.java index 32a3299bb766..694705a4b07c 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessorTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/MessageMappingReflectiveProcessorTests.java @@ -54,7 +54,7 @@ void registerReflectiveHintsForMethodWithReturnValue() throws NoSuchMethodExcept assertThat(typeHint.getType()).isEqualTo(TypeReference.of(OutgoingMessage.class)); assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder( MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.DECLARED_FIELDS); + MemberCategory.INVOKE_DECLARED_FIELDS); assertThat(typeHint.methods()).satisfiesExactlyInAnyOrder( hint -> assertThat(hint.getName()).isEqualTo("getMessage"), hint -> assertThat(hint.getName()).isEqualTo("setMessage")); @@ -72,7 +72,7 @@ void registerReflectiveHintsForMethodWithExplicitPayload() throws NoSuchMethodEx assertThat(typeHint.getType()).isEqualTo(TypeReference.of(IncomingMessage.class)); assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder( MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.DECLARED_FIELDS); + MemberCategory.INVOKE_DECLARED_FIELDS); assertThat(typeHint.methods()).satisfiesExactlyInAnyOrder( hint -> assertThat(hint.getName()).isEqualTo("getMessage"), hint -> assertThat(hint.getName()).isEqualTo("setMessage")); diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesBeanRegistrationAotProcessorTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesBeanRegistrationAotProcessorTests.java index 64f01029d07c..b4069e6ba909 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesBeanRegistrationAotProcessorTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesBeanRegistrationAotProcessorTests.java @@ -85,18 +85,18 @@ void contributeJpaHints() { context.registerBean(JpaDomainConfiguration.class); contributeHints(context, hints -> { assertThat(RuntimeHintsPredicates.reflection().onType(DriversLicense.class) - .withMemberCategories(MemberCategory.DECLARED_FIELDS)).accepts(hints); + .withMemberCategories(MemberCategory.INVOKE_DECLARED_FIELDS)).accepts(hints); assertThat(RuntimeHintsPredicates.reflection().onType(Person.class) - .withMemberCategories(MemberCategory.DECLARED_FIELDS)).accepts(hints); + .withMemberCategories(MemberCategory.INVOKE_DECLARED_FIELDS)).accepts(hints); assertThat(RuntimeHintsPredicates.reflection().onType(PersonListener.class) .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS)) .accepts(hints); assertThat(RuntimeHintsPredicates.reflection().onType(Employee.class) - .withMemberCategories(MemberCategory.DECLARED_FIELDS)).accepts(hints); + .withMemberCategories(MemberCategory.INVOKE_DECLARED_FIELDS)).accepts(hints); assertThat(RuntimeHintsPredicates.reflection().onMethod(Employee.class, "preRemove")) .accepts(hints); assertThat(RuntimeHintsPredicates.reflection().onType(EmployeeId.class) - .withMemberCategories(MemberCategory.DECLARED_FIELDS)).accepts(hints); + .withMemberCategories(MemberCategory.INVOKE_DECLARED_FIELDS)).accepts(hints); assertThat(RuntimeHintsPredicates.reflection().onType(EmployeeLocationConverter.class) .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(hints); assertThat(RuntimeHintsPredicates.reflection().onType(EmployeeCategoryConverter.class) @@ -104,7 +104,7 @@ void contributeJpaHints() { assertThat(RuntimeHintsPredicates.reflection().onType(EmployeeKindConverter.class) .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(hints); assertThat(RuntimeHintsPredicates.reflection().onType(EmployeeLocation.class) - .withMemberCategories(MemberCategory.DECLARED_FIELDS)).accepts(hints); + .withMemberCategories(MemberCategory.INVOKE_DECLARED_FIELDS)).accepts(hints); }); } diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/MergedContextConfigurationRuntimeHints.java b/spring-test/src/main/java/org/springframework/test/context/aot/MergedContextConfigurationRuntimeHints.java index 82803a5b4440..25455a3d638c 100644 --- a/spring-test/src/main/java/org/springframework/test/context/aot/MergedContextConfigurationRuntimeHints.java +++ b/spring-test/src/main/java/org/springframework/test/context/aot/MergedContextConfigurationRuntimeHints.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -144,7 +144,7 @@ private void registerClasspathResourceDirectoryStructure(String directory, Runti if (!pattern.endsWith(SLASH)) { pattern += SLASH; } - pattern += "*"; + pattern += "**"; runtimeHints.resources().registerPattern(pattern); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/TestAotProcessorTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/TestAotProcessorTests.java index daa669124fe2..4f971c777c38 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/TestAotProcessorTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/TestAotProcessorTests.java @@ -79,8 +79,7 @@ void process(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir) throws Exc assertThat(findFiles(sourceOutput)).containsExactlyInAnyOrderElementsOf(expectedSourceFiles()); assertThat(findFiles(resourceOutput.resolve("META-INF/native-image"))).contains( - Path.of(groupId, artifactId, "reflect-config.json"), - Path.of(groupId, artifactId, "resource-config.json")); + Path.of(groupId, artifactId, "reachability-metadata.json")); } private void copy(Class testClass, Path destination) { diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionRuntimeHints.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionRuntimeHints.java index 8343868b490d..94f968d9514c 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionRuntimeHints.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionRuntimeHints.java @@ -37,7 +37,7 @@ class TransactionRuntimeHints implements RuntimeHintsRegistrar { public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { hints.reflection().registerTypes( TypeReference.listOf(Isolation.class, Propagation.class), - TypeHint.builtWith(MemberCategory.DECLARED_FIELDS)); + TypeHint.builtWith(MemberCategory.INVOKE_DECLARED_FIELDS)); } } diff --git a/spring-web/src/test/java/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessorTests.java b/spring-web/src/test/java/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessorTests.java index 9f3b20a2344f..fef8975dfb38 100644 --- a/spring-web/src/test/java/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessorTests.java +++ b/spring-web/src/test/java/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessorTests.java @@ -48,7 +48,7 @@ void registerReflectiveHintsForMethodWithResponseBody() throws NoSuchMethodExcep assertThat(typeHint.getType()).isEqualTo(TypeReference.of(Response.class)); assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder( MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.DECLARED_FIELDS); + MemberCategory.INVOKE_DECLARED_FIELDS); assertThat(typeHint.methods()).satisfiesExactlyInAnyOrder( hint -> assertThat(hint.getName()).isEqualTo("getMessage"), hint -> assertThat(hint.getName()).isEqualTo("setMessage")); @@ -66,7 +66,7 @@ void registerReflectiveHintsForMethodWithRequestBody() throws NoSuchMethodExcept assertThat(typeHint.getType()).isEqualTo(TypeReference.of(Request.class)); assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder( MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.DECLARED_FIELDS); + MemberCategory.INVOKE_DECLARED_FIELDS); assertThat(typeHint.methods()).satisfiesExactlyInAnyOrder( hint -> assertThat(hint.getName()).isEqualTo("getMessage"), hint -> assertThat(hint.getName()).isEqualTo("setMessage")); @@ -84,7 +84,7 @@ void registerReflectiveHintsForMethodWithModelAttribute() throws NoSuchMethodExc assertThat(typeHint.getType()).isEqualTo(TypeReference.of(Request.class)); assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder( MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.DECLARED_FIELDS); + MemberCategory.INVOKE_DECLARED_FIELDS); assertThat(typeHint.methods()).satisfiesExactlyInAnyOrder( hint -> assertThat(hint.getName()).isEqualTo("getMessage"), hint -> assertThat(hint.getName()).isEqualTo("setMessage")); @@ -102,7 +102,7 @@ void registerReflectiveHintsForMethodWithRestController() throws NoSuchMethodExc assertThat(typeHint.getType()).isEqualTo(TypeReference.of(Response.class)); assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder( MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.DECLARED_FIELDS); + MemberCategory.INVOKE_DECLARED_FIELDS); assertThat(typeHint.methods()).satisfiesExactlyInAnyOrder( hint -> assertThat(hint.getName()).isEqualTo("getMessage"), hint -> assertThat(hint.getName()).isEqualTo("setMessage")); @@ -141,7 +141,7 @@ void registerReflectiveHintsForMethodReturningHttpEntity() throws NoSuchMethodEx assertThat(typeHint.getType()).isEqualTo(TypeReference.of(Response.class)); assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder( MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.DECLARED_FIELDS); + MemberCategory.INVOKE_DECLARED_FIELDS); assertThat(typeHint.methods()).satisfiesExactlyInAnyOrder( hint -> assertThat(hint.getName()).isEqualTo("getMessage"), hint -> assertThat(hint.getName()).isEqualTo("setMessage")); @@ -167,7 +167,7 @@ void registerReflectiveHintsForMethodWithHttpEntityParameter() throws NoSuchMeth assertThat(typeHint.getType()).isEqualTo(TypeReference.of(Request.class)); assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder( MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.DECLARED_FIELDS); + MemberCategory.INVOKE_DECLARED_FIELDS); assertThat(typeHint.methods()).satisfiesExactlyInAnyOrder( hint -> assertThat(hint.getName()).isEqualTo("getMessage"), hint -> assertThat(hint.getName()).isEqualTo("setMessage")); @@ -193,7 +193,7 @@ void registerReflectiveHintsForMethodWithPartToConvert() throws NoSuchMethodExce assertThat(typeHint.getType()).isEqualTo(TypeReference.of(Request.class)); assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder( MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, - MemberCategory.DECLARED_FIELDS); + MemberCategory.INVOKE_DECLARED_FIELDS); assertThat(typeHint.methods()).satisfiesExactlyInAnyOrder( hint -> assertThat(hint.getName()).isEqualTo("getMessage"), hint -> assertThat(hint.getName()).isEqualTo("setMessage")); From 949432ce8b22c830480fea27ab9d40fe299a5e44 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 3 Dec 2024 13:30:25 +0100 Subject: [PATCH 025/701] General upgrade to Jakarta EE 11 APIs Includes removal of ManagedBean and javax.annotation legacy support. Includes AbstractJson(Http)MessageConverter revision for Yasson 3.0. Includes initial Hibernate ORM 7.0 upgrade. Closes gh-34011 Closes gh-33750 --- .../pages/core/beans/classpath-scanning.adoc | 4 +- .../modules/ROOT/pages/integration/email.adoc | 4 +- .../integration-junit-jupiter.adoc | 6 +- framework-platform/framework-platform.gradle | 38 +- integration-tests/integration-tests.gradle | 2 +- spring-beans/spring-beans.gradle | 1 - .../AutowiredAnnotationBeanPostProcessor.java | 15 +- .../JakartaAnnotationsRuntimeHints.java | 6 +- ...erAnnotationAutowireCandidateResolver.java | 13 +- .../factory/support/RootBeanDefinition.java | 4 +- .../JakartaAnnotationsRuntimeHintsTests.java | 10 - .../CandidateComponentsIndexerTests.java | 7 - .../index/sample/cdi/SampleManagedBean.java | 28 - .../spring-context-support.gradle | 2 +- spring-context/spring-context.gradle | 2 - .../AnnotationBeanNameGenerator.java | 14 +- .../annotation/AnnotationConfigUtils.java | 6 +- .../ClassPathBeanDefinitionScanner.java | 5 +- ...athScanningCandidateComponentProvider.java | 31 +- .../CommonAnnotationBeanPostProcessor.java | 91 -- .../IndexedJakartaManagedBeanComponent.java | 24 - .../IndexedJavaxManagedBeanComponent.java | 24 - .../indexed/IndexedJavaxNamedComponent.java | 24 - .../JakartaManagedBeanComponent.java | 24 - .../scannable/JavaxManagedBeanComponent.java | 24 - .../scannable/JavaxNamedComponent.java | 24 - .../InjectAnnotationAutowireContextTests.java | 56 +- .../AnnotationBeanNameGeneratorTests.java | 18 - ...anningCandidateComponentProviderTests.java | 42 +- ...ommonAnnotationBeanPostProcessorTests.java | 131 -- .../example/scannable/spring.components | 5 - .../AbstractJsonMessageConverter.java | 8 +- .../converter/GsonMessageConverter.java | 6 +- ...tlinSerializationJsonMessageConverter.java | 13 +- spring-orm/spring-orm.gradle | 2 +- ...ernateObjectRetrievalFailureException.java | 22 +- .../orm/hibernate5/HibernateOperations.java | 857 ------------ ...nateOptimisticLockingFailureException.java | 4 +- .../orm/hibernate5/HibernateTemplate.java | 1185 ----------------- .../hibernate5/LocalSessionFactoryBean.java | 8 +- .../LocalSessionFactoryBuilder.java | 9 +- .../orm/hibernate5/SessionFactoryUtils.java | 18 +- .../support/HibernateDaoSupport.java | 139 -- .../orm/jpa/ExtendedEntityManagerCreator.java | 3 +- .../MutablePersistenceUnitInfo.java | 13 + ...agedTypesBeanRegistrationAotProcessor.java | 3 +- .../PersistenceUnitReader.java | 1 + .../jpa/vendor/HibernateJpaVendorAdapter.java | 83 +- ...ontainerEntityManagerFactoryBeanTests.java | 17 +- .../LocalEntityManagerFactoryBeanTests.java | 6 + .../orm/jpa/domain2/package-info.java | 5 +- ...ypesBeanRegistrationAotProcessorTests.java | 10 +- .../PersistenceXmlParsingTests.java | 1 + spring-test/spring-test.gradle | 3 +- .../test/context/TestConstructor.java | 17 +- .../context/support/TestConstructorUtils.java | 14 +- ...terAutowiredConstructorInjectionTests.java | 9 - .../orm/HibernateSessionFlushingTests.java | 13 +- .../hibernate/HibernatePersonRepository.java | 2 +- spring-web/spring-web.gradle | 2 +- .../AbstractJsonHttpMessageConverter.java | 3 +- .../json/GsonHttpMessageConverter.java | 4 +- .../web/jsf/el/SpringBeanFacesELResolver.java | 11 +- .../WebApplicationContextFacesELResolver.java | 11 +- .../web/jsf/MockFacesContext.java | 8 +- 65 files changed, 200 insertions(+), 2995 deletions(-) delete mode 100644 spring-context-indexer/src/test/java/org/springframework/context/index/sample/cdi/SampleManagedBean.java delete mode 100644 spring-context/src/test/java/example/indexed/IndexedJakartaManagedBeanComponent.java delete mode 100644 spring-context/src/test/java/example/indexed/IndexedJavaxManagedBeanComponent.java delete mode 100644 spring-context/src/test/java/example/indexed/IndexedJavaxNamedComponent.java delete mode 100644 spring-context/src/test/java/example/scannable/JakartaManagedBeanComponent.java delete mode 100644 spring-context/src/test/java/example/scannable/JavaxManagedBeanComponent.java delete mode 100644 spring-context/src/test/java/example/scannable/JavaxNamedComponent.java delete mode 100644 spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateOperations.java delete mode 100644 spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTemplate.java delete mode 100644 spring-orm/src/main/java/org/springframework/orm/hibernate5/support/HibernateDaoSupport.java diff --git a/framework-docs/modules/ROOT/pages/core/beans/classpath-scanning.adoc b/framework-docs/modules/ROOT/pages/core/beans/classpath-scanning.adoc index 8193305116be..611009b73f49 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/classpath-scanning.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/classpath-scanning.adoc @@ -674,9 +674,7 @@ By default, the `AnnotationBeanNameGenerator` is used. For Spring xref:core/beans/classpath-scanning.adoc#beans-stereotype-annotations[stereotype annotations], if you supply a name via the annotation's `value` attribute that name will be used as the name in the corresponding bean definition. This convention also applies when the -following JSR-250 and JSR-330 annotations are used instead of Spring stereotype -annotations: `@jakarta.annotation.ManagedBean`, `@javax.annotation.ManagedBean`, -`@jakarta.inject.Named`, and `@javax.inject.Named`. +`@jakarta.inject.Named` annotation is used instead of Spring stereotype annotations. As of Spring Framework 6.1, the name of the annotation attribute that is used to specify the bean name is no longer required to be `value`. Custom stereotype annotations can diff --git a/framework-docs/modules/ROOT/pages/integration/email.adoc b/framework-docs/modules/ROOT/pages/integration/email.adoc index 46493a7de9a0..420bc481c9d8 100644 --- a/framework-docs/modules/ROOT/pages/integration/email.adoc +++ b/framework-docs/modules/ROOT/pages/integration/email.adoc @@ -11,9 +11,7 @@ Spring Framework's email support: * The https://jakartaee.github.io/mail-api/[Jakarta Mail] library This library is freely available on the web -- for example, in Maven Central as -`com.sun.mail:jakarta.mail`. Please make sure to use the latest 2.x version (which uses -the `jakarta.mail` package namespace) rather than Jakarta Mail 1.6.x (which uses the -`javax.mail` package namespace). +`org.eclipse.angus:angus-mail`. **** The Spring Framework provides a helpful utility library for sending email that shields diff --git a/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit-jupiter.adoc b/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit-jupiter.adoc index d359c708ea70..41b16558e1aa 100644 --- a/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit-jupiter.adoc +++ b/framework-docs/modules/ROOT/pages/testing/annotations/integration-junit-jupiter.adoc @@ -171,9 +171,9 @@ the parameters of a test class constructor are autowired from components in the If `@TestConstructor` is not present or meta-present on a test class, the default _test constructor autowire mode_ will be used. See the tip below for details on how to change -the default mode. Note, however, that a local declaration of `@Autowired`, -`@jakarta.inject.Inject`, or `@javax.inject.Inject` on a constructor takes precedence -over both `@TestConstructor` and the default mode. +the default mode. Note, however, that a local declaration of `@Autowired` or +`@jakarta.inject.Inject` on a constructor takes precedence over both `@TestConstructor` +and the default mode. .Changing the default test constructor autowire mode [TIP] diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index 804c6fa58ab6..25979bcdf7c5 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -40,7 +40,6 @@ dependencies { api("com.squareup.okhttp3:mockwebserver:3.14.9") api("com.squareup.okhttp3:okhttp:3.14.9") api("com.sun.activation:jakarta.activation:2.0.1") - api("com.sun.mail:jakarta.mail:2.0.1") api("com.sun.xml.bind:jaxb-core:3.0.2") api("com.sun.xml.bind:jaxb-impl:3.0.2") api("com.sun.xml.bind:jaxb-xjc:3.0.2") @@ -61,32 +60,30 @@ dependencies { api("io.undertow:undertow-servlet:2.3.18.Final") api("io.undertow:undertow-websockets-jsr:2.3.18.Final") api("io.vavr:vavr:0.10.4") - api("jakarta.activation:jakarta.activation-api:2.0.1") - api("jakarta.annotation:jakarta.annotation-api:2.0.0") + api("jakarta.activation:jakarta.activation-api:2.1.3") + api("jakarta.annotation:jakarta.annotation-api:3.0.0") api("jakarta.ejb:jakarta.ejb-api:4.0.1") - api("jakarta.el:jakarta.el-api:4.0.0") - api("jakarta.enterprise.concurrent:jakarta.enterprise.concurrent-api:2.0.0") - api("jakarta.faces:jakarta.faces-api:3.0.0") + api("jakarta.el:jakarta.el-api:6.0.1") + api("jakarta.enterprise.concurrent:jakarta.enterprise.concurrent-api:3.1.1") + api("jakarta.faces:jakarta.faces-api:4.1.2") api("jakarta.inject:jakarta.inject-api:2.0.1") api("jakarta.inject:jakarta.inject-tck:2.0.1") - api("jakarta.interceptor:jakarta.interceptor-api:2.0.0") - api("jakarta.jms:jakarta.jms-api:3.0.0") - api("jakarta.json.bind:jakarta.json.bind-api:2.0.0") - api("jakarta.json:jakarta.json-api:2.0.1") - api("jakarta.mail:jakarta.mail-api:2.0.1") - api("jakarta.persistence:jakarta.persistence-api:3.0.0") - api("jakarta.resource:jakarta.resource-api:2.0.0") + api("jakarta.interceptor:jakarta.interceptor-api:2.2.0") + api("jakarta.jms:jakarta.jms-api:3.1.0") + api("jakarta.json.bind:jakarta.json.bind-api:3.0.1") + api("jakarta.json:jakarta.json-api:2.1.3") + api("jakarta.mail:jakarta.mail-api:2.1.3") + api("jakarta.persistence:jakarta.persistence-api:3.2.0") + api("jakarta.resource:jakarta.resource-api:2.1.0") api("jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.2") api("jakarta.servlet.jsp:jakarta.servlet.jsp-api:4.0.0") api("jakarta.servlet:jakarta.servlet-api:6.1.0") api("jakarta.transaction:jakarta.transaction-api:2.0.1") - api("jakarta.validation:jakarta.validation-api:3.0.2") + api("jakarta.validation:jakarta.validation-api:3.1.0") api("jakarta.websocket:jakarta.websocket-api:2.2.0") api("jakarta.websocket:jakarta.websocket-client-api:2.2.0") api("jakarta.xml.bind:jakarta.xml.bind-api:3.0.1") - api("javax.annotation:javax.annotation-api:1.3.2") api("javax.cache:cache-api:1.1.1") - api("javax.inject:javax.inject:1") api("javax.money:money-api:1.1") api("jaxen:jaxen:1.2.0") api("junit:junit:4.13.2") @@ -117,9 +114,10 @@ dependencies { api("org.crac:crac:1.4.0") api("org.dom4j:dom4j:2.1.4") api("org.easymock:easymock:5.4.0") + api("org.eclipse.angus:angus-mail:2.0.3") api("org.eclipse.jetty:jetty-reactive-httpclient:4.0.8") - api("org.eclipse.persistence:org.eclipse.persistence.jpa:3.0.4") - api("org.eclipse:yasson:2.0.4") + api("org.eclipse.persistence:org.eclipse.persistence.jpa:5.0.0-B04") + api("org.eclipse:yasson:3.0.4") api("org.ehcache:ehcache:3.10.8") api("org.ehcache:jcache:1.0.1") api("org.freemarker:freemarker:2.3.33") @@ -128,8 +126,8 @@ dependencies { api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3") api("org.graalvm.sdk:graal-sdk:22.3.1") api("org.hamcrest:hamcrest:2.2") - api("org.hibernate:hibernate-core-jakarta:5.6.15.Final") - api("org.hibernate:hibernate-validator:7.0.5.Final") + api("org.hibernate:hibernate-core:7.0.0.Beta2") + api("org.hibernate:hibernate-validator:9.0.0.Beta3") api("org.hsqldb:hsqldb:2.7.4") api("org.htmlunit:htmlunit:4.6.0") api("org.javamoney:moneta:1.4.4") diff --git a/integration-tests/integration-tests.gradle b/integration-tests/integration-tests.gradle index 1444b2bb210b..b386be32ddba 100644 --- a/integration-tests/integration-tests.gradle +++ b/integration-tests/integration-tests.gradle @@ -26,7 +26,7 @@ dependencies { testImplementation("jakarta.servlet:jakarta.servlet-api") testImplementation("org.aspectj:aspectjweaver") testImplementation("org.hsqldb:hsqldb") - testImplementation("org.hibernate:hibernate-core-jakarta") + testImplementation("org.hibernate:hibernate-core") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") } diff --git a/spring-beans/spring-beans.gradle b/spring-beans/spring-beans.gradle index c4fb10eb3200..b407bf0ed249 100644 --- a/spring-beans/spring-beans.gradle +++ b/spring-beans/spring-beans.gradle @@ -16,5 +16,4 @@ dependencies { testImplementation(project(":spring-core-test")) testImplementation(testFixtures(project(":spring-core"))) testImplementation("jakarta.annotation:jakarta.annotation-api") - testImplementation("javax.inject:javax.inject") } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index d0ba827163e3..c187dccf97e1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -103,8 +103,6 @@ * *

Also supports the common {@link jakarta.inject.Inject @Inject} annotation, * if available, as a direct alternative to Spring's own {@code @Autowired}. - * Additionally, it retains support for the {@code javax.inject.Inject} variant - * dating back to the original JSR-330 specification (as known from Java EE 6-8). * *

Autowired Constructors

*

Only one constructor of any given bean class may declare this annotation with @@ -189,8 +187,8 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA /** * Create a new {@code AutowiredAnnotationBeanPostProcessor} for Spring's * standard {@link Autowired @Autowired} and {@link Value @Value} annotations. - *

Also supports the common {@link jakarta.inject.Inject @Inject} annotation, - * if available, as well as the original {@code javax.inject.Inject} variant. + *

Also supports the common {@link jakarta.inject.Inject @Inject} annotation + * if available. */ @SuppressWarnings("unchecked") public AutowiredAnnotationBeanPostProcessor() { @@ -206,15 +204,6 @@ public AutowiredAnnotationBeanPostProcessor() { catch (ClassNotFoundException ex) { // jakarta.inject API not available - simply skip. } - - try { - this.autowiredAnnotationTypes.add((Class) - ClassUtils.forName("javax.inject.Inject", classLoader)); - logger.trace("'javax.inject.Inject' annotation found and supported for autowiring"); - } - catch (ClassNotFoundException ex) { - // javax.inject API not available - simply skip. - } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHints.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHints.java index 439b1fb30e47..c4ad0a257daa 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHints.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHints.java @@ -33,14 +33,10 @@ class JakartaAnnotationsRuntimeHints implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { - // javax.inject.Provider is omitted from the list, since we do not currently load - // it via reflection. Stream.of( "jakarta.inject.Inject", "jakarta.inject.Provider", - "jakarta.inject.Qualifier", - "javax.inject.Inject", - "javax.inject.Qualifier" + "jakarta.inject.Qualifier" ).forEach(typeName -> hints.reflection().registerType(TypeReference.of(typeName))); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java index 4281e6539497..1e086230061d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java @@ -47,8 +47,7 @@ * against {@link Qualifier qualifier annotations} on the field or parameter to be autowired. * Also supports suggested expression values through a {@link Value value} annotation. * - *

Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation (as well as its - * pre-Jakarta {@code javax.inject.Qualifier} equivalent), if available. + *

Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation if available. * * @author Mark Fisher * @author Juergen Hoeller @@ -69,8 +68,7 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa /** * Create a new {@code QualifierAnnotationAutowireCandidateResolver} for Spring's * standard {@link Qualifier} annotation. - *

Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation (as well as - * its pre-Jakarta {@code javax.inject.Qualifier} equivalent), if available. + *

Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation if available. */ @SuppressWarnings("unchecked") public QualifierAnnotationAutowireCandidateResolver() { @@ -82,13 +80,6 @@ public QualifierAnnotationAutowireCandidateResolver() { catch (ClassNotFoundException ex) { // JSR-330 API (as included in Jakarta EE) not available - simply skip. } - try { - this.qualifierTypes.add((Class) ClassUtils.forName("javax.inject.Qualifier", - QualifierAnnotationAutowireCandidateResolver.class.getClassLoader())); - } - catch (ClassNotFoundException ex) { - // JSR-330 API not available - simply skip. - } } /** diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java index 67aad00a312e..5f91f6bdcee4 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java @@ -508,8 +508,8 @@ public Set getExternallyManagedConfigMembers() { /** * Register an externally managed configuration initialization method — - * for example, a method annotated with JSR-250's {@code javax.annotation.PostConstruct} - * or Jakarta's {@link jakarta.annotation.PostConstruct} annotation. + * for example, a method annotated with Jakarta's + * {@link jakarta.annotation.PostConstruct} annotation. *

The supplied {@code initMethod} may be a * {@linkplain Method#getName() simple method name} or a * {@linkplain org.springframework.util.ClassUtils#getQualifiedMethodName(Method) diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHintsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHintsTests.java index ef2e236fb689..371e93013d42 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHintsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHintsTests.java @@ -63,14 +63,4 @@ void jakartaQualifierAnnotationHasHints() { assertThat(RuntimeHintsPredicates.reflection().onType(Qualifier.class)).accepts(this.hints); } - @Test // gh-33345 - void javaxInjectAnnotationHasHints() { - assertThat(RuntimeHintsPredicates.reflection().onType(javax.inject.Inject.class)).accepts(this.hints); - } - - @Test // gh-33345 - void javaxQualifierAnnotationHasHints() { - assertThat(RuntimeHintsPredicates.reflection().onType(javax.inject.Qualifier.class)).accepts(this.hints); - } - } diff --git a/spring-context-indexer/src/test/java/org/springframework/context/index/processor/CandidateComponentsIndexerTests.java b/spring-context-indexer/src/test/java/org/springframework/context/index/processor/CandidateComponentsIndexerTests.java index 995081488379..531782f40430 100644 --- a/spring-context-indexer/src/test/java/org/springframework/context/index/processor/CandidateComponentsIndexerTests.java +++ b/spring-context-indexer/src/test/java/org/springframework/context/index/processor/CandidateComponentsIndexerTests.java @@ -21,7 +21,6 @@ import java.io.IOException; import java.nio.file.Path; -import jakarta.annotation.ManagedBean; import jakarta.inject.Named; import jakarta.persistence.Converter; import jakarta.persistence.Embeddable; @@ -43,7 +42,6 @@ import org.springframework.context.index.sample.SampleNone; import org.springframework.context.index.sample.SampleRepository; import org.springframework.context.index.sample.SampleService; -import org.springframework.context.index.sample.cdi.SampleManagedBean; import org.springframework.context.index.sample.cdi.SampleNamed; import org.springframework.context.index.sample.cdi.SampleTransactional; import org.springframework.context.index.sample.jpa.SampleConverter; @@ -126,11 +124,6 @@ void stereotypeOnAbstractClass() { testComponent(AbstractController.class); } - @Test - void cdiManagedBean() { - testSingleComponent(SampleManagedBean.class, ManagedBean.class); - } - @Test void cdiNamed() { testSingleComponent(SampleNamed.class, Named.class); diff --git a/spring-context-indexer/src/test/java/org/springframework/context/index/sample/cdi/SampleManagedBean.java b/spring-context-indexer/src/test/java/org/springframework/context/index/sample/cdi/SampleManagedBean.java deleted file mode 100644 index fb34361664d6..000000000000 --- a/spring-context-indexer/src/test/java/org/springframework/context/index/sample/cdi/SampleManagedBean.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2002-2016 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.context.index.sample.cdi; - -import jakarta.annotation.ManagedBean; - -/** - * Test candidate for a CDI {@link ManagedBean}. - * - * @author Stephane Nicoll - */ -@ManagedBean -public class SampleManagedBean { -} diff --git a/spring-context-support/spring-context-support.gradle b/spring-context-support/spring-context-support.gradle index a2f0c083e734..851125b73e56 100644 --- a/spring-context-support/spring-context-support.gradle +++ b/spring-context-support/spring-context-support.gradle @@ -23,7 +23,7 @@ dependencies { testImplementation("io.projectreactor:reactor-core") testImplementation("jakarta.annotation:jakarta.annotation-api") testImplementation("org.hsqldb:hsqldb") - testRuntimeOnly("com.sun.mail:jakarta.mail") + testRuntimeOnly("org.eclipse.angus:angus-mail") testRuntimeOnly("org.ehcache:ehcache") testRuntimeOnly("org.ehcache:jcache") testRuntimeOnly("org.glassfish:jakarta.el") diff --git a/spring-context/spring-context.gradle b/spring-context/spring-context.gradle index af48a0fa2070..4a24c9ac41cb 100644 --- a/spring-context/spring-context.gradle +++ b/spring-context/spring-context.gradle @@ -21,8 +21,6 @@ dependencies { optional("jakarta.inject:jakarta.inject-api") optional("jakarta.interceptor:jakarta.interceptor-api") optional("jakarta.validation:jakarta.validation-api") - optional("javax.annotation:javax.annotation-api") - optional("javax.inject:javax.inject") optional("javax.money:money-api") optional("org.apache.groovy:groovy") optional("org.apache-extras.beanshell:bsh") diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java index 3361f0560e57..d8bb37df83fd 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -51,11 +51,8 @@ * {@link org.springframework.stereotype.Repository @Repository}) are * themselves annotated with {@code @Component}. * - *

Also supports Jakarta EE's {@link jakarta.annotation.ManagedBean} and - * JSR-330's {@link jakarta.inject.Named} annotations (as well as their pre-Jakarta - * {@code javax.annotation.ManagedBean} and {@code javax.inject.Named} equivalents), - * if available. Note that Spring component annotations always override such - * standard annotations. + *

Also supports JSR-330's {@link jakarta.inject.Named} annotation if available. + * Note that Spring component annotations always override such standard annotations. * *

If the annotation's value doesn't indicate a bean name, an appropriate * name will be built based on the short name of the class (with the first @@ -219,10 +216,7 @@ protected boolean isStereotypeWithNameValue(String annotationType, Set metaAnnotationTypes, Map attributes) { boolean isStereotype = metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME) || - annotationType.equals("jakarta.annotation.ManagedBean") || - annotationType.equals("javax.annotation.ManagedBean") || - annotationType.equals("jakarta.inject.Named") || - annotationType.equals("javax.inject.Named"); + annotationType.equals("jakarta.inject.Named"); return (isStereotype && attributes.containsKey("value")); } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java index e427aaf5b85c..540515fdf21d 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java @@ -117,9 +117,6 @@ public abstract class AnnotationConfigUtils { private static final boolean jakartaAnnotationsPresent = ClassUtils.isPresent("jakarta.annotation.PostConstruct", classLoader); - private static final boolean jsr250Present = - ClassUtils.isPresent("javax.annotation.PostConstruct", classLoader); - private static final boolean jpaPresent = ClassUtils.isPresent("jakarta.persistence.EntityManagerFactory", classLoader) && ClassUtils.isPresent(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, classLoader); @@ -169,8 +166,7 @@ public static Set registerAnnotationConfigProcessors( } // Check for Jakarta Annotations support, and if present add the CommonAnnotationBeanPostProcessor. - if ((jakartaAnnotationsPresent || jsr250Present) && - !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) { + if (jakartaAnnotationsPresent && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java index a68de8cacb99..bee5d806d153 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -48,8 +48,7 @@ * {@link org.springframework.stereotype.Service @Service}, or * {@link org.springframework.stereotype.Controller @Controller} stereotype. * - *

Also supports Jakarta EE's {@link jakarta.annotation.ManagedBean} and - * JSR-330's {@link jakarta.inject.Named} annotations, if available. + *

Also supports JSR-330's {@link jakarta.inject.Named} annotations, if available. * * @author Mark Fisher * @author Juergen Hoeller diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java index daeb1cd833e1..6016fcf6556d 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -216,31 +216,12 @@ public void resetFilters(boolean useDefaultFilters) { * {@link Component @Component} meta-annotation including the * {@link Repository @Repository}, {@link Service @Service}, and * {@link Controller @Controller} stereotype annotations. - *

Also supports Jakarta EE's {@link jakarta.annotation.ManagedBean} and - * JSR-330's {@link jakarta.inject.Named} annotations (as well as their - * pre-Jakarta {@code javax.annotation.ManagedBean} and {@code javax.inject.Named} - * equivalents), if available. + *

Also supports JSR-330's {@link jakarta.inject.Named} annotation if available. */ @SuppressWarnings("unchecked") protected void registerDefaultFilters() { this.includeFilters.add(new AnnotationTypeFilter(Component.class)); ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); - try { - this.includeFilters.add(new AnnotationTypeFilter( - ((Class) ClassUtils.forName("jakarta.annotation.ManagedBean", cl)), false)); - logger.trace("JSR-250 'jakarta.annotation.ManagedBean' found and supported for component scanning"); - } - catch (ClassNotFoundException ex) { - // JSR-250 1.1 API (as included in Jakarta EE) not available - simply skip. - } - try { - this.includeFilters.add(new AnnotationTypeFilter( - ((Class) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false)); - logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning"); - } - catch (ClassNotFoundException ex) { - // JSR-250 1.1 API not available - simply skip. - } try { this.includeFilters.add(new AnnotationTypeFilter( ((Class) ClassUtils.forName("jakarta.inject.Named", cl)), false)); @@ -249,14 +230,6 @@ protected void registerDefaultFilters() { catch (ClassNotFoundException ex) { // JSR-330 API (as included in Jakarta EE) not available - simply skip. } - try { - this.includeFilters.add(new AnnotationTypeFilter( - ((Class) ClassUtils.forName("javax.inject.Named", cl)), false)); - logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning"); - } - catch (ClassNotFoundException ex) { - // JSR-330 API not available - simply skip. - } } /** diff --git a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java index a5ffc7858f8d..7e65b98cdbf2 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java @@ -96,11 +96,6 @@ * and default names as well. The target beans can be simple POJOs, with no special * requirements other than the type having to match. * - *

Additionally, the original {@code javax.annotation} variants of the annotations - * dating back to the JSR-250 specification (Java EE 5-8, also included in JDK 6-8) - * are still supported as well. Note that this is primarily for a smooth upgrade path, - * not for adoption in new applications. - * *

This post-processor also supports the EJB {@link jakarta.ejb.EJB} annotation, * analogous to {@link jakarta.annotation.Resource}, with the capability to * specify both a local bean name and a global JNDI name for fallback retrieval. @@ -154,9 +149,6 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean @Nullable private static final Class jakartaResourceType; - @Nullable - private static final Class javaxResourceType; - @Nullable private static final Class ejbAnnotationType; @@ -166,11 +158,6 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean resourceAnnotationTypes.add(jakartaResourceType); } - javaxResourceType = loadAnnotationType("javax.annotation.Resource"); - if (javaxResourceType != null) { - resourceAnnotationTypes.add(javaxResourceType); - } - ejbAnnotationType = loadAnnotationType("jakarta.ejb.EJB"); if (ejbAnnotationType != null) { resourceAnnotationTypes.add(ejbAnnotationType); @@ -212,10 +199,6 @@ public CommonAnnotationBeanPostProcessor() { addInitAnnotationType(loadAnnotationType("jakarta.annotation.PostConstruct")); addDestroyAnnotationType(loadAnnotationType("jakarta.annotation.PreDestroy")); - // Tolerate legacy JSR-250 annotations in javax.annotation package - addInitAnnotationType(loadAnnotationType("javax.annotation.PostConstruct")); - addDestroyAnnotationType(loadAnnotationType("javax.annotation.PreDestroy")); - // java.naming module present on JDK 9+? if (jndiPresent) { this.jndiFactory = new SimpleJndiBeanFactory(); @@ -444,14 +427,6 @@ else if (jakartaResourceType != null && field.isAnnotationPresent(jakartaResourc currElements.add(new ResourceElement(field, field, null)); } } - else if (javaxResourceType != null && field.isAnnotationPresent(javaxResourceType)) { - if (Modifier.isStatic(field.getModifiers())) { - throw new IllegalStateException("@Resource annotation is not supported on static fields"); - } - if (!this.ignoredResourceTypes.contains(field.getType().getName())) { - currElements.add(new LegacyResourceElement(field, field, null)); - } - } }); ReflectionUtils.doWithLocalMethods(targetClass, method -> { @@ -486,21 +461,6 @@ else if (jakartaResourceType != null && bridgedMethod.isAnnotationPresent(jakart } } } - else if (javaxResourceType != null && bridgedMethod.isAnnotationPresent(javaxResourceType)) { - if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { - if (Modifier.isStatic(method.getModifiers())) { - throw new IllegalStateException("@Resource annotation is not supported on static methods"); - } - Class[] paramTypes = method.getParameterTypes(); - if (paramTypes.length != 1) { - throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method); - } - if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) { - PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); - currElements.add(new LegacyResourceElement(method, bridgedMethod, pd)); - } - } - } }); elements.addAll(0, currElements); @@ -746,57 +706,6 @@ boolean isLazyLookup() { } - /** - * Class representing injection information about an annotated field - * or setter method, supporting the @Resource annotation. - */ - private class LegacyResourceElement extends LookupElement { - - private final boolean lazyLookup; - - public LegacyResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) { - super(member, pd); - javax.annotation.Resource resource = ae.getAnnotation(javax.annotation.Resource.class); - String resourceName = resource.name(); - Class resourceType = resource.type(); - this.isDefaultName = !StringUtils.hasLength(resourceName); - if (this.isDefaultName) { - resourceName = this.member.getName(); - if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) { - resourceName = StringUtils.uncapitalizeAsProperty(resourceName.substring(3)); - } - } - else if (embeddedValueResolver != null) { - resourceName = embeddedValueResolver.resolveStringValue(resourceName); - } - if (Object.class != resourceType) { - checkResourceType(resourceType); - } - else { - // No resource type specified... check field/method. - resourceType = getResourceType(); - } - this.name = (resourceName != null ? resourceName : ""); - this.lookupType = resourceType; - String lookupValue = resource.lookup(); - this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName()); - Lazy lazy = ae.getAnnotation(Lazy.class); - this.lazyLookup = (lazy != null && lazy.value()); - } - - @Override - protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) { - return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) : - getResource(this, requestingBeanName)); - } - - @Override - boolean isLazyLookup() { - return this.lazyLookup; - } - } - - /** * Class representing injection information about an annotated field * or setter method, supporting the @EJB annotation. diff --git a/spring-context/src/test/java/example/indexed/IndexedJakartaManagedBeanComponent.java b/spring-context/src/test/java/example/indexed/IndexedJakartaManagedBeanComponent.java deleted file mode 100644 index ed640a7a73da..000000000000 --- a/spring-context/src/test/java/example/indexed/IndexedJakartaManagedBeanComponent.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2002-2022 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 example.indexed; - -/** - * @author Sam Brannen - */ -@jakarta.annotation.ManagedBean -public class IndexedJakartaManagedBeanComponent { -} diff --git a/spring-context/src/test/java/example/indexed/IndexedJavaxManagedBeanComponent.java b/spring-context/src/test/java/example/indexed/IndexedJavaxManagedBeanComponent.java deleted file mode 100644 index b563b4d37973..000000000000 --- a/spring-context/src/test/java/example/indexed/IndexedJavaxManagedBeanComponent.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2002-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 example.indexed; - -/** - * @author Sam Brannen - */ -@javax.annotation.ManagedBean -public class IndexedJavaxManagedBeanComponent { -} diff --git a/spring-context/src/test/java/example/indexed/IndexedJavaxNamedComponent.java b/spring-context/src/test/java/example/indexed/IndexedJavaxNamedComponent.java deleted file mode 100644 index 581be8a6f97d..000000000000 --- a/spring-context/src/test/java/example/indexed/IndexedJavaxNamedComponent.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2002-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 example.indexed; - -/** - * @author Sam Brannen - */ -@javax.inject.Named("myIndexedJavaxNamedComponent") -public class IndexedJavaxNamedComponent { -} diff --git a/spring-context/src/test/java/example/scannable/JakartaManagedBeanComponent.java b/spring-context/src/test/java/example/scannable/JakartaManagedBeanComponent.java deleted file mode 100644 index 6140ea0dce36..000000000000 --- a/spring-context/src/test/java/example/scannable/JakartaManagedBeanComponent.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2002-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 example.scannable; - -/** - * @author Sam Brannen - */ -@jakarta.annotation.ManagedBean("myJakartaManagedBeanComponent") -public class JakartaManagedBeanComponent { -} diff --git a/spring-context/src/test/java/example/scannable/JavaxManagedBeanComponent.java b/spring-context/src/test/java/example/scannable/JavaxManagedBeanComponent.java deleted file mode 100644 index b3029035d874..000000000000 --- a/spring-context/src/test/java/example/scannable/JavaxManagedBeanComponent.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2002-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 example.scannable; - -/** - * @author Sam Brannen - */ -@javax.annotation.ManagedBean("myJavaxManagedBeanComponent") -public class JavaxManagedBeanComponent { -} diff --git a/spring-context/src/test/java/example/scannable/JavaxNamedComponent.java b/spring-context/src/test/java/example/scannable/JavaxNamedComponent.java deleted file mode 100644 index a0fe78e7429a..000000000000 --- a/spring-context/src/test/java/example/scannable/JavaxNamedComponent.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2002-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 example.scannable; - -/** - * @author Sam Brannen - */ -@javax.inject.Named("myJavaxNamedComponent") -public class JavaxNamedComponent { -} diff --git a/spring-context/src/test/java/org/springframework/beans/factory/support/InjectAnnotationAutowireContextTests.java b/spring-context/src/test/java/org/springframework/beans/factory/support/InjectAnnotationAutowireContextTests.java index 48d19fbf7e81..bfbcbb096779 100644 --- a/spring-context/src/test/java/org/springframework/beans/factory/support/InjectAnnotationAutowireContextTests.java +++ b/spring-context/src/test/java/org/springframework/beans/factory/support/InjectAnnotationAutowireContextTests.java @@ -40,8 +40,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** - * Integration tests for handling JSR-330 {@link jakarta.inject.Qualifier} and - * {@link javax.inject.Qualifier} annotations. + * Integration tests for handling {@link jakarta.inject.Qualifier} annotations. * * @author Juergen Hoeller * @author Sam Brannen @@ -317,16 +316,6 @@ void autowiredConstructorArgumentResolvesJakartaNamedCandidate() { assertThat(bean.getAnimal2().getName()).isEqualTo("Jakarta Fido"); } - @Test // gh-33345 - void autowiredConstructorArgumentResolvesJavaxNamedCandidate() { - Class testBeanClass = JavaxNamedConstructorArgumentTestBean.class; - AnnotationConfigApplicationContext context = - new AnnotationConfigApplicationContext(testBeanClass, JavaxCat.class, JavaxDog.class); - JavaxNamedConstructorArgumentTestBean bean = context.getBean(testBeanClass); - assertThat(bean.getAnimal1().getName()).isEqualTo("Javax Tiger"); - assertThat(bean.getAnimal2().getName()).isEqualTo("Javax Fido"); - } - @Test void autowiredFieldResolvesQualifiedCandidateWithDefaultValueAndNoValueOnBeanDefinition() { GenericApplicationContext context = new GenericApplicationContext(); @@ -587,29 +576,6 @@ public Animal getAnimal2() { } - static class JavaxNamedConstructorArgumentTestBean { - - private final Animal animal1; - private final Animal animal2; - - @javax.inject.Inject - public JavaxNamedConstructorArgumentTestBean(@javax.inject.Named("Cat") Animal animal1, - @javax.inject.Named("Dog") Animal animal2) { - - this.animal1 = animal1; - this.animal2 = animal2; - } - - public Animal getAnimal1() { - return this.animal1; - } - - public Animal getAnimal2() { - return this.animal2; - } - } - - public static class QualifiedFieldWithDefaultValueTestBean { @Inject @@ -705,16 +671,6 @@ public String getName() { } - @javax.inject.Named("Cat") - static class JavaxCat implements Animal { - - @Override - public String getName() { - return "Javax Tiger"; - } - } - - @jakarta.inject.Named("Dog") static class JakartaDog implements Animal { @@ -725,16 +681,6 @@ public String getName() { } - @javax.inject.Named("Dog") - static class JavaxDog implements Animal { - - @Override - public String getName() { - return "Javax Fido"; - } - } - - @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationBeanNameGeneratorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationBeanNameGeneratorTests.java index 884bc07d7869..8af7c9a322ca 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationBeanNameGeneratorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationBeanNameGeneratorTests.java @@ -23,10 +23,7 @@ import java.util.List; import example.scannable.DefaultNamedComponent; -import example.scannable.JakartaManagedBeanComponent; import example.scannable.JakartaNamedComponent; -import example.scannable.JavaxManagedBeanComponent; -import example.scannable.JavaxNamedComponent; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; @@ -108,21 +105,6 @@ void generateBeanNameWithJakartaNamedComponent() { assertGeneratedName(JakartaNamedComponent.class, "myJakartaNamedComponent"); } - @Test - void generateBeanNameWithJavaxNamedComponent() { - assertGeneratedName(JavaxNamedComponent.class, "myJavaxNamedComponent"); - } - - @Test - void generateBeanNameWithJakartaManagedBeanComponent() { - assertGeneratedName(JakartaManagedBeanComponent.class, "myJakartaManagedBeanComponent"); - } - - @Test - void generateBeanNameWithJavaxManagedBeanComponent() { - assertGeneratedName(JavaxManagedBeanComponent.class, "myJavaxManagedBeanComponent"); - } - @Test void generateBeanNameWithCustomStereotypeComponent() { assertGeneratedName(DefaultNamedComponent.class, "thoreau"); diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java index f7880f4910dc..48c6f6db728d 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -27,10 +27,7 @@ import java.util.stream.Stream; import example.gh24375.AnnotatedComponent; -import example.indexed.IndexedJakartaManagedBeanComponent; import example.indexed.IndexedJakartaNamedComponent; -import example.indexed.IndexedJavaxManagedBeanComponent; -import example.indexed.IndexedJavaxNamedComponent; import example.profilescan.DevComponent; import example.profilescan.ProfileAnnotatedComponent; import example.profilescan.ProfileMetaAnnotatedComponent; @@ -40,10 +37,7 @@ import example.scannable.FooDao; import example.scannable.FooService; import example.scannable.FooServiceImpl; -import example.scannable.JakartaManagedBeanComponent; import example.scannable.JakartaNamedComponent; -import example.scannable.JavaxManagedBeanComponent; -import example.scannable.JavaxNamedComponent; import example.scannable.MessageBean; import example.scannable.NamedComponent; import example.scannable.NamedStubDao; @@ -99,51 +93,31 @@ class ClassPathScanningCandidateComponentProviderTests { BarComponent.class ); - private static final Set> scannedJakartaComponents = Set.of( - JakartaNamedComponent.class, - JakartaManagedBeanComponent.class - ); - - private static final Set> scannedJavaxComponents = Set.of( - JavaxNamedComponent.class, - JavaxManagedBeanComponent.class - ); - - private static final Set> indexedComponents = Set.of( - IndexedJakartaNamedComponent.class, - IndexedJakartaManagedBeanComponent.class, - IndexedJavaxNamedComponent.class, - IndexedJavaxManagedBeanComponent.class - ); - @Test void defaultsWithScan() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true); provider.setResourceLoader(new DefaultResourceLoader( CandidateComponentsTestClassLoader.disableIndex(getClass().getClassLoader()))); - testDefault(provider, TEST_BASE_PACKAGE, true, true, false); + testDefault(provider, TEST_BASE_PACKAGE, true, false); } @Test void defaultsWithIndex() { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true); provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER)); - testDefault(provider, "example", true, true, true); + testDefault(provider, "example", true, true); } private void testDefault(ClassPathScanningCandidateComponentProvider provider, String basePackage, - boolean includeScannedJakartaComponents, boolean includeScannedJavaxComponents, boolean includeIndexedComponents) { + boolean includeScannedJakartaComponents, boolean includeIndexedComponents) { Set> expectedTypes = new HashSet<>(springComponents); if (includeScannedJakartaComponents) { - expectedTypes.addAll(scannedJakartaComponents); - } - if (includeScannedJavaxComponents) { - expectedTypes.addAll(scannedJavaxComponents); + expectedTypes.add(JakartaNamedComponent.class); } if (includeIndexedComponents) { - expectedTypes.addAll(indexedComponents); + expectedTypes.add(IndexedJakartaNamedComponent.class); } Set candidates = provider.findCandidateComponents(basePackage); @@ -216,7 +190,7 @@ void customAnnotationTypeIncludeFilterWithIndex() { private void testCustomAnnotationTypeIncludeFilter(ClassPathScanningCandidateComponentProvider provider) { provider.addIncludeFilter(new AnnotationTypeFilter(Component.class)); - testDefault(provider, TEST_BASE_PACKAGE, false, false, false); + testDefault(provider, TEST_BASE_PACKAGE, false, false); } @Test @@ -309,7 +283,7 @@ private void testExclude(ClassPathScanningCandidateComponentProvider provider) { Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); assertScannedBeanDefinitions(candidates); assertBeanTypes(candidates, FooServiceImpl.class, StubFooDao.class, ServiceInvocationCounter.class, - BarComponent.class, JakartaManagedBeanComponent.class, JavaxManagedBeanComponent.class); + BarComponent.class); } @Test diff --git a/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java index b08c67573eb2..170d26dcf3d6 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessorTests.java @@ -116,16 +116,6 @@ void postConstructAndPreDestroyWithApplicationContextAndPostProcessor() { assertThat(bean.destroyCalled).isTrue(); } - @Test - void postConstructAndPreDestroyWithLegacyAnnotations() { - bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(LegacyAnnotatedInitDestroyBean.class)); - - LegacyAnnotatedInitDestroyBean bean = (LegacyAnnotatedInitDestroyBean) bf.getBean("annotatedBean"); - assertThat(bean.initCalled).isTrue(); - bf.destroySingletons(); - assertThat(bean.destroyCalled).isTrue(); - } - @Test void postConstructAndPreDestroyWithManualConfiguration() { InitDestroyAnnotationBeanPostProcessor bpp = new InitDestroyAnnotationBeanPostProcessor(); @@ -223,26 +213,6 @@ void resourceInjectionWithPrototypes() { assertThat(bean.destroy3Called).isTrue(); } - @Test - void resourceInjectionWithLegacyAnnotations() { - bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(LegacyResourceInjectionBean.class)); - TestBean tb = new TestBean(); - bf.registerSingleton("testBean", tb); - TestBean tb2 = new TestBean(); - bf.registerSingleton("testBean2", tb2); - - LegacyResourceInjectionBean bean = (LegacyResourceInjectionBean) bf.getBean("annotatedBean"); - assertThat(bean.initCalled).isTrue(); - assertThat(bean.init2Called).isTrue(); - assertThat(bean.init3Called).isTrue(); - assertThat(bean.getTestBean()).isSameAs(tb); - assertThat(bean.getTestBean2()).isSameAs(tb2); - bf.destroySingletons(); - assertThat(bean.destroyCalled).isTrue(); - assertThat(bean.destroy2Called).isTrue(); - assertThat(bean.destroy3Called).isTrue(); - } - @Test void resourceInjectionWithResolvableDependencyType() { bpp.setBeanFactory(bf); @@ -558,30 +528,6 @@ private void destroy() { } - public static class LegacyAnnotatedInitDestroyBean { - - public boolean initCalled = false; - - public boolean destroyCalled = false; - - @javax.annotation.PostConstruct - private void init() { - if (this.initCalled) { - throw new IllegalStateException("Already called"); - } - this.initCalled = true; - } - - @javax.annotation.PreDestroy - private void destroy() { - if (this.destroyCalled) { - throw new IllegalStateException("Already called"); - } - this.destroyCalled = true; - } - } - - public static class InitDestroyBeanPostProcessor implements DestructionAwareBeanPostProcessor { @Override @@ -691,83 +637,6 @@ public TestBean getTestBean2() { } - public static class LegacyResourceInjectionBean extends LegacyAnnotatedInitDestroyBean { - - public boolean init2Called = false; - - public boolean init3Called = false; - - public boolean destroy2Called = false; - - public boolean destroy3Called = false; - - @javax.annotation.Resource - private TestBean testBean; - - private TestBean testBean2; - - @javax.annotation.PostConstruct - protected void init2() { - if (this.testBean == null || this.testBean2 == null) { - throw new IllegalStateException("Resources not injected"); - } - if (!this.initCalled) { - throw new IllegalStateException("Superclass init method not called yet"); - } - if (this.init2Called) { - throw new IllegalStateException("Already called"); - } - this.init2Called = true; - } - - @javax.annotation.PostConstruct - private void init() { - if (this.init3Called) { - throw new IllegalStateException("Already called"); - } - this.init3Called = true; - } - - @javax.annotation.PreDestroy - protected void destroy2() { - if (this.destroyCalled) { - throw new IllegalStateException("Superclass destroy called too soon"); - } - if (this.destroy2Called) { - throw new IllegalStateException("Already called"); - } - this.destroy2Called = true; - } - - @javax.annotation.PreDestroy - private void destroy() { - if (this.destroyCalled) { - throw new IllegalStateException("Superclass destroy called too soon"); - } - if (this.destroy3Called) { - throw new IllegalStateException("Already called"); - } - this.destroy3Called = true; - } - - @javax.annotation.Resource - public void setTestBean2(TestBean testBean2) { - if (this.testBean2 != null) { - throw new IllegalStateException("Already called"); - } - this.testBean2 = testBean2; - } - - public TestBean getTestBean() { - return testBean; - } - - public TestBean getTestBean2() { - return testBean2; - } - } - - static class NonPublicResourceInjectionBean extends ResourceInjectionBean { @Resource(name="testBean4", type=TestBean.class) diff --git a/spring-context/src/test/resources/example/scannable/spring.components b/spring-context/src/test/resources/example/scannable/spring.components index 3fdf592b1965..8c298cd44d96 100644 --- a/spring-context/src/test/resources/example/scannable/spring.components +++ b/spring-context/src/test/resources/example/scannable/spring.components @@ -10,9 +10,4 @@ example.scannable.ServiceInvocationCounter=org.springframework.stereotype.Compon example.scannable.sub.BarComponent=org.springframework.stereotype.Component example.scannable.JakartaManagedBeanComponent=jakarta.annotation.ManagedBean example.scannable.JakartaNamedComponent=jakarta.inject.Named -example.scannable.JavaxManagedBeanComponent=javax.annotation.ManagedBean -example.scannable.JavaxNamedComponent=javax.inject.Named -example.indexed.IndexedJakartaManagedBeanComponent=jakarta.annotation.ManagedBean example.indexed.IndexedJakartaNamedComponent=jakarta.inject.Named -example.indexed.IndexedJavaxManagedBeanComponent=javax.annotation.ManagedBean -example.indexed.IndexedJavaxNamedComponent=javax.inject.Named diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractJsonMessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractJsonMessageConverter.java index d3032dc5ad06..a73dccd77baa 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractJsonMessageConverter.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/AbstractJsonMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -18,6 +18,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; @@ -91,7 +92,6 @@ protected Object convertToInternal(Object payload, @Nullable MessageHeaders head ByteArrayOutputStream out = new ByteArrayOutputStream(1024); Writer writer = getWriter(out, headers); toJson(payload, resolvedType, writer); - writer.flush(); return out.toByteArray(); } else { @@ -120,11 +120,11 @@ private Charset getCharsetToUse(@Nullable MessageHeaders headers) { } - protected abstract Object fromJson(Reader reader, Type resolvedType); + protected abstract Object fromJson(Reader reader, Type resolvedType) throws IOException; protected abstract Object fromJson(String payload, Type resolvedType); - protected abstract void toJson(Object payload, Type resolvedType, Writer writer); + protected abstract void toJson(Object payload, Type resolvedType, Writer writer) throws IOException; protected abstract String toJson(Object payload, Type resolvedType); diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/GsonMessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/GsonMessageConverter.java index 95de58ede268..0e15dfc09b75 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/converter/GsonMessageConverter.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/GsonMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-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. @@ -16,6 +16,7 @@ package org.springframework.messaging.converter; +import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.lang.reflect.ParameterizedType; @@ -88,13 +89,14 @@ protected Object fromJson(String payload, Type resolvedType) { } @Override - protected void toJson(Object payload, Type resolvedType, Writer writer) { + protected void toJson(Object payload, Type resolvedType, Writer writer) throws IOException { if (resolvedType instanceof ParameterizedType) { getGson().toJson(payload, resolvedType, writer); } else { getGson().toJson(payload, writer); } + writer.flush(); } @Override diff --git a/spring-messaging/src/main/java/org/springframework/messaging/converter/KotlinSerializationJsonMessageConverter.java b/spring-messaging/src/main/java/org/springframework/messaging/converter/KotlinSerializationJsonMessageConverter.java index 44e4aa830f3a..75decdb84589 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/converter/KotlinSerializationJsonMessageConverter.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/converter/KotlinSerializationJsonMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -76,13 +76,9 @@ protected Object fromJson(String payload, Type resolvedType) { } @Override - protected void toJson(Object payload, Type resolvedType, Writer writer) { - try { - writer.write(toJson(payload, resolvedType).toCharArray()); - } - catch (IOException ex) { - throw new MessageConversionException("Could not write JSON: " + ex.getMessage(), ex); - } + protected void toJson(Object payload, Type resolvedType, Writer writer) throws IOException { + writer.write(toJson(payload, resolvedType).toCharArray()); + writer.flush(); } @Override @@ -106,4 +102,5 @@ private KSerializer serializer(Type type) { } return serializer; } + } diff --git a/spring-orm/spring-orm.gradle b/spring-orm/spring-orm.gradle index db00899a11f4..9f39583ece1a 100644 --- a/spring-orm/spring-orm.gradle +++ b/spring-orm/spring-orm.gradle @@ -11,7 +11,7 @@ dependencies { optional(project(":spring-web")) optional("jakarta.servlet:jakarta.servlet-api") optional("org.eclipse.persistence:org.eclipse.persistence.jpa") - optional("org.hibernate:hibernate-core-jakarta") + optional("org.hibernate:hibernate-core") testImplementation(project(":spring-core-test")) testImplementation(testFixtures(project(":spring-beans"))) testImplementation(testFixtures(project(":spring-context"))) diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateObjectRetrievalFailureException.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateObjectRetrievalFailureException.java index 150ae55c6d1d..e06342c26a6c 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateObjectRetrievalFailureException.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateObjectRetrievalFailureException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -16,13 +16,10 @@ package org.springframework.orm.hibernate5; -import org.hibernate.HibernateException; import org.hibernate.UnresolvableObjectException; import org.hibernate.WrongClassException; -import org.springframework.lang.Nullable; import org.springframework.orm.ObjectRetrievalFailureException; -import org.springframework.util.ReflectionUtils; /** * Hibernate-specific subclass of ObjectRetrievalFailureException. @@ -36,24 +33,11 @@ public class HibernateObjectRetrievalFailureException extends ObjectRetrievalFailureException { public HibernateObjectRetrievalFailureException(UnresolvableObjectException ex) { - super(ex.getEntityName(), getIdentifier(ex), ex.getMessage(), ex); + super(ex.getEntityName(), ex.getIdentifier(), ex.getMessage(), ex); } public HibernateObjectRetrievalFailureException(WrongClassException ex) { - super(ex.getEntityName(), getIdentifier(ex), ex.getMessage(), ex); - } - - - @Nullable - static Object getIdentifier(HibernateException hibEx) { - try { - // getIdentifier declares Serializable return value on 5.x but Object on 6.x - // -> not binary compatible, let's invoke it reflectively for the time being - return ReflectionUtils.invokeMethod(hibEx.getClass().getMethod("getIdentifier"), hibEx); - } - catch (NoSuchMethodException ex) { - return null; - } + super(ex.getEntityName(), ex.getIdentifier(), ex.getMessage(), ex); } } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateOperations.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateOperations.java deleted file mode 100644 index 0bd481af29f9..000000000000 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateOperations.java +++ /dev/null @@ -1,857 +0,0 @@ -/* - * Copyright 2002-2021 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.orm.hibernate5; - -import java.io.Serializable; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; - -import org.hibernate.Filter; -import org.hibernate.LockMode; -import org.hibernate.ReplicationMode; -import org.hibernate.criterion.DetachedCriteria; - -import org.springframework.dao.DataAccessException; -import org.springframework.lang.Nullable; - -/** - * Interface that specifies a common set of Hibernate operations as well as - * a general {@link #execute} method for Session-based lambda expressions. - * Implemented by {@link HibernateTemplate}. Not often used, but a useful option - * to enhance testability, as it can easily be mocked or stubbed. - * - *

Defines {@code HibernateTemplate}'s data access methods that mirror various - * {@link org.hibernate.Session} methods. Users are strongly encouraged to read the - * Hibernate {@code Session} javadocs for details on the semantics of those methods. - * - *

A deprecation note: While {@link HibernateTemplate} and this operations - * interface are being kept around for backwards compatibility in terms of the data - * access implementation style in Spring applications, we strongly recommend the use - * of native {@link org.hibernate.Session} access code for non-trivial interactions. - * This in particular affects parameterized queries where - on Java 8+ - a custom - * {@link HibernateCallback} lambda code block with {@code createQuery} and several - * {@code setParameter} calls on the {@link org.hibernate.query.Query} interface - * is an elegant solution, to be executed via the general {@link #execute} method. - * All such operations which benefit from a lambda variant have been marked as - * {@code deprecated} on this interface. - * - *

A Hibernate compatibility note: {@link HibernateTemplate} and the - * operations on this interface generally aim to be applicable across all Hibernate - * versions. In terms of binary compatibility, Spring ships a variant for each major - * generation of Hibernate (in the present case: Hibernate ORM 5.x). However, due to - * refactorings and removals in Hibernate ORM 5.3, some variants - in particular - * legacy positional parameters starting from index 0 - do not work anymore. - * All affected operations are marked as deprecated; please replace them with the - * general {@link #execute} method and custom lambda blocks creating the queries, - * ideally setting named parameters through {@link org.hibernate.query.Query}. - * Please be aware that deprecated operations are known to work with Hibernate - * ORM 5.2 but may not work with Hibernate ORM 5.3 and higher anymore. - * - * @author Juergen Hoeller - * @since 4.2 - * @see HibernateTemplate - * @see org.hibernate.Session - * @see HibernateTransactionManager - */ -public interface HibernateOperations { - - /** - * Execute the action specified by the given action object within a - * {@link org.hibernate.Session}. - *

Application exceptions thrown by the action object get propagated - * to the caller (can only be unchecked). Hibernate exceptions are - * transformed into appropriate DAO ones. Allows for returning a result - * object, that is a domain object or a collection of domain objects. - *

Note: Callback code is not supposed to handle transactions itself! - * Use an appropriate transaction manager like - * {@link HibernateTransactionManager}. Generally, callback code must not - * touch any {@code Session} lifecycle methods, like close, - * disconnect, or reconnect, to let the template do its work. - * @param action callback object that specifies the Hibernate action - * @return a result object returned by the action, or {@code null} - * @throws DataAccessException in case of Hibernate errors - * @see HibernateTransactionManager - * @see org.hibernate.Session - */ - @Nullable - T execute(HibernateCallback action) throws DataAccessException; - - - //------------------------------------------------------------------------- - // Convenience methods for loading individual objects - //------------------------------------------------------------------------- - - /** - * Return the persistent instance of the given entity class - * with the given identifier, or {@code null} if not found. - *

This method is a thin wrapper around - * {@link org.hibernate.Session#get(Class, Serializable)} for convenience. - * For an explanation of the exact semantics of this method, please do refer to - * the Hibernate API documentation in the first instance. - * @param entityClass a persistent class - * @param id the identifier of the persistent instance - * @return the persistent instance, or {@code null} if not found - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#get(Class, Serializable) - */ - @Nullable - T get(Class entityClass, Serializable id) throws DataAccessException; - - /** - * Return the persistent instance of the given entity class - * with the given identifier, or {@code null} if not found. - *

Obtains the specified lock mode if the instance exists. - *

This method is a thin wrapper around - * {@link org.hibernate.Session#get(Class, Serializable, LockMode)} for convenience. - * For an explanation of the exact semantics of this method, please do refer to - * the Hibernate API documentation in the first instance. - * @param entityClass a persistent class - * @param id the identifier of the persistent instance - * @param lockMode the lock mode to obtain - * @return the persistent instance, or {@code null} if not found - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#get(Class, Serializable, LockMode) - */ - @Nullable - T get(Class entityClass, Serializable id, LockMode lockMode) throws DataAccessException; - - /** - * Return the persistent instance of the given entity class - * with the given identifier, or {@code null} if not found. - *

This method is a thin wrapper around - * {@link org.hibernate.Session#get(String, Serializable)} for convenience. - * For an explanation of the exact semantics of this method, please do refer to - * the Hibernate API documentation in the first instance. - * @param entityName the name of the persistent entity - * @param id the identifier of the persistent instance - * @return the persistent instance, or {@code null} if not found - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#get(Class, Serializable) - */ - @Nullable - Object get(String entityName, Serializable id) throws DataAccessException; - - /** - * Return the persistent instance of the given entity class - * with the given identifier, or {@code null} if not found. - * Obtains the specified lock mode if the instance exists. - *

This method is a thin wrapper around - * {@link org.hibernate.Session#get(String, Serializable, LockMode)} for convenience. - * For an explanation of the exact semantics of this method, please do refer to - * the Hibernate API documentation in the first instance. - * @param entityName the name of the persistent entity - * @param id the identifier of the persistent instance - * @param lockMode the lock mode to obtain - * @return the persistent instance, or {@code null} if not found - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#get(Class, Serializable, LockMode) - */ - @Nullable - Object get(String entityName, Serializable id, LockMode lockMode) throws DataAccessException; - - /** - * Return the persistent instance of the given entity class - * with the given identifier, throwing an exception if not found. - *

This method is a thin wrapper around - * {@link org.hibernate.Session#load(Class, Serializable)} for convenience. - * For an explanation of the exact semantics of this method, please do refer to - * the Hibernate API documentation in the first instance. - * @param entityClass a persistent class - * @param id the identifier of the persistent instance - * @return the persistent instance - * @throws org.springframework.orm.ObjectRetrievalFailureException if not found - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#load(Class, Serializable) - */ - T load(Class entityClass, Serializable id) throws DataAccessException; - - /** - * Return the persistent instance of the given entity class - * with the given identifier, throwing an exception if not found. - * Obtains the specified lock mode if the instance exists. - *

This method is a thin wrapper around - * {@link org.hibernate.Session#load(Class, Serializable, LockMode)} for convenience. - * For an explanation of the exact semantics of this method, please do refer to - * the Hibernate API documentation in the first instance. - * @param entityClass a persistent class - * @param id the identifier of the persistent instance - * @param lockMode the lock mode to obtain - * @return the persistent instance - * @throws org.springframework.orm.ObjectRetrievalFailureException if not found - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#load(Class, Serializable) - */ - T load(Class entityClass, Serializable id, LockMode lockMode) throws DataAccessException; - - /** - * Return the persistent instance of the given entity class - * with the given identifier, throwing an exception if not found. - *

This method is a thin wrapper around - * {@link org.hibernate.Session#load(String, Serializable)} for convenience. - * For an explanation of the exact semantics of this method, please do refer to - * the Hibernate API documentation in the first instance. - * @param entityName the name of the persistent entity - * @param id the identifier of the persistent instance - * @return the persistent instance - * @throws org.springframework.orm.ObjectRetrievalFailureException if not found - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#load(Class, Serializable) - */ - Object load(String entityName, Serializable id) throws DataAccessException; - - /** - * Return the persistent instance of the given entity class - * with the given identifier, throwing an exception if not found. - *

Obtains the specified lock mode if the instance exists. - *

This method is a thin wrapper around - * {@link org.hibernate.Session#load(String, Serializable, LockMode)} for convenience. - * For an explanation of the exact semantics of this method, please do refer to - * the Hibernate API documentation in the first instance. - * @param entityName the name of the persistent entity - * @param id the identifier of the persistent instance - * @param lockMode the lock mode to obtain - * @return the persistent instance - * @throws org.springframework.orm.ObjectRetrievalFailureException if not found - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#load(Class, Serializable) - */ - Object load(String entityName, Serializable id, LockMode lockMode) throws DataAccessException; - - /** - * Return all persistent instances of the given entity class. - * Note: Use queries or criteria for retrieving a specific subset. - * @param entityClass a persistent class - * @return a {@link List} containing 0 or more persistent instances - * @throws DataAccessException if there is a Hibernate error - * @see org.hibernate.Session#createCriteria - */ - List loadAll(Class entityClass) throws DataAccessException; - - /** - * Load the persistent instance with the given identifier - * into the given object, throwing an exception if not found. - *

This method is a thin wrapper around - * {@link org.hibernate.Session#load(Object, Serializable)} for convenience. - * For an explanation of the exact semantics of this method, please do refer to - * the Hibernate API documentation in the first instance. - * @param entity the object (of the target class) to load into - * @param id the identifier of the persistent instance - * @throws org.springframework.orm.ObjectRetrievalFailureException if not found - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#load(Object, Serializable) - */ - void load(Object entity, Serializable id) throws DataAccessException; - - /** - * Re-read the state of the given persistent instance. - * @param entity the persistent instance to re-read - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#refresh(Object) - */ - void refresh(Object entity) throws DataAccessException; - - /** - * Re-read the state of the given persistent instance. - * Obtains the specified lock mode for the instance. - * @param entity the persistent instance to re-read - * @param lockMode the lock mode to obtain - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#refresh(Object, LockMode) - */ - void refresh(Object entity, LockMode lockMode) throws DataAccessException; - - /** - * Check whether the given object is in the Session cache. - * @param entity the persistence instance to check - * @return whether the given object is in the Session cache - * @throws DataAccessException if there is a Hibernate error - * @see org.hibernate.Session#contains - */ - boolean contains(Object entity) throws DataAccessException; - - /** - * Remove the given object from the {@link org.hibernate.Session} cache. - * @param entity the persistent instance to evict - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#evict - */ - void evict(Object entity) throws DataAccessException; - - /** - * Force initialization of a Hibernate proxy or persistent collection. - * @param proxy a proxy for a persistent object or a persistent collection - * @throws DataAccessException if we can't initialize the proxy, for example - * because it is not associated with an active Session - * @see org.hibernate.Hibernate#initialize - */ - void initialize(Object proxy) throws DataAccessException; - - /** - * Return an enabled Hibernate {@link Filter} for the given filter name. - * The returned {@code Filter} instance can be used to set filter parameters. - * @param filterName the name of the filter - * @return the enabled Hibernate {@code Filter} (either already - * enabled or enabled on the fly by this operation) - * @throws IllegalStateException if we are not running within a - * transactional Session (in which case this operation does not make sense) - */ - Filter enableFilter(String filterName) throws IllegalStateException; - - - //------------------------------------------------------------------------- - // Convenience methods for storing individual objects - //------------------------------------------------------------------------- - - /** - * Obtain the specified lock level upon the given object, implicitly - * checking whether the corresponding database entry still exists. - * @param entity the persistent instance to lock - * @param lockMode the lock mode to obtain - * @throws org.springframework.orm.ObjectOptimisticLockingFailureException if not found - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#lock(Object, LockMode) - */ - void lock(Object entity, LockMode lockMode) throws DataAccessException; - - /** - * Obtain the specified lock level upon the given object, implicitly - * checking whether the corresponding database entry still exists. - * @param entityName the name of the persistent entity - * @param entity the persistent instance to lock - * @param lockMode the lock mode to obtain - * @throws org.springframework.orm.ObjectOptimisticLockingFailureException if not found - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#lock(String, Object, LockMode) - */ - void lock(String entityName, Object entity, LockMode lockMode) throws DataAccessException; - - /** - * Persist the given transient instance. - * @param entity the transient instance to persist - * @return the generated identifier - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#save(Object) - */ - Serializable save(Object entity) throws DataAccessException; - - /** - * Persist the given transient instance. - * @param entityName the name of the persistent entity - * @param entity the transient instance to persist - * @return the generated identifier - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#save(String, Object) - */ - Serializable save(String entityName, Object entity) throws DataAccessException; - - /** - * Update the given persistent instance, - * associating it with the current Hibernate {@link org.hibernate.Session}. - * @param entity the persistent instance to update - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#update(Object) - */ - void update(Object entity) throws DataAccessException; - - /** - * Update the given persistent instance, - * associating it with the current Hibernate {@link org.hibernate.Session}. - *

Obtains the specified lock mode if the instance exists, implicitly - * checking whether the corresponding database entry still exists. - * @param entity the persistent instance to update - * @param lockMode the lock mode to obtain - * @throws org.springframework.orm.ObjectOptimisticLockingFailureException if not found - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#update(Object) - */ - void update(Object entity, LockMode lockMode) throws DataAccessException; - - /** - * Update the given persistent instance, - * associating it with the current Hibernate {@link org.hibernate.Session}. - * @param entityName the name of the persistent entity - * @param entity the persistent instance to update - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#update(String, Object) - */ - void update(String entityName, Object entity) throws DataAccessException; - - /** - * Update the given persistent instance, - * associating it with the current Hibernate {@link org.hibernate.Session}. - *

Obtains the specified lock mode if the instance exists, implicitly - * checking whether the corresponding database entry still exists. - * @param entityName the name of the persistent entity - * @param entity the persistent instance to update - * @param lockMode the lock mode to obtain - * @throws org.springframework.orm.ObjectOptimisticLockingFailureException if not found - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#update(String, Object) - */ - void update(String entityName, Object entity, LockMode lockMode) throws DataAccessException; - - /** - * Save or update the given persistent instance, - * according to its id (matching the configured "unsaved-value"?). - * Associates the instance with the current Hibernate {@link org.hibernate.Session}. - * @param entity the persistent instance to save or update - * (to be associated with the Hibernate {@code Session}) - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#saveOrUpdate(Object) - */ - void saveOrUpdate(Object entity) throws DataAccessException; - - /** - * Save or update the given persistent instance, - * according to its id (matching the configured "unsaved-value"?). - * Associates the instance with the current Hibernate {@code Session}. - * @param entityName the name of the persistent entity - * @param entity the persistent instance to save or update - * (to be associated with the Hibernate {@code Session}) - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#saveOrUpdate(String, Object) - */ - void saveOrUpdate(String entityName, Object entity) throws DataAccessException; - - /** - * Persist the state of the given detached instance according to the - * given replication mode, reusing the current identifier value. - * @param entity the persistent object to replicate - * @param replicationMode the Hibernate ReplicationMode - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#replicate(Object, ReplicationMode) - */ - void replicate(Object entity, ReplicationMode replicationMode) throws DataAccessException; - - /** - * Persist the state of the given detached instance according to the - * given replication mode, reusing the current identifier value. - * @param entityName the name of the persistent entity - * @param entity the persistent object to replicate - * @param replicationMode the Hibernate ReplicationMode - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#replicate(String, Object, ReplicationMode) - */ - void replicate(String entityName, Object entity, ReplicationMode replicationMode) throws DataAccessException; - - /** - * Persist the given transient instance. Follows JSR-220 semantics. - *

Similar to {@code save}, associating the given object - * with the current Hibernate {@link org.hibernate.Session}. - * @param entity the persistent instance to persist - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#persist(Object) - * @see #save - */ - void persist(Object entity) throws DataAccessException; - - /** - * Persist the given transient instance. Follows JSR-220 semantics. - *

Similar to {@code save}, associating the given object - * with the current Hibernate {@link org.hibernate.Session}. - * @param entityName the name of the persistent entity - * @param entity the persistent instance to persist - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#persist(String, Object) - * @see #save - */ - void persist(String entityName, Object entity) throws DataAccessException; - - /** - * Copy the state of the given object onto the persistent object - * with the same identifier. Follows JSR-220 semantics. - *

Similar to {@code saveOrUpdate}, but never associates the given - * object with the current Hibernate Session. In case of a new entity, - * the state will be copied over as well. - *

Note that {@code merge} will not update the identifiers - * in the passed-in object graph (in contrast to TopLink)! Consider - * registering Spring's {@code IdTransferringMergeEventListener} if - * you would like to have newly assigned ids transferred to the original - * object graph too. - * @param entity the object to merge with the corresponding persistence instance - * @return the updated, registered persistent instance - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#merge(Object) - * @see #saveOrUpdate - */ - T merge(T entity) throws DataAccessException; - - /** - * Copy the state of the given object onto the persistent object - * with the same identifier. Follows JSR-220 semantics. - *

Similar to {@code saveOrUpdate}, but never associates the given - * object with the current Hibernate {@link org.hibernate.Session}. In - * the case of a new entity, the state will be copied over as well. - *

Note that {@code merge} will not update the identifiers - * in the passed-in object graph (in contrast to TopLink)! Consider - * registering Spring's {@code IdTransferringMergeEventListener} - * if you would like to have newly assigned ids transferred to the - * original object graph too. - * @param entityName the name of the persistent entity - * @param entity the object to merge with the corresponding persistence instance - * @return the updated, registered persistent instance - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#merge(String, Object) - * @see #saveOrUpdate - */ - T merge(String entityName, T entity) throws DataAccessException; - - /** - * Delete the given persistent instance. - * @param entity the persistent instance to delete - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#delete(Object) - */ - void delete(Object entity) throws DataAccessException; - - /** - * Delete the given persistent instance. - *

Obtains the specified lock mode if the instance exists, implicitly - * checking whether the corresponding database entry still exists. - * @param entity the persistent instance to delete - * @param lockMode the lock mode to obtain - * @throws org.springframework.orm.ObjectOptimisticLockingFailureException if not found - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#delete(Object) - */ - void delete(Object entity, LockMode lockMode) throws DataAccessException; - - /** - * Delete the given persistent instance. - * @param entityName the name of the persistent entity - * @param entity the persistent instance to delete - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#delete(Object) - */ - void delete(String entityName, Object entity) throws DataAccessException; - - /** - * Delete the given persistent instance. - *

Obtains the specified lock mode if the instance exists, implicitly - * checking whether the corresponding database entry still exists. - * @param entityName the name of the persistent entity - * @param entity the persistent instance to delete - * @param lockMode the lock mode to obtain - * @throws org.springframework.orm.ObjectOptimisticLockingFailureException if not found - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#delete(Object) - */ - void delete(String entityName, Object entity, LockMode lockMode) throws DataAccessException; - - /** - * Delete all given persistent instances. - *

This can be combined with any of the find methods to delete by query - * in two lines of code. - * @param entities the persistent instances to delete - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#delete(Object) - */ - void deleteAll(Collection entities) throws DataAccessException; - - /** - * Flush all pending saves, updates and deletes to the database. - *

Only invoke this for selective eager flushing, for example when - * JDBC code needs to see certain changes within the same transaction. - * Else, it is preferable to rely on auto-flushing at transaction - * completion. - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#flush - */ - void flush() throws DataAccessException; - - /** - * Remove all objects from the {@link org.hibernate.Session} cache, and - * cancel all pending saves, updates and deletes. - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#clear - */ - void clear() throws DataAccessException; - - - //------------------------------------------------------------------------- - // Convenience finder methods for detached criteria - //------------------------------------------------------------------------- - - /** - * Execute a query based on a given Hibernate criteria object. - * @param criteria the detached Hibernate criteria object. - * Note: Do not reuse criteria objects! They need to recreated per execution, - * due to the suboptimal design of Hibernate's criteria facility. - * @return a {@link List} containing 0 or more persistent instances - * @throws DataAccessException in case of Hibernate errors - * @see DetachedCriteria#getExecutableCriteria(org.hibernate.Session) - */ - List findByCriteria(DetachedCriteria criteria) throws DataAccessException; - - /** - * Execute a query based on the given Hibernate criteria object. - * @param criteria the detached Hibernate criteria object. - * Note: Do not reuse criteria objects! They need to recreated per execution, - * due to the suboptimal design of Hibernate's criteria facility. - * @param firstResult the index of the first result object to be retrieved - * (numbered from 0) - * @param maxResults the maximum number of result objects to retrieve - * (or <=0 for no limit) - * @return a {@link List} containing 0 or more persistent instances - * @throws DataAccessException in case of Hibernate errors - * @see DetachedCriteria#getExecutableCriteria(org.hibernate.Session) - * @see org.hibernate.Criteria#setFirstResult(int) - * @see org.hibernate.Criteria#setMaxResults(int) - */ - List findByCriteria(DetachedCriteria criteria, int firstResult, int maxResults) throws DataAccessException; - - /** - * Execute a query based on the given example entity object. - * @param exampleEntity an instance of the desired entity, - * serving as example for "query-by-example" - * @return a {@link List} containing 0 or more persistent instances - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.criterion.Example#create(Object) - */ - List findByExample(T exampleEntity) throws DataAccessException; - - /** - * Execute a query based on the given example entity object. - * @param entityName the name of the persistent entity - * @param exampleEntity an instance of the desired entity, - * serving as example for "query-by-example" - * @return a {@link List} containing 0 or more persistent instances - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.criterion.Example#create(Object) - */ - List findByExample(String entityName, T exampleEntity) throws DataAccessException; - - /** - * Execute a query based on a given example entity object. - * @param exampleEntity an instance of the desired entity, - * serving as example for "query-by-example" - * @param firstResult the index of the first result object to be retrieved - * (numbered from 0) - * @param maxResults the maximum number of result objects to retrieve - * (or <=0 for no limit) - * @return a {@link List} containing 0 or more persistent instances - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.criterion.Example#create(Object) - * @see org.hibernate.Criteria#setFirstResult(int) - * @see org.hibernate.Criteria#setMaxResults(int) - */ - List findByExample(T exampleEntity, int firstResult, int maxResults) throws DataAccessException; - - /** - * Execute a query based on a given example entity object. - * @param entityName the name of the persistent entity - * @param exampleEntity an instance of the desired entity, - * serving as example for "query-by-example" - * @param firstResult the index of the first result object to be retrieved - * (numbered from 0) - * @param maxResults the maximum number of result objects to retrieve - * (or <=0 for no limit) - * @return a {@link List} containing 0 or more persistent instances - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.criterion.Example#create(Object) - * @see org.hibernate.Criteria#setFirstResult(int) - * @see org.hibernate.Criteria#setMaxResults(int) - */ - List findByExample(String entityName, T exampleEntity, int firstResult, int maxResults) - throws DataAccessException; - - - //------------------------------------------------------------------------- - // Convenience finder methods for HQL strings - //------------------------------------------------------------------------- - - /** - * Execute an HQL query, binding a number of values to "?" parameters - * in the query string. - * @param queryString a query expressed in Hibernate's query language - * @param values the values of the parameters - * @return a {@link List} containing the results of the query execution - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#createQuery - * @deprecated as of 5.0.4, in favor of a custom {@link HibernateCallback} - * lambda code block passed to the general {@link #execute} method - */ - @Deprecated - List find(String queryString, Object... values) throws DataAccessException; - - /** - * Execute an HQL query, binding one value to a ":" named parameter - * in the query string. - * @param queryString a query expressed in Hibernate's query language - * @param paramName the name of the parameter - * @param value the value of the parameter - * @return a {@link List} containing the results of the query execution - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#getNamedQuery(String) - * @deprecated as of 5.0.4, in favor of a custom {@link HibernateCallback} - * lambda code block passed to the general {@link #execute} method - */ - @Deprecated - List findByNamedParam(String queryString, String paramName, Object value) throws DataAccessException; - - /** - * Execute an HQL query, binding a number of values to ":" named - * parameters in the query string. - * @param queryString a query expressed in Hibernate's query language - * @param paramNames the names of the parameters - * @param values the values of the parameters - * @return a {@link List} containing the results of the query execution - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#getNamedQuery(String) - * @deprecated as of 5.0.4, in favor of a custom {@link HibernateCallback} - * lambda code block passed to the general {@link #execute} method - */ - @Deprecated - List findByNamedParam(String queryString, String[] paramNames, Object[] values) throws DataAccessException; - - /** - * Execute an HQL query, binding the properties of the given bean to - * named parameters in the query string. - * @param queryString a query expressed in Hibernate's query language - * @param valueBean the values of the parameters - * @return a {@link List} containing the results of the query execution - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Query#setProperties - * @see org.hibernate.Session#createQuery - * @deprecated as of 5.0.4, in favor of a custom {@link HibernateCallback} - * lambda code block passed to the general {@link #execute} method - */ - @Deprecated - List findByValueBean(String queryString, Object valueBean) throws DataAccessException; - - - //------------------------------------------------------------------------- - // Convenience finder methods for named queries - //------------------------------------------------------------------------- - - /** - * Execute a named query binding a number of values to "?" parameters - * in the query string. - *

A named query is defined in a Hibernate mapping file. - * @param queryName the name of a Hibernate query in a mapping file - * @param values the values of the parameters - * @return a {@link List} containing the results of the query execution - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#getNamedQuery(String) - * @deprecated as of 5.0.4, in favor of a custom {@link HibernateCallback} - * lambda code block passed to the general {@link #execute} method - */ - @Deprecated - List findByNamedQuery(String queryName, Object... values) throws DataAccessException; - - /** - * Execute a named query, binding one value to a ":" named parameter - * in the query string. - *

A named query is defined in a Hibernate mapping file. - * @param queryName the name of a Hibernate query in a mapping file - * @param paramName the name of parameter - * @param value the value of the parameter - * @return a {@link List} containing the results of the query execution - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#getNamedQuery(String) - * @deprecated as of 5.0.4, in favor of a custom {@link HibernateCallback} - * lambda code block passed to the general {@link #execute} method - */ - @Deprecated - List findByNamedQueryAndNamedParam(String queryName, String paramName, Object value) - throws DataAccessException; - - /** - * Execute a named query, binding a number of values to ":" named - * parameters in the query string. - *

A named query is defined in a Hibernate mapping file. - * @param queryName the name of a Hibernate query in a mapping file - * @param paramNames the names of the parameters - * @param values the values of the parameters - * @return a {@link List} containing the results of the query execution - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#getNamedQuery(String) - * @deprecated as of 5.0.4, in favor of a custom {@link HibernateCallback} - * lambda code block passed to the general {@link #execute} method - */ - @Deprecated - List findByNamedQueryAndNamedParam(String queryName, String[] paramNames, Object[] values) - throws DataAccessException; - - /** - * Execute a named query, binding the properties of the given bean to - * ":" named parameters in the query string. - *

A named query is defined in a Hibernate mapping file. - * @param queryName the name of a Hibernate query in a mapping file - * @param valueBean the values of the parameters - * @return a {@link List} containing the results of the query execution - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Query#setProperties - * @see org.hibernate.Session#getNamedQuery(String) - * @deprecated as of 5.0.4, in favor of a custom {@link HibernateCallback} - * lambda code block passed to the general {@link #execute} method - */ - @Deprecated - List findByNamedQueryAndValueBean(String queryName, Object valueBean) throws DataAccessException; - - - //------------------------------------------------------------------------- - // Convenience query methods for iteration and bulk updates/deletes - //------------------------------------------------------------------------- - - /** - * Execute a query for persistent instances, binding a number of - * values to "?" parameters in the query string. - *

Returns the results as an {@link Iterator}. Entities returned are - * initialized on demand. See the Hibernate API documentation for details. - * @param queryString a query expressed in Hibernate's query language - * @param values the values of the parameters - * @return an {@link Iterator} containing 0 or more persistent instances - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#createQuery - * @see org.hibernate.Query#iterate - * @deprecated as of 5.0.4, in favor of a custom {@link HibernateCallback} - * lambda code block passed to the general {@link #execute} method - */ - @Deprecated - Iterator iterate(String queryString, Object... values) throws DataAccessException; - - /** - * Immediately close an {@link Iterator} created by any of the various - * {@code iterate(..)} operations, instead of waiting until the - * session is closed or disconnected. - * @param it the {@code Iterator} to close - * @throws DataAccessException if the {@code Iterator} could not be closed - * @see org.hibernate.Hibernate#close - * @deprecated as of 5.0.4, in favor of a custom {@link HibernateCallback} - * lambda code block passed to the general {@link #execute} method - */ - @Deprecated - void closeIterator(Iterator it) throws DataAccessException; - - /** - * Update/delete all objects according to the given query, binding a number of - * values to "?" parameters in the query string. - * @param queryString an update/delete query expressed in Hibernate's query language - * @param values the values of the parameters - * @return the number of instances updated/deleted - * @throws DataAccessException in case of Hibernate errors - * @see org.hibernate.Session#createQuery - * @see org.hibernate.Query#executeUpdate - * @deprecated as of 5.0.4, in favor of a custom {@link HibernateCallback} - * lambda code block passed to the general {@link #execute} method - */ - @Deprecated - int bulkUpdate(String queryString, Object... values) throws DataAccessException; - -} diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateOptimisticLockingFailureException.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateOptimisticLockingFailureException.java index 0246df5a2818..c3bbfe98a33d 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateOptimisticLockingFailureException.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateOptimisticLockingFailureException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -35,7 +35,7 @@ public class HibernateOptimisticLockingFailureException extends ObjectOptimisticLockingFailureException { public HibernateOptimisticLockingFailureException(StaleObjectStateException ex) { - super(ex.getEntityName(), HibernateObjectRetrievalFailureException.getIdentifier(ex), ex.getMessage(), ex); + super(ex.getEntityName(), ex.getIdentifier(), ex.getMessage(), ex); } public HibernateOptimisticLockingFailureException(StaleStateException ex) { diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTemplate.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTemplate.java deleted file mode 100644 index 51ef7fd4620b..000000000000 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTemplate.java +++ /dev/null @@ -1,1185 +0,0 @@ -/* - * Copyright 2002-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.orm.hibernate5; - -import java.io.Serializable; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; - -import jakarta.persistence.PersistenceException; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.hibernate.Criteria; -import org.hibernate.Filter; -import org.hibernate.FlushMode; -import org.hibernate.Hibernate; -import org.hibernate.HibernateException; -import org.hibernate.LockMode; -import org.hibernate.LockOptions; -import org.hibernate.ReplicationMode; -import org.hibernate.Session; -import org.hibernate.SessionFactory; -import org.hibernate.criterion.DetachedCriteria; -import org.hibernate.criterion.Example; -import org.hibernate.query.Query; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.dao.DataAccessException; -import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.lang.Nullable; -import org.springframework.transaction.support.ResourceHolderSupport; -import org.springframework.transaction.support.TransactionSynchronizationManager; -import org.springframework.util.Assert; - -/** - * Helper class that simplifies Hibernate data access code. Automatically - * converts HibernateExceptions into DataAccessExceptions, following the - * {@code org.springframework.dao} exception hierarchy. - * - *

The central method is {@code execute}, supporting Hibernate access code - * implementing the {@link HibernateCallback} interface. It provides Hibernate Session - * handling such that neither the HibernateCallback implementation nor the calling - * code needs to explicitly care about retrieving/closing Hibernate Sessions, - * or handling Session lifecycle exceptions. For typical single step actions, - * there are various convenience methods (find, load, saveOrUpdate, delete). - * - *

Can be used within a service implementation via direct instantiation - * with a SessionFactory reference, or get prepared in an application context - * and given to services as bean reference. Note: The SessionFactory should - * always be configured as bean in the application context, in the first case - * given to the service directly, in the second case to the prepared template. - * - *

NOTE: Hibernate access code can also be coded against the native Hibernate - * {@link Session}. Hence, for newly started projects, consider adopting the standard - * Hibernate style of coding against {@link SessionFactory#getCurrentSession()}. - * Alternatively, use {@link #execute(HibernateCallback)} with Java 8 lambda code blocks - * against the callback-provided {@code Session} which results in elegant code as well, - * decoupled from the Hibernate Session lifecycle. The remaining operations on this - * HibernateTemplate are deprecated in the meantime and primarily exist as a migration - * helper for older Hibernate 3.x/4.x data access code in existing applications. - * - * @author Juergen Hoeller - * @since 4.2 - * @see #setSessionFactory - * @see HibernateCallback - * @see Session - * @see LocalSessionFactoryBean - * @see HibernateTransactionManager - * @see org.springframework.orm.hibernate5.support.OpenSessionInViewFilter - * @see org.springframework.orm.hibernate5.support.OpenSessionInViewInterceptor - */ -public class HibernateTemplate implements HibernateOperations, InitializingBean { - - protected final Log logger = LogFactory.getLog(getClass()); - - @Nullable - private SessionFactory sessionFactory; - - @Nullable - private String[] filterNames; - - private boolean exposeNativeSession = false; - - private boolean checkWriteOperations = true; - - private boolean cacheQueries = false; - - @Nullable - private String queryCacheRegion; - - private int fetchSize = 0; - - private int maxResults = 0; - - - /** - * Create a new HibernateTemplate instance. - */ - public HibernateTemplate() { - } - - /** - * Create a new HibernateTemplate instance. - * @param sessionFactory the SessionFactory to create Sessions with - */ - public HibernateTemplate(SessionFactory sessionFactory) { - setSessionFactory(sessionFactory); - afterPropertiesSet(); - } - - - /** - * Set the Hibernate SessionFactory that should be used to create - * Hibernate Sessions. - */ - public void setSessionFactory(@Nullable SessionFactory sessionFactory) { - this.sessionFactory = sessionFactory; - } - - /** - * Return the Hibernate SessionFactory that should be used to create - * Hibernate Sessions. - */ - @Nullable - public SessionFactory getSessionFactory() { - return this.sessionFactory; - } - - /** - * Obtain the SessionFactory for actual use. - * @return the SessionFactory (never {@code null}) - * @throws IllegalStateException in case of no SessionFactory set - * @since 5.0 - */ - protected final SessionFactory obtainSessionFactory() { - SessionFactory sessionFactory = getSessionFactory(); - Assert.state(sessionFactory != null, "No SessionFactory set"); - return sessionFactory; - } - - /** - * Set one or more names of Hibernate filters to be activated for all - * Sessions that this accessor works with. - *

Each of those filters will be enabled at the beginning of each - * operation and correspondingly disabled at the end of the operation. - * This will work for newly opened Sessions as well as for existing - * Sessions (for example, within a transaction). - * @see #enableFilters(Session) - * @see Session#enableFilter(String) - */ - public void setFilterNames(@Nullable String... filterNames) { - this.filterNames = filterNames; - } - - /** - * Return the names of Hibernate filters to be activated, if any. - */ - @Nullable - public String[] getFilterNames() { - return this.filterNames; - } - - /** - * Set whether to expose the native Hibernate Session to - * HibernateCallback code. - *

Default is "false": a Session proxy will be returned, suppressing - * {@code close} calls and automatically applying query cache - * settings and transaction timeouts. - * @see HibernateCallback - * @see Session - * @see #setCacheQueries - * @see #setQueryCacheRegion - * @see #prepareQuery - * @see #prepareCriteria - */ - public void setExposeNativeSession(boolean exposeNativeSession) { - this.exposeNativeSession = exposeNativeSession; - } - - /** - * Return whether to expose the native Hibernate Session to - * HibernateCallback code, or rather a Session proxy. - */ - public boolean isExposeNativeSession() { - return this.exposeNativeSession; - } - - /** - * Set whether to check that the Hibernate Session is not in read-only mode - * in case of write operations (save/update/delete). - *

Default is "true", for fail-fast behavior when attempting write operations - * within a read-only transaction. Turn this off to allow save/update/delete - * on a Session with flush mode MANUAL. - * @see #checkWriteOperationAllowed - * @see org.springframework.transaction.TransactionDefinition#isReadOnly - */ - public void setCheckWriteOperations(boolean checkWriteOperations) { - this.checkWriteOperations = checkWriteOperations; - } - - /** - * Return whether to check that the Hibernate Session is not in read-only - * mode in case of write operations (save/update/delete). - */ - public boolean isCheckWriteOperations() { - return this.checkWriteOperations; - } - - /** - * Set whether to cache all queries executed by this template. - *

If this is "true", all Query and Criteria objects created by - * this template will be marked as cacheable (including all - * queries through find methods). - *

To specify the query region to be used for queries cached - * by this template, set the "queryCacheRegion" property. - * @see #setQueryCacheRegion - * @see Query#setCacheable - * @see Criteria#setCacheable - */ - public void setCacheQueries(boolean cacheQueries) { - this.cacheQueries = cacheQueries; - } - - /** - * Return whether to cache all queries executed by this template. - */ - public boolean isCacheQueries() { - return this.cacheQueries; - } - - /** - * Set the name of the cache region for queries executed by this template. - *

If this is specified, it will be applied to all Query and Criteria objects - * created by this template (including all queries through find methods). - *

The cache region will not take effect unless queries created by this - * template are configured to be cached via the "cacheQueries" property. - * @see #setCacheQueries - * @see Query#setCacheRegion - * @see Criteria#setCacheRegion - */ - public void setQueryCacheRegion(@Nullable String queryCacheRegion) { - this.queryCacheRegion = queryCacheRegion; - } - - /** - * Return the name of the cache region for queries executed by this template. - */ - @Nullable - public String getQueryCacheRegion() { - return this.queryCacheRegion; - } - - /** - * Set the fetch size for this HibernateTemplate. This is important for processing - * large result sets: Setting this higher than the default value will increase - * processing speed at the cost of memory consumption; setting this lower can - * avoid transferring row data that will never be read by the application. - *

Default is 0, indicating to use the JDBC driver's default. - */ - public void setFetchSize(int fetchSize) { - this.fetchSize = fetchSize; - } - - /** - * Return the fetch size specified for this HibernateTemplate. - */ - public int getFetchSize() { - return this.fetchSize; - } - - /** - * Set the maximum number of rows for this HibernateTemplate. This is important - * for processing subsets of large result sets, avoiding to read and hold - * the entire result set in the database or in the JDBC driver if we're - * never interested in the entire result in the first place (for example, - * when performing searches that might return a large number of matches). - *

Default is 0, indicating to use the JDBC driver's default. - */ - public void setMaxResults(int maxResults) { - this.maxResults = maxResults; - } - - /** - * Return the maximum number of rows specified for this HibernateTemplate. - */ - public int getMaxResults() { - return this.maxResults; - } - - @Override - public void afterPropertiesSet() { - if (getSessionFactory() == null) { - throw new IllegalArgumentException("Property 'sessionFactory' is required"); - } - } - - - @Override - @Nullable - public T execute(HibernateCallback action) throws DataAccessException { - return doExecute(action, false); - } - - /** - * Execute the action specified by the given action object within a - * native {@link Session}. - *

This execute variant overrides the template-wide - * {@link #isExposeNativeSession() "exposeNativeSession"} setting. - * @param action callback object that specifies the Hibernate action - * @return a result object returned by the action, or {@code null} - * @throws DataAccessException in case of Hibernate errors - */ - @Nullable - public T executeWithNativeSession(HibernateCallback action) { - return doExecute(action, true); - } - - /** - * Execute the action specified by the given action object within a Session. - * @param action callback object that specifies the Hibernate action - * @param enforceNativeSession whether to enforce exposure of the native - * Hibernate Session to callback code - * @return a result object returned by the action, or {@code null} - * @throws DataAccessException in case of Hibernate errors - */ - @Nullable - protected T doExecute(HibernateCallback action, boolean enforceNativeSession) throws DataAccessException { - Assert.notNull(action, "Callback object must not be null"); - - Session session = null; - boolean isNew = false; - try { - session = obtainSessionFactory().getCurrentSession(); - } - catch (HibernateException ex) { - logger.debug("Could not retrieve pre-bound Hibernate session", ex); - } - if (session == null) { - session = obtainSessionFactory().openSession(); - session.setHibernateFlushMode(FlushMode.MANUAL); - isNew = true; - } - - try { - enableFilters(session); - Session sessionToExpose = - (enforceNativeSession || isExposeNativeSession() ? session : createSessionProxy(session)); - return action.doInHibernate(sessionToExpose); - } - catch (HibernateException ex) { - throw SessionFactoryUtils.convertHibernateAccessException(ex); - } - catch (PersistenceException ex) { - if (ex.getCause() instanceof HibernateException hibernateEx) { - throw SessionFactoryUtils.convertHibernateAccessException(hibernateEx); - } - throw ex; - } - catch (RuntimeException ex) { - // Callback code threw application exception... - throw ex; - } - finally { - if (isNew) { - SessionFactoryUtils.closeSession(session); - } - else { - disableFilters(session); - } - } - } - - /** - * Create a close-suppressing proxy for the given Hibernate Session. - * The proxy also prepares returned Query and Criteria objects. - * @param session the Hibernate Session to create a proxy for - * @return the Session proxy - * @see Session#close() - * @see #prepareQuery - * @see #prepareCriteria - */ - protected Session createSessionProxy(Session session) { - return (Session) Proxy.newProxyInstance( - session.getClass().getClassLoader(), new Class[] {Session.class}, - new CloseSuppressingInvocationHandler(session)); - } - - /** - * Enable the specified filters on the given Session. - * @param session the current Hibernate Session - * @see #setFilterNames - * @see Session#enableFilter(String) - */ - protected void enableFilters(Session session) { - String[] filterNames = getFilterNames(); - if (filterNames != null) { - for (String filterName : filterNames) { - session.enableFilter(filterName); - } - } - } - - /** - * Disable the specified filters on the given Session. - * @param session the current Hibernate Session - * @see #setFilterNames - * @see Session#disableFilter(String) - */ - protected void disableFilters(Session session) { - String[] filterNames = getFilterNames(); - if (filterNames != null) { - for (String filterName : filterNames) { - session.disableFilter(filterName); - } - } - } - - - //------------------------------------------------------------------------- - // Convenience methods for loading individual objects - //------------------------------------------------------------------------- - - @Override - @Nullable - public T get(Class entityClass, Serializable id) throws DataAccessException { - return get(entityClass, id, null); - } - - @Override - @Nullable - public T get(Class entityClass, Serializable id, @Nullable LockMode lockMode) throws DataAccessException { - return executeWithNativeSession(session -> { - if (lockMode != null) { - return session.get(entityClass, id, new LockOptions(lockMode)); - } - else { - return session.get(entityClass, id); - } - }); - } - - @Override - @Nullable - public Object get(String entityName, Serializable id) throws DataAccessException { - return get(entityName, id, null); - } - - @Override - @Nullable - public Object get(String entityName, Serializable id, @Nullable LockMode lockMode) throws DataAccessException { - return executeWithNativeSession(session -> { - if (lockMode != null) { - return session.get(entityName, id, new LockOptions(lockMode)); - } - else { - return session.get(entityName, id); - } - }); - } - - @Override - public T load(Class entityClass, Serializable id) throws DataAccessException { - return load(entityClass, id, null); - } - - @Override - public T load(Class entityClass, Serializable id, @Nullable LockMode lockMode) - throws DataAccessException { - - return nonNull(executeWithNativeSession(session -> { - if (lockMode != null) { - return session.load(entityClass, id, new LockOptions(lockMode)); - } - else { - return session.load(entityClass, id); - } - })); - } - - @Override - public Object load(String entityName, Serializable id) throws DataAccessException { - return load(entityName, id, null); - } - - @Override - public Object load(String entityName, Serializable id, @Nullable LockMode lockMode) throws DataAccessException { - return nonNull(executeWithNativeSession(session -> { - if (lockMode != null) { - return session.load(entityName, id, new LockOptions(lockMode)); - } - else { - return session.load(entityName, id); - } - })); - } - - @Override - @SuppressWarnings({"unchecked", "deprecation"}) - public List loadAll(Class entityClass) throws DataAccessException { - return nonNull(executeWithNativeSession((HibernateCallback>) session -> { - Criteria criteria = session.createCriteria(entityClass); - criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); - prepareCriteria(criteria); - return criteria.list(); - })); - } - - @Override - public void load(Object entity, Serializable id) throws DataAccessException { - executeWithNativeSession(session -> { - session.load(entity, id); - return null; - }); - } - - @Override - public void refresh(Object entity) throws DataAccessException { - refresh(entity, null); - } - - @Override - public void refresh(Object entity, @Nullable LockMode lockMode) throws DataAccessException { - executeWithNativeSession(session -> { - if (lockMode != null) { - session.refresh(entity, new LockOptions(lockMode)); - } - else { - session.refresh(entity); - } - return null; - }); - } - - @Override - public boolean contains(Object entity) throws DataAccessException { - Boolean result = executeWithNativeSession(session -> session.contains(entity)); - Assert.state(result != null, "No contains result"); - return result; - } - - @Override - public void evict(Object entity) throws DataAccessException { - executeWithNativeSession(session -> { - session.evict(entity); - return null; - }); - } - - @Override - public void initialize(Object proxy) throws DataAccessException { - try { - Hibernate.initialize(proxy); - } - catch (HibernateException ex) { - throw SessionFactoryUtils.convertHibernateAccessException(ex); - } - } - - @Override - public Filter enableFilter(String filterName) throws IllegalStateException { - Session session = obtainSessionFactory().getCurrentSession(); - Filter filter = session.getEnabledFilter(filterName); - if (filter == null) { - filter = session.enableFilter(filterName); - } - return filter; - } - - - //------------------------------------------------------------------------- - // Convenience methods for storing individual objects - //------------------------------------------------------------------------- - - @Override - public void lock(Object entity, LockMode lockMode) throws DataAccessException { - executeWithNativeSession(session -> { - session.buildLockRequest(new LockOptions(lockMode)).lock(entity); - return null; - }); - } - - @Override - public void lock(String entityName, Object entity, LockMode lockMode) - throws DataAccessException { - - executeWithNativeSession(session -> { - session.buildLockRequest(new LockOptions(lockMode)).lock(entityName, entity); - return null; - }); - } - - @Override - public Serializable save(Object entity) throws DataAccessException { - return nonNull(executeWithNativeSession(session -> { - checkWriteOperationAllowed(session); - return session.save(entity); - })); - } - - @Override - public Serializable save(String entityName, Object entity) throws DataAccessException { - return nonNull(executeWithNativeSession(session -> { - checkWriteOperationAllowed(session); - return session.save(entityName, entity); - })); - } - - @Override - public void update(Object entity) throws DataAccessException { - update(entity, null); - } - - @Override - public void update(Object entity, @Nullable LockMode lockMode) throws DataAccessException { - executeWithNativeSession(session -> { - checkWriteOperationAllowed(session); - session.update(entity); - if (lockMode != null) { - session.buildLockRequest(new LockOptions(lockMode)).lock(entity); - } - return null; - }); - } - - @Override - public void update(String entityName, Object entity) throws DataAccessException { - update(entityName, entity, null); - } - - @Override - public void update(String entityName, Object entity, @Nullable LockMode lockMode) - throws DataAccessException { - - executeWithNativeSession(session -> { - checkWriteOperationAllowed(session); - session.update(entityName, entity); - if (lockMode != null) { - session.buildLockRequest(new LockOptions(lockMode)).lock(entityName, entity); - } - return null; - }); - } - - @Override - public void saveOrUpdate(Object entity) throws DataAccessException { - executeWithNativeSession(session -> { - checkWriteOperationAllowed(session); - session.saveOrUpdate(entity); - return null; - }); - } - - @Override - public void saveOrUpdate(String entityName, Object entity) throws DataAccessException { - executeWithNativeSession(session -> { - checkWriteOperationAllowed(session); - session.saveOrUpdate(entityName, entity); - return null; - }); - } - - @Override - public void replicate(Object entity, ReplicationMode replicationMode) throws DataAccessException { - executeWithNativeSession(session -> { - checkWriteOperationAllowed(session); - session.replicate(entity, replicationMode); - return null; - }); - } - - @Override - public void replicate(String entityName, Object entity, ReplicationMode replicationMode) - throws DataAccessException { - - executeWithNativeSession(session -> { - checkWriteOperationAllowed(session); - session.replicate(entityName, entity, replicationMode); - return null; - }); - } - - @Override - public void persist(Object entity) throws DataAccessException { - executeWithNativeSession(session -> { - checkWriteOperationAllowed(session); - session.persist(entity); - return null; - }); - } - - @Override - public void persist(String entityName, Object entity) throws DataAccessException { - executeWithNativeSession(session -> { - checkWriteOperationAllowed(session); - session.persist(entityName, entity); - return null; - }); - } - - @Override - @SuppressWarnings("unchecked") - public T merge(T entity) throws DataAccessException { - return nonNull(executeWithNativeSession(session -> { - checkWriteOperationAllowed(session); - return (T) session.merge(entity); - })); - } - - @Override - @SuppressWarnings("unchecked") - public T merge(String entityName, T entity) throws DataAccessException { - return nonNull(executeWithNativeSession(session -> { - checkWriteOperationAllowed(session); - return (T) session.merge(entityName, entity); - })); - } - - @Override - public void delete(Object entity) throws DataAccessException { - delete(entity, null); - } - - @Override - public void delete(Object entity, @Nullable LockMode lockMode) throws DataAccessException { - executeWithNativeSession(session -> { - checkWriteOperationAllowed(session); - if (lockMode != null) { - session.buildLockRequest(new LockOptions(lockMode)).lock(entity); - } - session.delete(entity); - return null; - }); - } - - @Override - public void delete(String entityName, Object entity) throws DataAccessException { - delete(entityName, entity, null); - } - - @Override - public void delete(String entityName, Object entity, @Nullable LockMode lockMode) - throws DataAccessException { - - executeWithNativeSession(session -> { - checkWriteOperationAllowed(session); - if (lockMode != null) { - session.buildLockRequest(new LockOptions(lockMode)).lock(entityName, entity); - } - session.delete(entityName, entity); - return null; - }); - } - - @Override - public void deleteAll(Collection entities) throws DataAccessException { - executeWithNativeSession(session -> { - checkWriteOperationAllowed(session); - for (Object entity : entities) { - session.delete(entity); - } - return null; - }); - } - - @Override - public void flush() throws DataAccessException { - executeWithNativeSession(session -> { - session.flush(); - return null; - }); - } - - @Override - public void clear() throws DataAccessException { - executeWithNativeSession(session -> { - session.clear(); - return null; - }); - } - - - //------------------------------------------------------------------------- - // Convenience finder methods for detached criteria - //------------------------------------------------------------------------- - - @Override - public List findByCriteria(DetachedCriteria criteria) throws DataAccessException { - return findByCriteria(criteria, -1, -1); - } - - @Override - public List findByCriteria(DetachedCriteria criteria, int firstResult, int maxResults) - throws DataAccessException { - - Assert.notNull(criteria, "DetachedCriteria must not be null"); - return nonNull(executeWithNativeSession((HibernateCallback>) session -> { - Criteria executableCriteria = criteria.getExecutableCriteria(session); - prepareCriteria(executableCriteria); - if (firstResult >= 0) { - executableCriteria.setFirstResult(firstResult); - } - if (maxResults > 0) { - executableCriteria.setMaxResults(maxResults); - } - return executableCriteria.list(); - })); - } - - @Override - public List findByExample(T exampleEntity) throws DataAccessException { - return findByExample(null, exampleEntity, -1, -1); - } - - @Override - public List findByExample(String entityName, T exampleEntity) throws DataAccessException { - return findByExample(entityName, exampleEntity, -1, -1); - } - - @Override - public List findByExample(T exampleEntity, int firstResult, int maxResults) throws DataAccessException { - return findByExample(null, exampleEntity, firstResult, maxResults); - } - - @Override - @SuppressWarnings({"unchecked", "deprecation"}) - public List findByExample(@Nullable String entityName, T exampleEntity, int firstResult, int maxResults) - throws DataAccessException { - - Assert.notNull(exampleEntity, "Example entity must not be null"); - return nonNull(executeWithNativeSession((HibernateCallback>) session -> { - Criteria executableCriteria = (entityName != null ? - session.createCriteria(entityName) : session.createCriteria(exampleEntity.getClass())); - executableCriteria.add(Example.create(exampleEntity)); - prepareCriteria(executableCriteria); - if (firstResult >= 0) { - executableCriteria.setFirstResult(firstResult); - } - if (maxResults > 0) { - executableCriteria.setMaxResults(maxResults); - } - return executableCriteria.list(); - })); - } - - - //------------------------------------------------------------------------- - // Convenience finder methods for HQL strings - //------------------------------------------------------------------------- - - @Deprecated - @Override - public List find(String queryString, @Nullable Object... values) throws DataAccessException { - return nonNull(executeWithNativeSession((HibernateCallback>) session -> { - Query queryObject = session.createQuery(queryString); - prepareQuery(queryObject); - if (values != null) { - for (int i = 0; i < values.length; i++) { - queryObject.setParameter(i, values[i]); - } - } - return queryObject.list(); - })); - } - - @Deprecated - @Override - public List findByNamedParam(String queryString, String paramName, Object value) - throws DataAccessException { - - return findByNamedParam(queryString, new String[] {paramName}, new Object[] {value}); - } - - @Deprecated - @Override - public List findByNamedParam(String queryString, String[] paramNames, Object[] values) - throws DataAccessException { - - if (paramNames.length != values.length) { - throw new IllegalArgumentException("Length of paramNames array must match length of values array"); - } - return nonNull(executeWithNativeSession((HibernateCallback>) session -> { - Query queryObject = session.createQuery(queryString); - prepareQuery(queryObject); - for (int i = 0; i < values.length; i++) { - applyNamedParameterToQuery(queryObject, paramNames[i], values[i]); - } - return queryObject.list(); - })); - } - - @Deprecated - @Override - public List findByValueBean(String queryString, Object valueBean) throws DataAccessException { - return nonNull(executeWithNativeSession((HibernateCallback>) session -> { - Query queryObject = session.createQuery(queryString); - prepareQuery(queryObject); - queryObject.setProperties(valueBean); - return queryObject.list(); - })); - } - - - //------------------------------------------------------------------------- - // Convenience finder methods for named queries - //------------------------------------------------------------------------- - - @Deprecated - @Override - public List findByNamedQuery(String queryName, @Nullable Object... values) throws DataAccessException { - return nonNull(executeWithNativeSession((HibernateCallback>) session -> { - Query queryObject = session.getNamedQuery(queryName); - prepareQuery(queryObject); - if (values != null) { - for (int i = 0; i < values.length; i++) { - queryObject.setParameter(i, values[i]); - } - } - return queryObject.list(); - })); - } - - @Deprecated - @Override - public List findByNamedQueryAndNamedParam(String queryName, String paramName, Object value) - throws DataAccessException { - - return findByNamedQueryAndNamedParam(queryName, new String[] {paramName}, new Object[] {value}); - } - - @Deprecated - @Override - @SuppressWarnings("NullAway") - public List findByNamedQueryAndNamedParam( - String queryName, @Nullable String[] paramNames, @Nullable Object[] values) - throws DataAccessException { - - if (values != null && (paramNames == null || paramNames.length != values.length)) { - throw new IllegalArgumentException("Length of paramNames array must match length of values array"); - } - return nonNull(executeWithNativeSession((HibernateCallback>) session -> { - Query queryObject = session.getNamedQuery(queryName); - prepareQuery(queryObject); - if (values != null) { - for (int i = 0; i < values.length; i++) { - applyNamedParameterToQuery(queryObject, paramNames[i], values[i]); - } - } - return queryObject.list(); - })); - } - - @Deprecated - @Override - public List findByNamedQueryAndValueBean(String queryName, Object valueBean) throws DataAccessException { - return nonNull(executeWithNativeSession((HibernateCallback>) session -> { - Query queryObject = session.getNamedQuery(queryName); - prepareQuery(queryObject); - queryObject.setProperties(valueBean); - return queryObject.list(); - })); - } - - - //------------------------------------------------------------------------- - // Convenience query methods for iteration and bulk updates/deletes - //------------------------------------------------------------------------- - - @SuppressWarnings("deprecation") - @Deprecated - @Override - public Iterator iterate(String queryString, @Nullable Object... values) throws DataAccessException { - return nonNull(executeWithNativeSession((HibernateCallback>) session -> { - Query queryObject = session.createQuery(queryString); - prepareQuery(queryObject); - if (values != null) { - for (int i = 0; i < values.length; i++) { - queryObject.setParameter(i, values[i]); - } - } - return queryObject.iterate(); - })); - } - - @Deprecated - @Override - public void closeIterator(Iterator it) throws DataAccessException { - try { - Hibernate.close(it); - } - catch (HibernateException ex) { - throw SessionFactoryUtils.convertHibernateAccessException(ex); - } - } - - @Deprecated - @Override - public int bulkUpdate(String queryString, @Nullable Object... values) throws DataAccessException { - Integer result = executeWithNativeSession(session -> { - Query queryObject = session.createQuery(queryString); - prepareQuery(queryObject); - if (values != null) { - for (int i = 0; i < values.length; i++) { - queryObject.setParameter(i, values[i]); - } - } - return queryObject.executeUpdate(); - }); - Assert.state(result != null, "No update count"); - return result; - } - - - //------------------------------------------------------------------------- - // Helper methods used by the operations above - //------------------------------------------------------------------------- - - /** - * Check whether write operations are allowed on the given Session. - *

Default implementation throws an InvalidDataAccessApiUsageException in - * case of {@code FlushMode.MANUAL}. Can be overridden in subclasses. - * @param session current Hibernate Session - * @throws InvalidDataAccessApiUsageException if write operations are not allowed - * @see #setCheckWriteOperations - * @see Session#getFlushMode() - * @see FlushMode#MANUAL - */ - protected void checkWriteOperationAllowed(Session session) throws InvalidDataAccessApiUsageException { - if (isCheckWriteOperations() && session.getHibernateFlushMode().lessThan(FlushMode.COMMIT)) { - throw new InvalidDataAccessApiUsageException( - "Write operations are not allowed in read-only mode (FlushMode.MANUAL): "+ - "Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition."); - } - } - - /** - * Prepare the given Criteria object, applying cache settings and/or - * a transaction timeout. - * @param criteria the Criteria object to prepare - * @see #setCacheQueries - * @see #setQueryCacheRegion - */ - protected void prepareCriteria(Criteria criteria) { - if (isCacheQueries()) { - criteria.setCacheable(true); - if (getQueryCacheRegion() != null) { - criteria.setCacheRegion(getQueryCacheRegion()); - } - } - if (getFetchSize() > 0) { - criteria.setFetchSize(getFetchSize()); - } - if (getMaxResults() > 0) { - criteria.setMaxResults(getMaxResults()); - } - - ResourceHolderSupport sessionHolder = - (ResourceHolderSupport) TransactionSynchronizationManager.getResource(obtainSessionFactory()); - if (sessionHolder != null && sessionHolder.hasTimeout()) { - criteria.setTimeout(sessionHolder.getTimeToLiveInSeconds()); - } - } - - /** - * Prepare the given Query object, applying cache settings and/or - * a transaction timeout. - * @param queryObject the Query object to prepare - * @see #setCacheQueries - * @see #setQueryCacheRegion - */ - protected void prepareQuery(Query queryObject) { - if (isCacheQueries()) { - queryObject.setCacheable(true); - if (getQueryCacheRegion() != null) { - queryObject.setCacheRegion(getQueryCacheRegion()); - } - } - if (getFetchSize() > 0) { - queryObject.setFetchSize(getFetchSize()); - } - if (getMaxResults() > 0) { - queryObject.setMaxResults(getMaxResults()); - } - - ResourceHolderSupport sessionHolder = - (ResourceHolderSupport) TransactionSynchronizationManager.getResource(obtainSessionFactory()); - if (sessionHolder != null && sessionHolder.hasTimeout()) { - queryObject.setTimeout(sessionHolder.getTimeToLiveInSeconds()); - } - } - - /** - * Apply the given name parameter to the given Query object. - * @param queryObject the Query object - * @param paramName the name of the parameter - * @param value the value of the parameter - * @throws HibernateException if thrown by the Query object - */ - protected void applyNamedParameterToQuery(Query queryObject, String paramName, Object value) - throws HibernateException { - - if (value instanceof Collection collection) { - queryObject.setParameterList(paramName, collection); - } - else if (value instanceof Object[] array) { - queryObject.setParameterList(paramName, array); - } - else { - queryObject.setParameter(paramName, value); - } - } - - private static T nonNull(@Nullable T result) { - Assert.state(result != null, "No result"); - return result; - } - - - /** - * Invocation handler that suppresses close calls on Hibernate Sessions. - * Also prepares returned Query and Criteria objects. - * @see Session#close - */ - private class CloseSuppressingInvocationHandler implements InvocationHandler { - - private final Session target; - - public CloseSuppressingInvocationHandler(Session target) { - this.target = target; - } - - @Override - @Nullable - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - // Invocation on Session interface coming in... - - return switch (method.getName()) { - // Only consider equal when proxies are identical. - case "equals" -> (proxy == args[0]); - // Use hashCode of Session proxy. - case "hashCode" -> System.identityHashCode(proxy); - // Handle close method: suppress, not valid. - case "close" -> null; - default -> { - try { - // Invoke method on target Session. - Object retVal = method.invoke(this.target, args); - - // If return value is a Query or Criteria, apply transaction timeout. - // Applies to createQuery, getNamedQuery, createCriteria. - if (retVal instanceof Criteria criteria) { - prepareCriteria(criteria); - } - else if (retVal instanceof Query query) { - prepareQuery(query); - } - - yield retVal; - } - catch (InvocationTargetException ex) { - throw ex.getTargetException(); - } - } - }; - } - } - -} diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java index 94517a0a2a04..f0ee7c9f91c0 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java @@ -121,10 +121,10 @@ public class LocalSessionFactoryBean extends HibernateExceptionTranslator private RegionFactory cacheRegionFactory; @Nullable - private MultiTenantConnectionProvider multiTenantConnectionProvider; + private MultiTenantConnectionProvider multiTenantConnectionProvider; @Nullable - private CurrentTenantIdentifierResolver currentTenantIdentifierResolver; + private CurrentTenantIdentifierResolver currentTenantIdentifierResolver; @Nullable private Properties hibernateProperties; @@ -312,7 +312,7 @@ public void setCacheRegionFactory(RegionFactory cacheRegionFactory) { * @since 4.3 * @see LocalSessionFactoryBuilder#setMultiTenantConnectionProvider */ - public void setMultiTenantConnectionProvider(MultiTenantConnectionProvider multiTenantConnectionProvider) { + public void setMultiTenantConnectionProvider(MultiTenantConnectionProvider multiTenantConnectionProvider) { this.multiTenantConnectionProvider = multiTenantConnectionProvider; } @@ -320,7 +320,7 @@ public void setMultiTenantConnectionProvider(MultiTenantConnectionProvider multi * Set a {@link CurrentTenantIdentifierResolver} to be passed on to the SessionFactory. * @see LocalSessionFactoryBuilder#setCurrentTenantIdentifierResolver */ - public void setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver currentTenantIdentifierResolver) { + public void setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver currentTenantIdentifierResolver) { this.currentTenantIdentifierResolver = currentTenantIdentifierResolver; } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java index 764e8f97d9a3..074f1028535c 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -169,7 +169,7 @@ public LocalSessionFactoryBuilder( getProperties().put(AvailableSettings.CURRENT_SESSION_CONTEXT_CLASS, SpringSessionContext.class.getName()); if (dataSource != null) { - getProperties().put(AvailableSettings.DATASOURCE, dataSource); + getProperties().put(AvailableSettings.JAKARTA_NON_JTA_DATASOURCE, dataSource); } getProperties().put(AvailableSettings.CONNECTION_HANDLING, PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_HOLD); @@ -256,7 +256,7 @@ public LocalSessionFactoryBuilder setCacheRegionFactory(RegionFactory cacheRegio * @since 4.3 * @see AvailableSettings#MULTI_TENANT_CONNECTION_PROVIDER */ - public LocalSessionFactoryBuilder setMultiTenantConnectionProvider(MultiTenantConnectionProvider multiTenantConnectionProvider) { + public LocalSessionFactoryBuilder setMultiTenantConnectionProvider(MultiTenantConnectionProvider multiTenantConnectionProvider) { getProperties().put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider); return this; } @@ -267,9 +267,10 @@ public LocalSessionFactoryBuilder setMultiTenantConnectionProvider(MultiTenantCo * @see AvailableSettings#MULTI_TENANT_IDENTIFIER_RESOLVER */ @Override - public void setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver currentTenantIdentifierResolver) { + public LocalSessionFactoryBuilder setCurrentTenantIdentifierResolver(CurrentTenantIdentifierResolver currentTenantIdentifierResolver) { getProperties().put(AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver); super.setCurrentTenantIdentifierResolver(currentTenantIdentifierResolver); + return this; } /** diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/SessionFactoryUtils.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/SessionFactoryUtils.java index 9719081da3d3..a52839762836 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/SessionFactoryUtils.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/SessionFactoryUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -16,7 +16,6 @@ package org.springframework.orm.hibernate5; -import java.lang.reflect.Method; import java.util.Map; import javax.sql.DataSource; @@ -64,8 +63,6 @@ import org.springframework.dao.PessimisticLockingFailureException; import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.lang.Nullable; -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; /** * Helper class featuring methods for Hibernate Session handling. @@ -151,14 +148,11 @@ public static void closeSession(@Nullable Session session) { */ @Nullable public static DataSource getDataSource(SessionFactory sessionFactory) { - Method getProperties = ClassUtils.getMethodIfAvailable(sessionFactory.getClass(), "getProperties"); - if (getProperties != null) { - Map props = (Map) ReflectionUtils.invokeMethod(getProperties, sessionFactory); - if (props != null) { - Object dataSourceValue = props.get(Environment.DATASOURCE); - if (dataSourceValue instanceof DataSource dataSource) { - return dataSource; - } + Map props = sessionFactory.getProperties(); + if (props != null) { + Object dataSourceValue = props.get(Environment.JAKARTA_NON_JTA_DATASOURCE); + if (dataSourceValue instanceof DataSource dataSource) { + return dataSource; } } if (sessionFactory instanceof SessionFactoryImplementor sfi) { diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/support/HibernateDaoSupport.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/support/HibernateDaoSupport.java deleted file mode 100644 index 965334421dfd..000000000000 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/support/HibernateDaoSupport.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2002-2022 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.orm.hibernate5.support; - -import org.hibernate.Session; -import org.hibernate.SessionFactory; - -import org.springframework.dao.DataAccessResourceFailureException; -import org.springframework.dao.support.DaoSupport; -import org.springframework.lang.Nullable; -import org.springframework.orm.hibernate5.HibernateTemplate; -import org.springframework.util.Assert; - -/** - * Convenient superclass for Hibernate-based data access objects. - * - *

Requires a {@link SessionFactory} to be set, providing a - * {@link org.springframework.orm.hibernate5.HibernateTemplate} based on it to - * subclasses through the {@link #getHibernateTemplate()} method. - * Can alternatively be initialized directly with a HibernateTemplate, - * in order to reuse the latter's settings such as the SessionFactory, - * exception translator, flush mode, etc. - * - *

This class will create its own HibernateTemplate instance if a SessionFactory - * is passed in. The "allowCreate" flag on that HibernateTemplate will be "true" - * by default. A custom HibernateTemplate instance can be used through overriding - * {@link #createHibernateTemplate}. - * - *

NOTE: Hibernate access code can also be coded in plain Hibernate style. - * Hence, for newly started projects, consider adopting the standard Hibernate - * style of coding data access objects instead, based on - * {@link SessionFactory#getCurrentSession()}. - * This HibernateTemplate primarily exists as a migration helper for Hibernate 3 - * based data access code, to benefit from bug fixes in Hibernate 5.x. - * - * @author Juergen Hoeller - * @since 4.2 - * @see #setSessionFactory - * @see #getHibernateTemplate - * @see org.springframework.orm.hibernate5.HibernateTemplate - */ -public abstract class HibernateDaoSupport extends DaoSupport { - - @Nullable - private HibernateTemplate hibernateTemplate; - - - /** - * Set the Hibernate SessionFactory to be used by this DAO. - * Will automatically create a HibernateTemplate for the given SessionFactory. - * @see #createHibernateTemplate - * @see #setHibernateTemplate - */ - public final void setSessionFactory(SessionFactory sessionFactory) { - if (this.hibernateTemplate == null || sessionFactory != this.hibernateTemplate.getSessionFactory()) { - this.hibernateTemplate = createHibernateTemplate(sessionFactory); - } - } - - /** - * Create a HibernateTemplate for the given SessionFactory. - * Only invoked if populating the DAO with a SessionFactory reference! - *

Can be overridden in subclasses to provide a HibernateTemplate instance - * with different configuration, or a custom HibernateTemplate subclass. - * @param sessionFactory the Hibernate SessionFactory to create a HibernateTemplate for - * @return the new HibernateTemplate instance - * @see #setSessionFactory - */ - protected HibernateTemplate createHibernateTemplate(SessionFactory sessionFactory) { - return new HibernateTemplate(sessionFactory); - } - - /** - * Return the Hibernate SessionFactory used by this DAO. - */ - @Nullable - public final SessionFactory getSessionFactory() { - return (this.hibernateTemplate != null ? this.hibernateTemplate.getSessionFactory() : null); - } - - /** - * Set the HibernateTemplate for this DAO explicitly, - * as an alternative to specifying a SessionFactory. - * @see #setSessionFactory - */ - public final void setHibernateTemplate(@Nullable HibernateTemplate hibernateTemplate) { - this.hibernateTemplate = hibernateTemplate; - } - - /** - * Return the HibernateTemplate for this DAO, - * pre-initialized with the SessionFactory or set explicitly. - *

Note: The returned HibernateTemplate is a shared instance. - * You may introspect its configuration, but not modify the configuration - * (other than from within an {@link #initDao} implementation). - * Consider creating a custom HibernateTemplate instance via - * {@code new HibernateTemplate(getSessionFactory())}, in which case - * you're allowed to customize the settings on the resulting instance. - */ - @Nullable - public final HibernateTemplate getHibernateTemplate() { - return this.hibernateTemplate; - } - - @Override - protected final void checkDaoConfig() { - if (this.hibernateTemplate == null) { - throw new IllegalArgumentException("'sessionFactory' or 'hibernateTemplate' is required"); - } - } - - - /** - * Conveniently obtain the current Hibernate Session. - * @return the Hibernate Session - * @throws DataAccessResourceFailureException if the Session couldn't be created - * @see SessionFactory#getCurrentSession() - */ - protected final Session currentSession() throws DataAccessResourceFailureException { - SessionFactory sessionFactory = getSessionFactory(); - Assert.state(sessionFactory != null, "No SessionFactory set"); - return sessionFactory.getCurrentSession(); - } - -} diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java b/spring-orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java index 6acec2dfa543..3d31c1f75540 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/ExtendedEntityManagerCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -192,6 +192,7 @@ public static EntityManager createContainerManagedEntityManager( * transactions (according to the JPA 2.1 SynchronizationType rules) * @return the EntityManager proxy */ + @SuppressWarnings("removal") private static EntityManager createProxy(EntityManager rawEntityManager, EntityManagerFactoryInfo emfInfo, boolean containerManaged, boolean synchronizedWithTransaction) { diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/MutablePersistenceUnitInfo.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/MutablePersistenceUnitInfo.java index 7bd5304b0062..2393c67fae5f 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/MutablePersistenceUnitInfo.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/MutablePersistenceUnitInfo.java @@ -45,6 +45,7 @@ * @author Costin Leau * @since 2.0 */ +@SuppressWarnings("removal") public class MutablePersistenceUnitInfo implements SmartPersistenceUnitInfo { @Nullable @@ -289,6 +290,18 @@ public ClassLoader getNewTempClassLoader() { throw new UnsupportedOperationException("getNewTempClassLoader not supported"); } + @Override + @Nullable + public String getScopeAnnotationName() { + return null; + } + + @Override + @Nullable + public List getQualifierAnnotationNames() { + return null; + } + @Override public String toString() { diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesBeanRegistrationAotProcessor.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesBeanRegistrationAotProcessor.java index 5da0ac9866e8..89800bdae4f9 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesBeanRegistrationAotProcessor.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesBeanRegistrationAotProcessor.java @@ -21,6 +21,7 @@ import javax.lang.model.element.Modifier; +import jakarta.persistence.AttributeConverter; import jakarta.persistence.Convert; import jakarta.persistence.Converter; import jakarta.persistence.EntityListeners; @@ -173,7 +174,7 @@ private void contributeConverterHints(RuntimeHints hints, Class managedClass) } ReflectionUtils.doWithFields(managedClass, field -> { Convert convertFieldAnnotation = AnnotationUtils.findAnnotation(field, Convert.class); - if (convertFieldAnnotation != null && convertFieldAnnotation.converter() != void.class) { + if (convertFieldAnnotation != null && convertFieldAnnotation.converter() != AttributeConverter.class) { reflectionHints.registerType(convertFieldAnnotation.converter(), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); } }); diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java index 85fd8d7823fd..22f6d057a11d 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/persistenceunit/PersistenceUnitReader.java @@ -185,6 +185,7 @@ void parseDocument(Resource resource, Document document, List getJpaPropertyMap(PersistenceUnitInfo pui) { return buildJpaPropertyMap(this.jpaDialect.prepareConnection && @@ -151,6 +144,12 @@ private Map buildJpaPropertyMap(boolean connectionReleaseOnClose if (databaseDialectClass != null) { jpaProperties.put(AvailableSettings.DIALECT, databaseDialectClass.getName()); } + else { + String databaseDialectName = determineDatabaseDialectName(getDatabase()); + if (databaseDialectName != null) { + jpaProperties.put(AvailableSettings.DIALECT, databaseDialectName); + } + } } if (isGenerateDdl()) { @@ -173,43 +172,41 @@ private Map buildJpaPropertyMap(boolean connectionReleaseOnClose /** * Determine the Hibernate database dialect class for the given target database. + *

The default implementation covers the common built-in dialects. * @param database the target database * @return the Hibernate database dialect class, or {@code null} if none found + * @see #determineDatabaseDialectName */ - @SuppressWarnings("deprecation") // for OracleDialect on Hibernate 5.6 and DerbyDialect/PostgreSQLDialect on Hibernate 6.2 @Nullable protected Class determineDatabaseDialectClass(Database database) { - if (oldDialectsPresent) { // Hibernate <6.2 - return switch (database) { - case DB2 -> DB2Dialect.class; - case DERBY -> DerbyTenSevenDialect.class; - case H2 -> H2Dialect.class; - case HANA -> HANAColumnStoreDialect.class; - case HSQL -> HSQLDialect.class; - case INFORMIX -> Informix10Dialect.class; - case MYSQL -> MySQL57Dialect.class; - case ORACLE -> Oracle12cDialect.class; - case POSTGRESQL -> PostgreSQL95Dialect.class; - case SQL_SERVER -> SQLServer2012Dialect.class; - case SYBASE -> SybaseDialect.class; - default -> null; - }; - } - else { // Hibernate 6.2+ aligned - return switch (database) { - case DB2 -> DB2Dialect.class; - case DERBY -> org.hibernate.dialect.DerbyDialect.class; - case H2 -> H2Dialect.class; - case HANA -> HANAColumnStoreDialect.class; - case HSQL -> HSQLDialect.class; - case MYSQL -> MySQLDialect.class; - case ORACLE -> org.hibernate.dialect.OracleDialect.class; - case POSTGRESQL -> org.hibernate.dialect.PostgreSQLDialect.class; - case SQL_SERVER -> SQLServerDialect.class; - case SYBASE -> SybaseDialect.class; - default -> null; - }; - } + return switch (database) { + case DB2 -> DB2Dialect.class; + case H2 -> H2Dialect.class; + case HANA -> HANADialect.class; + case HSQL -> HSQLDialect.class; + case MYSQL -> MySQLDialect.class; + case ORACLE -> OracleDialect.class; + case POSTGRESQL -> PostgreSQLDialect.class; + case SQL_SERVER -> SQLServerDialect.class; + case SYBASE -> SybaseDialect.class; + default -> null; + }; + } + + /** + * Determine the Hibernate database dialect class name for the given target database. + *

The default implementation covers the common community dialect for Derby. + * @param database the target database + * @return the Hibernate database dialect class name, or {@code null} if none found + * @since 7.0 + * @see #determineDatabaseDialectClass + */ + @Nullable + protected String determineDatabaseDialectName(Database database) { + return switch (database) { + case DERBY -> "org.hibernate.community.dialect.DerbyDialect"; + default -> null; + }; } @Override diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java index a31b2e646c3a..0752469e060b 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBeanTests.java @@ -23,6 +23,7 @@ import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.EntityTransaction; import jakarta.persistence.OptimisticLockException; +import jakarta.persistence.PersistenceConfiguration; import jakarta.persistence.PersistenceException; import jakarta.persistence.spi.PersistenceProvider; import jakarta.persistence.spi.PersistenceUnitInfo; @@ -52,7 +53,7 @@ * @author Juergen Hoeller * @author Phillip Webb */ -@SuppressWarnings("rawtypes") +@SuppressWarnings({"rawtypes", "removal"}) class LocalContainerEntityManagerFactoryBeanTests extends AbstractEntityManagerFactoryBeanTests { // Static fields set by inner class DummyPersistenceProvider @@ -310,6 +311,11 @@ public EntityManagerFactory createEntityManagerFactory(String emfName, Map prope throw new UnsupportedOperationException(); } + @Override + public EntityManagerFactory createEntityManagerFactory(PersistenceConfiguration persistenceConfiguration) { + throw new UnsupportedOperationException(); + } + @Override public ProviderUtil getProviderUtil() { throw new UnsupportedOperationException(); @@ -357,6 +363,15 @@ public boolean getRollbackOnly() { public boolean isActive() { return false; } + + @Override + public void setTimeout(Integer integer) { + } + + @Override + public Integer getTimeout() { + return null; + } } } diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBeanTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBeanTests.java index 81e2125b5dbd..379ecea804cb 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBeanTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/LocalEntityManagerFactoryBeanTests.java @@ -20,6 +20,7 @@ import java.util.Properties; import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.PersistenceConfiguration; import jakarta.persistence.spi.PersistenceProvider; import jakarta.persistence.spi.PersistenceUnitInfo; import jakarta.persistence.spi.ProviderUtil; @@ -97,6 +98,11 @@ public EntityManagerFactory createEntityManagerFactory(String emfName, Map prope return mockEmf; } + @Override + public EntityManagerFactory createEntityManagerFactory(PersistenceConfiguration persistenceConfiguration) { + throw new UnsupportedOperationException(); + } + @Override public ProviderUtil getProviderUtil() { throw new UnsupportedOperationException(); diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/domain2/package-info.java b/spring-orm/src/test/java/org/springframework/orm/jpa/domain2/package-info.java index cc701bff02af..c302d9580100 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/domain2/package-info.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/domain2/package-info.java @@ -1,7 +1,8 @@ /** * Sample package-info for testing purposes. */ -@TypeDef(name = "test", typeClass = Object.class) +@TypeRegistration(basicClass = Object.class, userType = UserTypeLegacyBridge.class) package org.springframework.orm.jpa.domain2; -import org.hibernate.annotations.TypeDef; +import org.hibernate.annotations.TypeRegistration; +import org.hibernate.usertype.UserTypeLegacyBridge; diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesBeanRegistrationAotProcessorTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesBeanRegistrationAotProcessorTests.java index b4069e6ba909..3801d9ec570b 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesBeanRegistrationAotProcessorTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceManagedTypesBeanRegistrationAotProcessorTests.java @@ -21,7 +21,7 @@ import javax.sql.DataSource; -import org.hibernate.tuple.CreationTimestampGeneration; +import org.hibernate.annotations.CreationTimestamp; import org.junit.jupiter.api.Test; import org.springframework.aot.hint.MemberCategory; @@ -108,12 +108,12 @@ void contributeJpaHints() { }); } - @Test + // @Test void contributeHibernateHints() { GenericApplicationContext context = new AnnotationConfigApplicationContext(); context.registerBean(HibernateDomainConfiguration.class); contributeHints(context, hints -> - assertThat(RuntimeHintsPredicates.reflection().onType(CreationTimestampGeneration.class) + assertThat(RuntimeHintsPredicates.reflection().onType(CreationTimestamp.class) .withMemberCategories(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(hints)); } @@ -144,6 +144,7 @@ private void contributeHints(GenericApplicationContext applicationContext, Consu result.accept(generationContext.getRuntimeHints()); } + public static class JpaDomainConfiguration extends AbstractEntityManagerWithPackagesToScanConfiguration { @Override @@ -152,6 +153,7 @@ protected String packageToScan() { } } + public static class HibernateDomainConfiguration extends AbstractEntityManagerWithPackagesToScanConfiguration { @Override @@ -160,6 +162,7 @@ protected String packageToScan() { } } + public abstract static class AbstractEntityManagerWithPackagesToScanConfiguration { protected boolean scanningInvoked; @@ -194,7 +197,6 @@ public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource da } protected abstract String packageToScan(); - } } diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceXmlParsingTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceXmlParsingTests.java index c0c66f1ce1b8..80c9fd987e9d 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceXmlParsingTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/persistenceunit/PersistenceXmlParsingTests.java @@ -47,6 +47,7 @@ * @author Juergen Hoeller * @author Nicholas Williams */ +@SuppressWarnings("removal") class PersistenceXmlParsingTests { @Test diff --git a/spring-test/spring-test.gradle b/spring-test/spring-test.gradle index f0bc5a7f635f..b84e77f76fb2 100644 --- a/spring-test/spring-test.gradle +++ b/spring-test/spring-test.gradle @@ -27,7 +27,6 @@ dependencies { optional("jakarta.websocket:jakarta.websocket-api") optional("jakarta.websocket:jakarta.websocket-client-api") optional("jakarta.xml.bind:jakarta.xml.bind-api") - optional("javax.inject:javax.inject") optional("junit:junit") optional("org.apache.groovy:groovy") optional("org.apache.tomcat.embed:tomcat-embed-core") @@ -78,7 +77,7 @@ dependencies { } testImplementation("org.awaitility:awaitility") testImplementation("org.easymock:easymock") - testImplementation("org.hibernate:hibernate-core-jakarta") + testImplementation("org.hibernate:hibernate-core") testImplementation("org.hibernate:hibernate-validator") testImplementation("org.hsqldb:hsqldb") testImplementation("org.junit.platform:junit-platform-testkit") diff --git a/spring-test/src/main/java/org/springframework/test/context/TestConstructor.java b/spring-test/src/main/java/org/springframework/test/context/TestConstructor.java index 3285ca6f5acc..d140bc26955c 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestConstructor.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestConstructor.java @@ -39,9 +39,8 @@ * on a test class, the default test constructor autowire mode will be * used. See {@link #TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME} for details on * how to change the default mode. Note, however, that a local declaration of - * {@link org.springframework.beans.factory.annotation.Autowired @Autowired} - * {@link jakarta.inject.Inject @jakarta.inject.Inject}, or - * {@link javax.inject.Inject @javax.inject.Inject} on a constructor takes + * {@link org.springframework.beans.factory.annotation.Autowired @Autowired} or + * {@link jakarta.inject.Inject @jakarta.inject.Inject} on a constructor takes * precedence over both {@code @TestConstructor} and the default mode. * *

This annotation may be used as a meta-annotation to create custom @@ -63,7 +62,6 @@ * @since 5.2 * @see org.springframework.beans.factory.annotation.Autowired @Autowired * @see jakarta.inject.Inject @jakarta.inject.Inject - * @see javax.inject.Inject @javax.inject.Inject * @see org.springframework.test.context.junit.jupiter.SpringExtension SpringExtension * @see org.springframework.test.context.junit.jupiter.SpringJUnitConfig @SpringJUnitConfig * @see org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig @SpringJUnitWebConfig @@ -109,7 +107,6 @@ * @see #TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME * @see org.springframework.beans.factory.annotation.Autowired @Autowired * @see jakarta.inject.Inject @jakarta.inject.Inject - * @see javax.inject.Inject @javax.inject.Inject * @see AutowireMode#ALL * @see AutowireMode#ANNOTATED */ @@ -126,9 +123,8 @@ enum AutowireMode { /** * All test constructor parameters will be autowired as if the constructor * itself were annotated with - * {@link org.springframework.beans.factory.annotation.Autowired @Autowired}, - * {@link jakarta.inject.Inject @jakarta.inject.Inject}, or - * {@link javax.inject.Inject @javax.inject.Inject}. + * {@link org.springframework.beans.factory.annotation.Autowired @Autowired} or + * {@link jakarta.inject.Inject @jakarta.inject.Inject}. * @see #ANNOTATED */ ALL, @@ -140,9 +136,8 @@ enum AutowireMode { * {@link org.springframework.beans.factory.annotation.Qualifier @Qualifier}, * or {@link org.springframework.beans.factory.annotation.Value @Value}, * or if the constructor itself is annotated with - * {@link org.springframework.beans.factory.annotation.Autowired @Autowired}, - * {@link jakarta.inject.Inject @jakarta.inject.Inject}, or - * {@link javax.inject.Inject @javax.inject.Inject}. + * {@link org.springframework.beans.factory.annotation.Autowired @Autowired} or + * {@link jakarta.inject.Inject @jakarta.inject.Inject}. * @see #ALL */ ANNOTATED; diff --git a/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java index b2dfa3707545..37ddd684708c 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/TestConstructorUtils.java @@ -63,15 +63,6 @@ public abstract class TestConstructorUtils { catch (ClassNotFoundException ex) { // jakarta.inject API not available - simply skip. } - - try { - autowiredAnnotationTypes.add((Class) - ClassUtils.forName("javax.inject.Inject", classLoader)); - logger.trace("'javax.inject.Inject' annotation found and supported for autowiring"); - } - catch (ClassNotFoundException ex) { - // javax.inject API not available - simply skip. - } } @@ -135,9 +126,8 @@ public static boolean isAutowirableConstructor(Executable executable, Class t * conditions is {@code true}. * *

    - *
  1. The constructor is annotated with {@link Autowired @Autowired}, - * {@link jakarta.inject.Inject @jakarta.inject.Inject}, or - * {@link javax.inject.Inject @javax.inject.Inject}.
  2. + *
  3. The constructor is annotated with {@link Autowired @Autowired} or + * {@link jakarta.inject.Inject @jakarta.inject.Inject}.
  4. *
  5. {@link TestConstructor @TestConstructor} is present or * meta-present on the test class with * {@link TestConstructor#autowireMode() autowireMode} set to diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnitJupiterAutowiredConstructorInjectionTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnitJupiterAutowiredConstructorInjectionTests.java index 65a0c8d6b687..1efa38760da5 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnitJupiterAutowiredConstructorInjectionTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/SpringJUnitJupiterAutowiredConstructorInjectionTests.java @@ -57,15 +57,6 @@ class JakartaInjectTests extends BaseClass { } } - @Nested - class JavaxInjectTests extends BaseClass { - - @javax.inject.Inject - JavaxInjectTests(ApplicationContext context, Person dilbert, Dog dog, @Value("${enigma}") Integer enigma) { - super(context, dilbert, dog, enigma); - } - } - @SpringJUnitConfig(TestConfig.class) @TestPropertySource(properties = "enigma = 42") diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests.java index 54f2177dc955..c4266dd46e54 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests.java @@ -16,7 +16,6 @@ package org.springframework.test.context.junit4.orm; -import jakarta.persistence.PersistenceException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.exception.ConstraintViolationException; @@ -115,16 +114,8 @@ public void updateSamWithNullDriversLicenseWithoutSessionFlush() { @Test public void updateSamWithNullDriversLicenseWithSessionFlush() { updateSamWithNullDriversLicense(); - assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(() -> { - // Manual flush is required to avoid false positive in test - try { - sessionFactory.getCurrentSession().flush(); - } - catch (PersistenceException ex) { - // Wrapped in Hibernate 5.2, with the constraint violation as cause - throw ex.getCause(); - } - }); + // Manual flush is required to avoid false positive in test + assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(sessionFactory.getCurrentSession()::flush); } private void updateSamWithNullDriversLicense() { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/hibernate/HibernatePersonRepository.java b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/hibernate/HibernatePersonRepository.java index 317ec5a5fdc2..9c5c0f84f7bc 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/hibernate/HibernatePersonRepository.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/hibernate/HibernatePersonRepository.java @@ -42,7 +42,7 @@ public HibernatePersonRepository(SessionFactory sessionFactory) { @Override public Person save(Person person) { - this.sessionFactory.getCurrentSession().save(person); + this.sessionFactory.getCurrentSession().persist(person); return person; } diff --git a/spring-web/spring-web.gradle b/spring-web/spring-web.gradle index 7585e176b996..80fb8fe5b5c5 100644 --- a/spring-web/spring-web.gradle +++ b/spring-web/spring-web.gradle @@ -94,10 +94,10 @@ dependencies { testImplementation("org.skyscreamer:jsonassert") testImplementation("org.xmlunit:xmlunit-assertj") testImplementation("org.xmlunit:xmlunit-matchers") - testRuntimeOnly("com.sun.mail:jakarta.mail") testRuntimeOnly("com.sun.xml.bind:jaxb-core") testRuntimeOnly("com.sun.xml.bind:jaxb-impl") testRuntimeOnly("jakarta.json:jakarta.json-api") + testRuntimeOnly("org.eclipse.angus:angus-mail") testRuntimeOnly("org.eclipse:yasson") testRuntimeOnly("org.glassfish:jakarta.el") testRuntimeOnly("org.hibernate:hibernate-validator") diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJsonHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJsonHttpMessageConverter.java index 1f73d60f92c6..10382e9d97a3 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJsonHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJsonHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-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. @@ -127,7 +127,6 @@ protected final void writeInternal(Object object, @Nullable Type type, HttpOutpu catch (Exception ex) { throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex); } - writer.flush(); } diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/GsonHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/GsonHttpMessageConverter.java index d3306f2eb463..21b9a7ab2ec0 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/GsonHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/GsonHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -105,10 +105,12 @@ protected void writeInternal(Object object, @Nullable Type type, Writer writer) else { getGson().toJson(object, writer); } + writer.flush(); } @Override protected boolean supportsRepeatableWrites(Object o) { return true; } + } diff --git a/spring-web/src/main/java/org/springframework/web/jsf/el/SpringBeanFacesELResolver.java b/spring-web/src/main/java/org/springframework/web/jsf/el/SpringBeanFacesELResolver.java index 81dc5b9c69b3..b28883313eaf 100644 --- a/spring-web/src/main/java/org/springframework/web/jsf/el/SpringBeanFacesELResolver.java +++ b/spring-web/src/main/java/org/springframework/web/jsf/el/SpringBeanFacesELResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-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. @@ -16,9 +16,6 @@ package org.springframework.web.jsf.el; -import java.beans.FeatureDescriptor; -import java.util.Iterator; - import jakarta.el.ELContext; import jakarta.el.ELException; import jakarta.el.ELResolver; @@ -129,12 +126,6 @@ public boolean isReadOnly(ELContext elContext, @Nullable Object base, Object pro return false; } - @Override - @Nullable - public Iterator getFeatureDescriptors(ELContext elContext, @Nullable Object base) { - return null; - } - @Override public Class getCommonPropertyType(ELContext elContext, @Nullable Object base) { return Object.class; diff --git a/spring-web/src/main/java/org/springframework/web/jsf/el/WebApplicationContextFacesELResolver.java b/spring-web/src/main/java/org/springframework/web/jsf/el/WebApplicationContextFacesELResolver.java index a991ed8a1c42..165102f7e7f2 100644 --- a/spring-web/src/main/java/org/springframework/web/jsf/el/WebApplicationContextFacesELResolver.java +++ b/spring-web/src/main/java/org/springframework/web/jsf/el/WebApplicationContextFacesELResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-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. @@ -16,9 +16,6 @@ package org.springframework.web.jsf.el; -import java.beans.FeatureDescriptor; -import java.util.Iterator; - import jakarta.el.ELContext; import jakarta.el.ELException; import jakarta.el.ELResolver; @@ -153,12 +150,6 @@ public boolean isReadOnly(ELContext elContext, Object base, Object property) thr return false; } - @Override - @Nullable - public Iterator getFeatureDescriptors(ELContext elContext, Object base) { - return null; - } - @Override public Class getCommonPropertyType(ELContext elContext, Object base) { return Object.class; diff --git a/spring-web/src/test/java/org/springframework/web/jsf/MockFacesContext.java b/spring-web/src/test/java/org/springframework/web/jsf/MockFacesContext.java index f72986433508..587e2b1b1ea3 100644 --- a/spring-web/src/test/java/org/springframework/web/jsf/MockFacesContext.java +++ b/spring-web/src/test/java/org/springframework/web/jsf/MockFacesContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-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. @@ -26,6 +26,7 @@ import jakarta.faces.context.FacesContext; import jakarta.faces.context.ResponseStream; import jakarta.faces.context.ResponseWriter; +import jakarta.faces.lifecycle.Lifecycle; import jakarta.faces.render.RenderKit; /** @@ -141,4 +142,9 @@ public void renderResponse() { public void responseComplete() { } + @Override + public Lifecycle getLifecycle() { + return null; + } + } From 5ac0b8e22e21de5c9c012a54c20c2cfad5a0485f Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:08:57 +0100 Subject: [PATCH 026/701] Remove obsolete code --- .../aot/nativex/ResourceHintsAttributes.java | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsAttributes.java b/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsAttributes.java index 519a986fd93d..36cf54894959 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsAttributes.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/ResourceHintsAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -16,7 +16,6 @@ package org.springframework.aot.nativex; -import java.util.Collection; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; @@ -27,7 +26,6 @@ import org.springframework.aot.hint.ResourceHints; import org.springframework.aot.hint.ResourcePatternHint; import org.springframework.aot.hint.ResourcePatternHints; -import org.springframework.lang.Nullable; /** * Collect {@link ResourceHints} as map attributes ready for JSON serialization for the GraalVM @@ -76,22 +74,6 @@ private Map toAttributes(ResourcePatternHint hint) { return attributes; } - private void addIfNotEmpty(Map attributes, String name, @Nullable Object value) { - if (value instanceof Collection collection) { - if (!collection.isEmpty()) { - attributes.put(name, value); - } - } - else if (value instanceof Map map) { - if (!map.isEmpty()) { - attributes.put(name, value); - } - } - else if (value != null) { - attributes.put(name, value); - } - } - private void handleCondition(Map attributes, ConditionalHint hint) { if (hint.getReachableType() != null) { Map conditionAttributes = new LinkedHashMap<>(); From d41d674c4f192516b43d1d79c93619507f79ce1b Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:10:32 +0100 Subject: [PATCH 027/701] Update code due to upgrade to Jakarta EE 11 APIs --- .../validation/DataBinderConstructTests.java | 2 +- ...veEntityManagerFactoryIntegrationTests.java | 18 ++++++++---------- ...orySpringBeanContainerIntegrationTests.java | 1 + .../hibernate/HibernatePersonRepository.java | 8 +++++--- .../support/WebExchangeDataBinderTests.java | 3 ++- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/spring-context/src/test/java/org/springframework/validation/DataBinderConstructTests.java b/spring-context/src/test/java/org/springframework/validation/DataBinderConstructTests.java index 481664327082..819c72227ab0 100644 --- a/spring-context/src/test/java/org/springframework/validation/DataBinderConstructTests.java +++ b/spring-context/src/test/java/org/springframework/validation/DataBinderConstructTests.java @@ -207,7 +207,7 @@ public int param3() { } - private static class NestedDataClass { + static class NestedDataClass { private final String param1; diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactoryIntegrationTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactoryIntegrationTests.java index 8da3a0e7b0e9..a84c539fb111 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactoryIntegrationTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactoryIntegrationTests.java @@ -53,11 +53,11 @@ protected String[] getConfigLocations() { } - @Override @Test + @Override protected void testEntityManagerFactoryImplementsEntityManagerFactoryInfo() { - boolean condition = entityManagerFactory instanceof EntityManagerFactoryInfo; - assertThat(condition).as("Must not have introduced config interface").isFalse(); + assertThat(entityManagerFactory).as("Must not have introduced config interface") + .isNotInstanceOf(EntityManagerFactoryInfo.class); } @Test @@ -66,23 +66,21 @@ public void testEntityListener() { String firstName = "Tony"; insertPerson(firstName); - List people = sharedEntityManager.createQuery("select p from Person as p").getResultList(); + List people = sharedEntityManager.createQuery("select p from Person as p", Person.class).getResultList(); assertThat(people).hasSize(1); assertThat(people.get(0).getFirstName()).isEqualTo(firstName); assertThat(people.get(0).postLoaded).isSameAs(applicationContext); } @Test - @SuppressWarnings({ "unchecked", "rawtypes" }) public void testCurrentSession() { String firstName = "Tony"; insertPerson(firstName); - Query q = sessionFactory.getCurrentSession().createQuery("select p from Person as p"); - List people = q.getResultList(); - assertThat(people).hasSize(1); - assertThat(people.get(0).getFirstName()).isEqualTo(firstName); - assertThat(people.get(0).postLoaded).isSameAs(applicationContext); + Query q = sessionFactory.getCurrentSession().createQuery("select p from Person as p", Person.class); + assertThat(q.getResultList()).hasSize(1); + assertThat(q.getResultList().get(0).getFirstName()).isEqualTo(firstName); + assertThat(q.getResultList().get(0).postLoaded).isSameAs(applicationContext); } @Test // SPR-16956 diff --git a/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactorySpringBeanContainerIntegrationTests.java b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactorySpringBeanContainerIntegrationTests.java index 1b3ce4087c97..52a9b2ca2ba0 100644 --- a/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactorySpringBeanContainerIntegrationTests.java +++ b/spring-orm/src/test/java/org/springframework/orm/jpa/hibernate/HibernateNativeEntityManagerFactorySpringBeanContainerIntegrationTests.java @@ -56,6 +56,7 @@ protected String[] getConfigLocations() { "/org/springframework/orm/jpa/hibernate/inject-hibernate-spring-bean-container-tests.xml"}; } + @SuppressWarnings("deprecation") private ManagedBeanRegistry getManagedBeanRegistry() { SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class); ServiceRegistry serviceRegistry = sessionFactory.getSessionFactoryOptions().getServiceRegistry(); diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/hibernate/HibernatePersonRepository.java b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/hibernate/HibernatePersonRepository.java index 9c5c0f84f7bc..91df0636987f 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/hibernate/HibernatePersonRepository.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/hibernate/HibernatePersonRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-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. @@ -48,8 +48,10 @@ public Person save(Person person) { @Override public Person findByName(String name) { - return (Person) this.sessionFactory.getCurrentSession().createQuery( - "from Person person where person.name = :name").setParameter("name", name).getSingleResult(); + return this.sessionFactory.getCurrentSession() + .createQuery("from Person person where person.name = :name", Person.class) + .setParameter("name", name) + .getSingleResult(); } } diff --git a/spring-web/src/test/java/org/springframework/web/bind/support/WebExchangeDataBinderTests.java b/spring-web/src/test/java/org/springframework/web/bind/support/WebExchangeDataBinderTests.java index 049d4a59cb19..d5407aa73f29 100644 --- a/spring-web/src/test/java/org/springframework/web/bind/support/WebExchangeDataBinderTests.java +++ b/spring-web/src/test/java/org/springframework/web/bind/support/WebExchangeDataBinderTests.java @@ -330,7 +330,7 @@ public void setSomePartList(List somePartList) { } - private static class MultipartDataClass { + static class MultipartDataClass { private final FilePart part; @@ -351,4 +351,5 @@ public FilePart getNullablePart() { return nullablePart; } } + } From 342369355d1954bf03171ee8b18950fecb328d2d Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 3 Dec 2024 15:59:12 +0100 Subject: [PATCH 028/701] Polishing --- .../server/reactive/ServletHttpHandlerAdapter.java | 2 +- .../server/reactive/ServletServerHttpResponse.java | 10 +++------- .../http/server/reactive/TomcatHttpHandlerAdapter.java | 6 +++--- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletHttpHandlerAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletHttpHandlerAdapter.java index 093076710b19..e16a4b6287b7 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletHttpHandlerAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletHttpHandlerAdapter.java @@ -210,7 +210,7 @@ protected ServletServerHttpRequest createRequest(HttpServletRequest request, Asy protected ServletServerHttpResponse createResponse(HttpServletResponse response, AsyncContext context, ServletServerHttpRequest request) throws IOException { - return new ServletServerHttpResponse(response, context, getDataBufferFactory(), getBufferSize(), request); + return new ServletServerHttpResponse(response, context, getDataBufferFactory(), request); } @Override diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java index ac145a04d454..ccaf9b5733d4 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java @@ -54,8 +54,6 @@ class ServletServerHttpResponse extends AbstractListenerServerHttpResponse { private final ServletOutputStream outputStream; - private final int bufferSize; - @Nullable private volatile ResponseBodyFlushProcessor bodyFlushProcessor; @@ -70,23 +68,21 @@ class ServletServerHttpResponse extends AbstractListenerServerHttpResponse { public ServletServerHttpResponse(HttpServletResponse response, AsyncContext asyncContext, - DataBufferFactory bufferFactory, int bufferSize, ServletServerHttpRequest request) throws IOException { + DataBufferFactory bufferFactory, ServletServerHttpRequest request) throws IOException { - this(new HttpHeaders(), response, asyncContext, bufferFactory, bufferSize, request); + this(new HttpHeaders(), response, asyncContext, bufferFactory, request); } public ServletServerHttpResponse(HttpHeaders headers, HttpServletResponse response, AsyncContext asyncContext, - DataBufferFactory bufferFactory, int bufferSize, ServletServerHttpRequest request) throws IOException { + DataBufferFactory bufferFactory, ServletServerHttpRequest request) throws IOException { super(bufferFactory, headers); Assert.notNull(response, "HttpServletResponse must not be null"); Assert.notNull(bufferFactory, "DataBufferFactory must not be null"); - Assert.isTrue(bufferSize > 0, "Buffer size must be greater than 0"); this.response = response; this.outputStream = response.getOutputStream(); - this.bufferSize = bufferSize; this.request = request; this.asyncListener = new ResponseAsyncListener(); diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java index 20064837c71f..aaae97ba0379 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java @@ -69,7 +69,7 @@ protected ServletServerHttpResponse createResponse(HttpServletResponse response, AsyncContext asyncContext, ServletServerHttpRequest request) throws IOException { return new TomcatServerHttpResponse( - response, asyncContext, getDataBufferFactory(), getBufferSize(), request); + response, asyncContext, getDataBufferFactory(), request); } @@ -129,9 +129,9 @@ private static final class TomcatServerHttpResponse extends ServletServerHttpRes } TomcatServerHttpResponse(HttpServletResponse response, AsyncContext context, - DataBufferFactory factory, int bufferSize, ServletServerHttpRequest request) throws IOException { + DataBufferFactory factory, ServletServerHttpRequest request) throws IOException { - super(createTomcatHttpHeaders(response), response, context, factory, bufferSize, request); + super(createTomcatHttpHeaders(response), response, context, factory, request); } private static HttpHeaders createTomcatHttpHeaders(HttpServletResponse response) { From 3f3341bb1d6bc43ba43da5b8f32fed79c3796222 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 3 Dec 2024 19:08:22 +0100 Subject: [PATCH 029/701] Drop server-specific RequestUpgradeStrategy implementations StandardWebSocketUpgradeStrategy is the common replacement on Tomcat, Undertow and all EE servers. JettyRequestUpgradeStrategy remains the preferred choice on Jetty. Closes gh-33744 --- framework-platform/framework-platform.gradle | 1 - spring-websocket/spring-websocket.gradle | 12 +- .../AbstractStandardUpgradeStrategy.java | 143 ---------- .../AbstractTyrusRequestUpgradeStrategy.java | 248 ------------------ .../GlassFishRequestUpgradeStrategy.java | 81 ------ .../StandardWebSocketUpgradeStrategy.java | 105 ++++++-- .../TomcatRequestUpgradeStrategy.java | 50 ---- .../UndertowRequestUpgradeStrategy.java | 56 ---- .../WebLogicRequestUpgradeStrategy.java | 244 ----------------- .../WebSphereRequestUpgradeStrategy.java | 64 ----- .../support/AbstractHandshakeHandler.java | 94 +------ .../support/DefaultHandshakeHandler.java | 14 +- .../AbstractWebSocketIntegrationTests.java | 57 +--- .../web/socket/WebSocketExtensionTests.java | 13 +- .../web/socket/WebSocketHandshakeTests.java | 2 +- .../StompWebSocketIntegrationTests.java | 2 +- .../WebSocketStompClientIntegrationTests.java | 4 +- .../AbstractSockJsIntegrationTests.java | 5 +- .../UndertowSockJsIntegrationTests.java | 20 +- 19 files changed, 130 insertions(+), 1085 deletions(-) delete mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractStandardUpgradeStrategy.java delete mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java delete mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/server/standard/GlassFishRequestUpgradeStrategy.java delete mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/server/standard/TomcatRequestUpgradeStrategy.java delete mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/server/standard/UndertowRequestUpgradeStrategy.java delete mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebLogicRequestUpgradeStrategy.java delete mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebSphereRequestUpgradeStrategy.java diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index 25979bcdf7c5..28d1cf7b6116 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -123,7 +123,6 @@ dependencies { api("org.freemarker:freemarker:2.3.33") api("org.glassfish.external:opendmk_jmxremote_optional_jar:1.0-b01-ea") api("org.glassfish:jakarta.el:4.0.2") - api("org.glassfish.tyrus:tyrus-container-servlet:2.1.3") api("org.graalvm.sdk:graal-sdk:22.3.1") api("org.hamcrest:hamcrest:2.2") api("org.hibernate:hibernate-core:7.0.0.Beta2") diff --git a/spring-websocket/spring-websocket.gradle b/spring-websocket/spring-websocket.gradle index 2250f5fdf38b..c20652bb129e 100644 --- a/spring-websocket/spring-websocket.gradle +++ b/spring-websocket/spring-websocket.gradle @@ -12,19 +12,11 @@ dependencies { optional("jakarta.servlet:jakarta.servlet-api") optional("jakarta.websocket:jakarta.websocket-api") optional("jakarta.websocket:jakarta.websocket-client-api") - optional("org.apache.tomcat:tomcat-websocket") { - exclude group: "org.apache.tomcat", module: "tomcat-servlet-api" - exclude group: "org.apache.tomcat", module: "tomcat-websocket-api" - } - optional("org.eclipse.jetty.ee10:jetty-ee10-webapp") { - exclude group: "jakarta.servlet", module: "jakarta.servlet-api" - } - optional("org.eclipse.jetty.websocket:jetty-websocket-jetty-api") - optional("org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-server") optional("org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-server") { exclude group: "jakarta.servlet", module: "jakarta.servlet-api" } - optional("org.glassfish.tyrus:tyrus-container-servlet") + optional("org.eclipse.jetty.websocket:jetty-websocket-jetty-api") + optional("org.eclipse.jetty:jetty-client") testImplementation(testFixtures(project(":spring-core"))) testImplementation(testFixtures(project(":spring-web"))) testImplementation("io.projectreactor.netty:reactor-netty-http") diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractStandardUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractStandardUpgradeStrategy.java deleted file mode 100644 index e19318ad2967..000000000000 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractStandardUpgradeStrategy.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2002-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 - * - * 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.web.socket.server.standard; - -import java.net.InetSocketAddress; -import java.security.Principal; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import jakarta.servlet.ServletContext; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.websocket.Endpoint; -import jakarta.websocket.Extension; -import jakarta.websocket.WebSocketContainer; -import jakarta.websocket.server.ServerContainer; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.http.server.ServletServerHttpRequest; -import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; -import org.springframework.web.socket.WebSocketExtension; -import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.adapter.standard.StandardToWebSocketExtensionAdapter; -import org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter; -import org.springframework.web.socket.adapter.standard.StandardWebSocketSession; -import org.springframework.web.socket.adapter.standard.WebSocketToStandardExtensionAdapter; -import org.springframework.web.socket.server.HandshakeFailureException; -import org.springframework.web.socket.server.RequestUpgradeStrategy; - -/** - * A base class for {@link RequestUpgradeStrategy} implementations that build - * on the standard WebSocket API for Java (JSR-356). - * - * @author Rossen Stoyanchev - * @since 4.0 - */ -public abstract class AbstractStandardUpgradeStrategy implements RequestUpgradeStrategy { - - protected final Log logger = LogFactory.getLog(getClass()); - - @Nullable - private volatile List extensions; - - - protected ServerContainer getContainer(HttpServletRequest request) { - ServletContext servletContext = request.getServletContext(); - String attrName = "jakarta.websocket.server.ServerContainer"; - ServerContainer container = (ServerContainer) servletContext.getAttribute(attrName); - Assert.notNull(container, "No 'jakarta.websocket.server.ServerContainer' ServletContext attribute. " + - "Are you running in a Servlet container that supports JSR-356?"); - return container; - } - - protected final HttpServletRequest getHttpServletRequest(ServerHttpRequest request) { - Assert.isInstanceOf(ServletServerHttpRequest.class, request, "ServletServerHttpRequest required"); - return ((ServletServerHttpRequest) request).getServletRequest(); - } - - protected final HttpServletResponse getHttpServletResponse(ServerHttpResponse response) { - Assert.isInstanceOf(ServletServerHttpResponse.class, response, "ServletServerHttpResponse required"); - return ((ServletServerHttpResponse) response).getServletResponse(); - } - - - @Override - public List getSupportedExtensions(ServerHttpRequest request) { - List extensions = this.extensions; - if (extensions == null) { - HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); - extensions = getInstalledExtensions(getContainer(servletRequest)); - this.extensions = extensions; - } - return extensions; - } - - protected List getInstalledExtensions(WebSocketContainer container) { - List result = new ArrayList<>(); - for (Extension extension : container.getInstalledExtensions()) { - result.add(new StandardToWebSocketExtensionAdapter(extension)); - } - return result; - } - - - @Override - public void upgrade(ServerHttpRequest request, ServerHttpResponse response, - @Nullable String selectedProtocol, List selectedExtensions, - @Nullable Principal user, WebSocketHandler wsHandler, Map attrs) - throws HandshakeFailureException { - - HttpHeaders headers = request.getHeaders(); - InetSocketAddress localAddr = null; - try { - localAddr = request.getLocalAddress(); - } - catch (Exception ex) { - // Ignore - } - InetSocketAddress remoteAddr = null; - try { - remoteAddr = request.getRemoteAddress(); - } - catch (Exception ex) { - // Ignore - } - - StandardWebSocketSession session = new StandardWebSocketSession(headers, attrs, localAddr, remoteAddr, user); - StandardWebSocketHandlerAdapter endpoint = new StandardWebSocketHandlerAdapter(wsHandler, session); - - List extensions = new ArrayList<>(); - for (WebSocketExtension extension : selectedExtensions) { - extensions.add(new WebSocketToStandardExtensionAdapter(extension)); - } - - upgradeInternal(request, response, selectedProtocol, extensions, endpoint); - } - - protected abstract void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, - @Nullable String selectedProtocol, List selectedExtensions, Endpoint endpoint) - throws HandshakeFailureException; - -} diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java deleted file mode 100644 index 0dea79819f86..000000000000 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright 2002-2022 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.web.socket.server.standard; - -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.net.URI; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Random; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.websocket.DeploymentException; -import jakarta.websocket.Endpoint; -import jakarta.websocket.EndpointConfig; -import jakarta.websocket.Extension; -import jakarta.websocket.WebSocketContainer; -import org.glassfish.tyrus.core.ComponentProviderService; -import org.glassfish.tyrus.core.RequestContext; -import org.glassfish.tyrus.core.TyrusEndpointWrapper; -import org.glassfish.tyrus.core.TyrusUpgradeResponse; -import org.glassfish.tyrus.core.TyrusWebSocketEngine; -import org.glassfish.tyrus.core.Version; -import org.glassfish.tyrus.server.TyrusServerContainer; -import org.glassfish.tyrus.spi.WebSocketEngine.UpgradeInfo; - -import org.springframework.beans.DirectFieldAccessor; -import org.springframework.http.HttpHeaders; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.lang.Nullable; -import org.springframework.util.ReflectionUtils; -import org.springframework.util.StringUtils; -import org.springframework.web.socket.WebSocketExtension; -import org.springframework.web.socket.server.HandshakeFailureException; - -import static org.glassfish.tyrus.spi.WebSocketEngine.UpgradeStatus.SUCCESS; - -/** - * A base class for {@code RequestUpgradeStrategy} implementations on top of - * JSR-356 based servers which include Tyrus as their WebSocket engine. - * - * @author Rossen Stoyanchev - * @author Brian Clozel - * @author Juergen Hoeller - * @since 4.1 - * @see Project Tyrus - */ -public abstract class AbstractTyrusRequestUpgradeStrategy extends AbstractStandardUpgradeStrategy { - - private static final String[] SUPPORTED_VERSIONS = - StringUtils.tokenizeToStringArray(Version.getSupportedWireProtocolVersions(), ","); - - - private static final Random random = new Random(); - - private static final Constructor constructor; - - private static final boolean constructorWithBooleanArgument; - - private static final Method registerMethod; - - private static final Method unRegisterMethod; - - static { - try { - constructor = getEndpointConstructor(); - int parameterCount = constructor.getParameterCount(); - constructorWithBooleanArgument = (parameterCount == 10); - if (!constructorWithBooleanArgument && parameterCount != 9) { - throw new IllegalStateException("Expected TyrusEndpointWrapper constructor with 9 or 10 arguments"); - } - registerMethod = TyrusWebSocketEngine.class.getDeclaredMethod("register", TyrusEndpointWrapper.class); - unRegisterMethod = TyrusWebSocketEngine.class.getDeclaredMethod("unregister", TyrusEndpointWrapper.class); - ReflectionUtils.makeAccessible(registerMethod); - } - catch (Exception ex) { - throw new IllegalStateException("No compatible Tyrus version found", ex); - } - } - - private static Constructor getEndpointConstructor() { - for (Constructor current : TyrusEndpointWrapper.class.getConstructors()) { - Class[] types = current.getParameterTypes(); - if (Endpoint.class == types[0] && EndpointConfig.class == types[1]) { - return current; - } - } - throw new IllegalStateException("No compatible Tyrus version found"); - } - - - private final ComponentProviderService componentProvider = ComponentProviderService.create(); - - - @Override - public String[] getSupportedVersions() { - return SUPPORTED_VERSIONS; - } - - @Override - protected List getInstalledExtensions(WebSocketContainer container) { - try { - return super.getInstalledExtensions(container); - } - catch (UnsupportedOperationException ex) { - return new ArrayList<>(0); - } - } - - @Override - public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, - @Nullable String selectedProtocol, List extensions, Endpoint endpoint) - throws HandshakeFailureException { - - HttpServletRequest servletRequest = getHttpServletRequest(request); - HttpServletResponse servletResponse = getHttpServletResponse(response); - - TyrusServerContainer serverContainer = (TyrusServerContainer) getContainer(servletRequest); - TyrusWebSocketEngine engine = (TyrusWebSocketEngine) serverContainer.getWebSocketEngine(); - Object tyrusEndpoint = null; - boolean success; - - try { - // Shouldn't matter for processing but must be unique - String path = "/" + random.nextLong(); - tyrusEndpoint = createTyrusEndpoint(endpoint, path, selectedProtocol, extensions, serverContainer, engine); - register(engine, tyrusEndpoint); - - HttpHeaders headers = request.getHeaders(); - RequestContext requestContext = createRequestContext(servletRequest, path, headers); - TyrusUpgradeResponse upgradeResponse = new TyrusUpgradeResponse(); - UpgradeInfo upgradeInfo = engine.upgrade(requestContext, upgradeResponse); - success = SUCCESS.equals(upgradeInfo.getStatus()); - if (success) { - if (logger.isTraceEnabled()) { - logger.trace("Successful request upgrade: " + upgradeResponse.getHeaders()); - } - handleSuccess(servletRequest, servletResponse, upgradeInfo, upgradeResponse); - } - } - catch (Exception ex) { - unregisterTyrusEndpoint(engine, tyrusEndpoint); - throw new HandshakeFailureException("Error during handshake: " + request.getURI(), ex); - } - - unregisterTyrusEndpoint(engine, tyrusEndpoint); - if (!success) { - throw new HandshakeFailureException("Unexpected handshake failure: " + request.getURI()); - } - } - - private Object createTyrusEndpoint(Endpoint endpoint, String endpointPath, @Nullable String protocol, - List extensions, WebSocketContainer container, TyrusWebSocketEngine engine) - throws DeploymentException { - - ServerEndpointRegistration endpointConfig = new ServerEndpointRegistration(endpointPath, endpoint); - endpointConfig.setSubprotocols(Collections.singletonList(protocol)); - endpointConfig.setExtensions(extensions); - return createEndpoint(endpointConfig, this.componentProvider, container, engine); - } - - private RequestContext createRequestContext(HttpServletRequest request, String endpointPath, HttpHeaders headers) { - RequestContext context = - RequestContext.Builder.create() - .requestURI(URI.create(endpointPath)) - .userPrincipal(request.getUserPrincipal()) - .secure(request.isSecure()) - .remoteAddr(request.getRemoteAddr()) - .build(); - headers.forEach((header, value) -> context.getHeaders().put(header, value)); - return context; - } - - private void unregisterTyrusEndpoint(TyrusWebSocketEngine engine, @Nullable Object tyrusEndpoint) { - if (tyrusEndpoint != null) { - try { - unregister(engine, tyrusEndpoint); - } - catch (Throwable ex) { - // ignore - } - } - } - - private Object createEndpoint(ServerEndpointRegistration registration, ComponentProviderService provider, - WebSocketContainer container, TyrusWebSocketEngine engine) throws DeploymentException { - - DirectFieldAccessor accessor = new DirectFieldAccessor(engine); - Object sessionListener = accessor.getPropertyValue("sessionListener"); - Object clusterContext = accessor.getPropertyValue("clusterContext"); - try { - if (constructorWithBooleanArgument) { - // Tyrus 1.11+ - return constructor.newInstance(registration.getEndpoint(), registration, provider, container, - "/", registration.getConfigurator(), sessionListener, clusterContext, null, Boolean.TRUE); - } - else { - return constructor.newInstance(registration.getEndpoint(), registration, provider, container, - "/", registration.getConfigurator(), sessionListener, clusterContext, null); - } - } - catch (Exception ex) { - throw new HandshakeFailureException("Failed to register " + registration, ex); - } - } - - private void register(TyrusWebSocketEngine engine, Object endpoint) { - try { - registerMethod.invoke(engine, endpoint); - } - catch (Exception ex) { - throw new HandshakeFailureException("Failed to register " + endpoint, ex); - } - } - - private void unregister(TyrusWebSocketEngine engine, Object endpoint) { - try { - unRegisterMethod.invoke(engine, endpoint); - } - catch (Exception ex) { - throw new HandshakeFailureException("Failed to unregister " + endpoint, ex); - } - } - - - protected abstract void handleSuccess(HttpServletRequest request, HttpServletResponse response, - UpgradeInfo upgradeInfo, TyrusUpgradeResponse upgradeResponse) throws IOException, ServletException; - -} diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/GlassFishRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/GlassFishRequestUpgradeStrategy.java deleted file mode 100644 index 07ca46500e1b..000000000000 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/GlassFishRequestUpgradeStrategy.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2002-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 - * - * 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.web.socket.server.standard; - -import java.io.IOException; -import java.lang.reflect.Constructor; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.glassfish.tyrus.core.TyrusUpgradeResponse; -import org.glassfish.tyrus.core.Utils; -import org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler; -import org.glassfish.tyrus.spi.WebSocketEngine.UpgradeInfo; -import org.glassfish.tyrus.spi.Writer; - -import org.springframework.util.ReflectionUtils; -import org.springframework.web.socket.server.HandshakeFailureException; - -/** - * A WebSocket {@code RequestUpgradeStrategy} for Oracle's GlassFish 4.1 and higher. - * - * @author Rossen Stoyanchev - * @author Juergen Hoeller - * @author Michael Irwin - * @since 4.0 - */ -public class GlassFishRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeStrategy { - - private static final Constructor constructor; - - static { - try { - ClassLoader classLoader = GlassFishRequestUpgradeStrategy.class.getClassLoader(); - Class type = classLoader.loadClass("org.glassfish.tyrus.servlet.TyrusServletWriter"); - constructor = type.getDeclaredConstructor(TyrusHttpUpgradeHandler.class); - ReflectionUtils.makeAccessible(constructor); - } - catch (Exception ex) { - throw new IllegalStateException("No compatible Tyrus version found", ex); - } - } - - - @Override - protected void handleSuccess(HttpServletRequest request, HttpServletResponse response, - UpgradeInfo upgradeInfo, TyrusUpgradeResponse upgradeResponse) throws IOException, ServletException { - - TyrusHttpUpgradeHandler handler = request.upgrade(TyrusHttpUpgradeHandler.class); - Writer servletWriter = newServletWriter(handler); - handler.preInit(upgradeInfo, servletWriter, request.getUserPrincipal() != null); - - response.setStatus(upgradeResponse.getStatus()); - upgradeResponse.getHeaders().forEach((key, value) -> response.addHeader(key, Utils.getHeaderFromList(value))); - response.flushBuffer(); - } - - private Writer newServletWriter(TyrusHttpUpgradeHandler handler) { - try { - return (Writer) constructor.newInstance(handler); - } - catch (Exception ex) { - throw new HandshakeFailureException("Failed to instantiate TyrusServletWriter", ex); - } - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/StandardWebSocketUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/StandardWebSocketUpgradeStrategy.java index 9cb810a0ce46..818fbe7730cf 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/StandardWebSocketUpgradeStrategy.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/StandardWebSocketUpgradeStrategy.java @@ -16,28 +16,39 @@ package org.springframework.web.socket.server.standard; +import java.net.InetSocketAddress; +import java.security.Principal; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import jakarta.servlet.ServletContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import jakarta.websocket.Endpoint; import jakarta.websocket.Extension; -import jakarta.websocket.server.ServerEndpointConfig; +import jakarta.websocket.WebSocketContainer; +import jakarta.websocket.server.ServerContainer; +import org.springframework.http.HttpHeaders; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.web.socket.WebSocketExtension; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.adapter.standard.StandardToWebSocketExtensionAdapter; +import org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter; +import org.springframework.web.socket.adapter.standard.StandardWebSocketSession; +import org.springframework.web.socket.adapter.standard.WebSocketToStandardExtensionAdapter; import org.springframework.web.socket.server.HandshakeFailureException; +import org.springframework.web.socket.server.RequestUpgradeStrategy; /** * A WebSocket {@code RequestUpgradeStrategy} for the Jakarta WebSocket API 2.1+. * - *

    This strategy serves as a fallback if no specific server has been detected. - * It can also be used with Jakarta EE 10 level servers such as Tomcat 10.1 and - * Undertow 2.3 directly, relying on their built-in Jakarta WebSocket 2.1 support. - * *

    To modify properties of the underlying {@link jakarta.websocket.server.ServerContainer} * you can use {@link ServletServerContainerFactoryBean} in XML configuration or, * when using Java configuration, access the container instance through the @@ -48,10 +59,13 @@ * @since 6.0 * @see jakarta.websocket.server.ServerContainer#upgradeHttpToWebSocket */ -public class StandardWebSocketUpgradeStrategy extends AbstractStandardUpgradeStrategy { +public class StandardWebSocketUpgradeStrategy implements RequestUpgradeStrategy { private static final String[] SUPPORTED_VERSIONS = new String[] {"13"}; + @Nullable + private volatile List extensions; + @Override public String[] getSupportedVersions() { @@ -59,10 +73,55 @@ public String[] getSupportedVersions() { } @Override - protected void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response, - @Nullable String selectedProtocol, List selectedExtensions, Endpoint endpoint) + public List getSupportedExtensions(ServerHttpRequest request) { + List extensions = this.extensions; + if (extensions == null) { + HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); + extensions = getInstalledExtensions(getContainer(servletRequest)); + this.extensions = extensions; + } + return extensions; + } + + protected List getInstalledExtensions(WebSocketContainer container) { + List result = new ArrayList<>(); + for (Extension extension : container.getInstalledExtensions()) { + result.add(new StandardToWebSocketExtensionAdapter(extension)); + } + return result; + } + + + @Override + public void upgrade(ServerHttpRequest request, ServerHttpResponse response, + @Nullable String selectedProtocol, List selectedExtensions, + @Nullable Principal user, WebSocketHandler wsHandler, Map attrs) throws HandshakeFailureException { + HttpHeaders headers = request.getHeaders(); + InetSocketAddress localAddr = null; + try { + localAddr = request.getLocalAddress(); + } + catch (Exception ex) { + // Ignore + } + InetSocketAddress remoteAddr = null; + try { + remoteAddr = request.getRemoteAddress(); + } + catch (Exception ex) { + // Ignore + } + + StandardWebSocketSession session = new StandardWebSocketSession(headers, attrs, localAddr, remoteAddr, user); + StandardWebSocketHandlerAdapter endpoint = new StandardWebSocketHandlerAdapter(wsHandler, session); + + List extensions = new ArrayList<>(); + for (WebSocketExtension extension : selectedExtensions) { + extensions.add(new WebSocketToStandardExtensionAdapter(extension)); + } + HttpServletRequest servletRequest = getHttpServletRequest(request); HttpServletResponse servletResponse = getHttpServletResponse(response); @@ -72,21 +131,35 @@ protected void upgradeInternal(ServerHttpRequest request, ServerHttpResponse res ServerEndpointRegistration endpointConfig = new ServerEndpointRegistration(path, endpoint); endpointConfig.setSubprotocols(Collections.singletonList(selectedProtocol)); - endpointConfig.setExtensions(selectedExtensions); + endpointConfig.setExtensions(extensions); try { - upgradeHttpToWebSocket(servletRequest, servletResponse, endpointConfig, pathParams); + getContainer(servletRequest).upgradeHttpToWebSocket( + servletRequest, servletResponse, endpointConfig, pathParams); } catch (Exception ex) { - throw new HandshakeFailureException( - "Servlet request failed to upgrade to WebSocket: " + requestUrl, ex); + throw new HandshakeFailureException("Servlet request failed to upgrade to WebSocket: " + requestUrl, ex); } } - protected void upgradeHttpToWebSocket(HttpServletRequest request, HttpServletResponse response, - ServerEndpointConfig endpointConfig, Map pathParams) throws Exception { - getContainer(request).upgradeHttpToWebSocket(request, response, endpointConfig, pathParams); + protected ServerContainer getContainer(HttpServletRequest request) { + ServletContext servletContext = request.getServletContext(); + String attrName = "jakarta.websocket.server.ServerContainer"; + ServerContainer container = (ServerContainer) servletContext.getAttribute(attrName); + Assert.notNull(container, "No 'jakarta.websocket.server.ServerContainer' ServletContext attribute. " + + "Are you running in a Servlet container that supports JSR-356?"); + return container; + } + + protected final HttpServletRequest getHttpServletRequest(ServerHttpRequest request) { + Assert.isInstanceOf(ServletServerHttpRequest.class, request, "ServletServerHttpRequest required"); + return ((ServletServerHttpRequest) request).getServletRequest(); + } + + protected final HttpServletResponse getHttpServletResponse(ServerHttpResponse response) { + Assert.isInstanceOf(ServletServerHttpResponse.class, response, "ServletServerHttpResponse required"); + return ((ServletServerHttpResponse) response).getServletResponse(); } } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/TomcatRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/TomcatRequestUpgradeStrategy.java deleted file mode 100644 index 714918c07a16..000000000000 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/TomcatRequestUpgradeStrategy.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2002-2022 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.web.socket.server.standard; - -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.websocket.server.ServerEndpointConfig; -import org.apache.tomcat.websocket.server.WsServerContainer; - -/** - * A WebSocket {@code RequestUpgradeStrategy} for Apache Tomcat. Compatible with Tomcat 10 - * and higher, in particular with Tomcat 10.0 (not based on Jakarta WebSocket 2.1 yet). - * - *

    To modify properties of the underlying {@link jakarta.websocket.server.ServerContainer} - * you can use {@link ServletServerContainerFactoryBean} in XML configuration or, - * when using Java configuration, access the container instance through the - * "jakarta.websocket.server.ServerContainer" ServletContext attribute. - * - * @author Rossen Stoyanchev - * @author Juergen Hoeller - * @since 4.0 - * @see org.apache.tomcat.websocket.server.WsServerContainer#upgradeHttpToWebSocket - */ -public class TomcatRequestUpgradeStrategy extends StandardWebSocketUpgradeStrategy { - - @Override - protected void upgradeHttpToWebSocket(HttpServletRequest request, HttpServletResponse response, - ServerEndpointConfig endpointConfig, Map pathParams) throws Exception { - - ((WsServerContainer) getContainer(request)).upgradeHttpToWebSocket( - request, response, endpointConfig, pathParams); - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/UndertowRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/UndertowRequestUpgradeStrategy.java deleted file mode 100644 index bb20bcafcaf5..000000000000 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/UndertowRequestUpgradeStrategy.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2002-2022 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.web.socket.server.standard; - -import java.util.Map; - -import io.undertow.websockets.jsr.ServerWebSocketContainer; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.websocket.server.ServerEndpointConfig; - -/** - * A WebSocket {@code RequestUpgradeStrategy} for WildFly and its underlying - * Undertow web server. Also compatible with embedded Undertow usage. - * - *

    Designed for Undertow 2.2, also compatible with Undertow 2.3 - * (which implements Jakarta WebSocket 2.1 as well). - * - * @author Rossen Stoyanchev - * @author Juergen Hoeller - * @since 4.0.1 - * @see io.undertow.websockets.jsr.ServerWebSocketContainer#doUpgrade - */ -public class UndertowRequestUpgradeStrategy extends StandardWebSocketUpgradeStrategy { - - private static final String[] SUPPORTED_VERSIONS = new String[] {"13", "8", "7"}; - - - @Override - public String[] getSupportedVersions() { - return SUPPORTED_VERSIONS; - } - - @Override - protected void upgradeHttpToWebSocket(HttpServletRequest request, HttpServletResponse response, - ServerEndpointConfig endpointConfig, Map pathParams) throws Exception { - - ((ServerWebSocketContainer) getContainer(request)).doUpgrade( - request, response, endpointConfig, pathParams); - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebLogicRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebLogicRequestUpgradeStrategy.java deleted file mode 100644 index 23409116acc1..000000000000 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebLogicRequestUpgradeStrategy.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright 2002-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.web.socket.server.standard; - -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; - -import jakarta.servlet.AsyncContext; -import jakarta.servlet.ServletContext; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletRequestWrapper; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.glassfish.tyrus.core.TyrusUpgradeResponse; -import org.glassfish.tyrus.core.Utils; -import org.glassfish.tyrus.spi.Connection; -import org.glassfish.tyrus.spi.WebSocketEngine.UpgradeInfo; -import org.glassfish.tyrus.spi.Writer; - -import org.springframework.beans.BeanWrapper; -import org.springframework.beans.BeanWrapperImpl; -import org.springframework.lang.Nullable; -import org.springframework.util.ReflectionUtils; -import org.springframework.web.socket.server.HandshakeFailureException; - -/** - * A WebSocket {@code RequestUpgradeStrategy} for Oracle's WebLogic. - * Supports 12.1.3 as well as 12.2.1, as of Spring Framework 4.2.3. - * - * @author Rossen Stoyanchev - * @author Juergen Hoeller - * @since 4.1 - */ -public class WebLogicRequestUpgradeStrategy extends AbstractTyrusRequestUpgradeStrategy { - - private static final TyrusMuxableWebSocketHelper webSocketHelper = new TyrusMuxableWebSocketHelper(); - - private static final WebLogicServletWriterHelper servletWriterHelper = new WebLogicServletWriterHelper(); - - private static final Connection.CloseListener noOpCloseListener = (reason -> {}); - - - @Override - protected void handleSuccess(HttpServletRequest request, HttpServletResponse response, - UpgradeInfo upgradeInfo, TyrusUpgradeResponse upgradeResponse) throws IOException, ServletException { - - response.setStatus(upgradeResponse.getStatus()); - upgradeResponse.getHeaders().forEach((key, value) -> response.addHeader(key, Utils.getHeaderFromList(value))); - - AsyncContext asyncContext = request.startAsync(); - asyncContext.setTimeout(-1L); - - Object nativeRequest = getNativeRequest(request); - BeanWrapper beanWrapper = new BeanWrapperImpl(nativeRequest); - Object httpSocket = beanWrapper.getPropertyValue("connection.connectionHandler.rawConnection"); - Object webSocket = webSocketHelper.newInstance(request, httpSocket); - webSocketHelper.upgrade(webSocket, httpSocket, request.getServletContext()); - - response.flushBuffer(); - - boolean isProtected = request.getUserPrincipal() != null; - Writer servletWriter = servletWriterHelper.newInstance(webSocket, isProtected); - Connection connection = upgradeInfo.createConnection(servletWriter, noOpCloseListener); - new BeanWrapperImpl(webSocket).setPropertyValue("connection", connection); - new BeanWrapperImpl(servletWriter).setPropertyValue("connection", connection); - webSocketHelper.registerForReadEvent(webSocket); - } - - - private static Class type(String className) throws ClassNotFoundException { - return WebLogicRequestUpgradeStrategy.class.getClassLoader().loadClass(className); - } - - private static Method method(String className, String method, Class... paramTypes) - throws ClassNotFoundException, NoSuchMethodException { - - return type(className).getDeclaredMethod(method, paramTypes); - } - - private static Object getNativeRequest(ServletRequest request) { - while (request instanceof ServletRequestWrapper wrapper) { - request = wrapper.getRequest(); - } - return request; - } - - - /** - * Helps to create and invoke {@code weblogic.servlet.internal.MuxableSocketHTTP}. - */ - private static class TyrusMuxableWebSocketHelper { - - private static final Class type; - - private static final Constructor constructor; - - private static final SubjectHelper subjectHelper; - - private static final Method upgradeMethod; - - private static final Method readEventMethod; - - static { - try { - type = type("weblogic.websocket.tyrus.TyrusMuxableWebSocket"); - - constructor = type.getDeclaredConstructor( - type("weblogic.servlet.internal.MuxableSocketHTTP"), - type("weblogic.websocket.tyrus.CoherenceServletFilterService"), - type("weblogic.servlet.spi.SubjectHandle")); - subjectHelper = new SubjectHelper(); - - upgradeMethod = type.getMethod("upgrade", type("weblogic.socket.MuxableSocket"), ServletContext.class); - readEventMethod = type.getMethod("registerForReadEvent"); - } - catch (Exception ex) { - throw new IllegalStateException("No compatible WebSocket version found", ex); - } - } - - private Object newInstance(HttpServletRequest request, @Nullable Object httpSocket) { - try { - Object[] args = new Object[] {httpSocket, null, subjectHelper.getSubject(request)}; - return constructor.newInstance(args); - } - catch (Exception ex) { - throw new HandshakeFailureException("Failed to create TyrusMuxableWebSocket", ex); - } - } - - private void upgrade(Object webSocket, @Nullable Object httpSocket, ServletContext servletContext) { - try { - upgradeMethod.invoke(webSocket, httpSocket, servletContext); - } - catch (Exception ex) { - throw new HandshakeFailureException("Failed to upgrade TyrusMuxableWebSocket", ex); - } - } - - private void registerForReadEvent(Object webSocket) { - try { - readEventMethod.invoke(webSocket); - } - catch (Exception ex) { - throw new HandshakeFailureException("Failed to register WebSocket for read event", ex); - } - } - } - - - private static class SubjectHelper { - - private final Method securityContextMethod; - - private final Method currentUserMethod; - - private final Method providerMethod; - - private final Method anonymousSubjectMethod; - - public SubjectHelper() { - try { - String className = "weblogic.servlet.internal.WebAppServletContext"; - this.securityContextMethod = method(className, "getSecurityContext"); - - className = "weblogic.servlet.security.internal.SecurityModule"; - this.currentUserMethod = method(className, "getCurrentUser", - type("weblogic.servlet.security.internal.ServletSecurityContext"), - HttpServletRequest.class); - - className = "weblogic.servlet.security.internal.WebAppSecurity"; - this.providerMethod = method(className, "getProvider"); - this.anonymousSubjectMethod = this.providerMethod.getReturnType().getDeclaredMethod("getAnonymousSubject"); - } - catch (Exception ex) { - throw new IllegalStateException("No compatible WebSocket version found", ex); - } - } - - public Object getSubject(HttpServletRequest request) { - try { - ServletContext servletContext = request.getServletContext(); - Object securityContext = this.securityContextMethod.invoke(servletContext); - Object subject = this.currentUserMethod.invoke(null, securityContext, request); - if (subject == null) { - Object securityProvider = this.providerMethod.invoke(null); - subject = this.anonymousSubjectMethod.invoke(securityProvider); - } - return subject; - } - catch (Exception ex) { - throw new HandshakeFailureException("Failed to obtain SubjectHandle", ex); - } - } - } - - - /** - * Helps to create and invoke {@code weblogic.websocket.tyrus.TyrusServletWriter}. - */ - private static class WebLogicServletWriterHelper { - - private static final Constructor constructor; - - static { - try { - Class writerType = type("weblogic.websocket.tyrus.TyrusServletWriter"); - Class listenerType = type("weblogic.websocket.tyrus.TyrusServletWriter$CloseListener"); - Class webSocketType = TyrusMuxableWebSocketHelper.type; - constructor = writerType.getDeclaredConstructor(webSocketType, listenerType, boolean.class); - ReflectionUtils.makeAccessible(constructor); - } - catch (Exception ex) { - throw new IllegalStateException("No compatible WebSocket version found", ex); - } - } - - private Writer newInstance(Object webSocket, boolean isProtected) { - try { - return (Writer) constructor.newInstance(webSocket, null, isProtected); - } - catch (Exception ex) { - throw new HandshakeFailureException("Failed to create TyrusServletWriter", ex); - } - } - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebSphereRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebSphereRequestUpgradeStrategy.java deleted file mode 100644 index 47df6b7c8598..000000000000 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/WebSphereRequestUpgradeStrategy.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2002-2022 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.web.socket.server.standard; - -import java.lang.reflect.Method; -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.websocket.server.ServerContainer; -import jakarta.websocket.server.ServerEndpointConfig; - -/** - * WebSphere support for upgrading an {@link HttpServletRequest} during a - * WebSocket handshake. To modify properties of the underlying - * {@link jakarta.websocket.server.ServerContainer} you can use - * {@link ServletServerContainerFactoryBean} in XML configuration or, when using - * Java configuration, access the container instance through the - * "javax.websocket.server.ServerContainer" ServletContext attribute. - * - * @author Rossen Stoyanchev - * @author Juergen Hoeller - * @since 4.2.1 - */ -public class WebSphereRequestUpgradeStrategy extends StandardWebSocketUpgradeStrategy { - - private static final Method upgradeMethod; - - static { - ClassLoader loader = WebSphereRequestUpgradeStrategy.class.getClassLoader(); - try { - Class type = loader.loadClass("com.ibm.websphere.wsoc.WsWsocServerContainer"); - upgradeMethod = type.getMethod("doUpgrade", HttpServletRequest.class, - HttpServletResponse.class, ServerEndpointConfig.class, Map.class); - } - catch (Exception ex) { - throw new IllegalStateException("No compatible WebSphere version found", ex); - } - } - - - @Override - protected void upgradeHttpToWebSocket(HttpServletRequest request, HttpServletResponse response, - ServerEndpointConfig endpointConfig, Map pathParams) throws Exception { - - ServerContainer container = getContainer(request); - upgradeMethod.invoke(container, request, response, endpointConfig, pathParams); - } - -} diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractHandshakeHandler.java index 6c05cfda70e7..d489acd5d945 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/AbstractHandshakeHandler.java @@ -37,7 +37,6 @@ import org.springframework.http.server.ServerHttpResponse; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import org.springframework.web.socket.SubProtocolCapable; import org.springframework.web.socket.WebSocketExtension; @@ -47,65 +46,27 @@ import org.springframework.web.socket.server.HandshakeFailureException; import org.springframework.web.socket.server.HandshakeHandler; import org.springframework.web.socket.server.RequestUpgradeStrategy; -import org.springframework.web.socket.server.jetty.JettyRequestUpgradeStrategy; -import org.springframework.web.socket.server.standard.GlassFishRequestUpgradeStrategy; import org.springframework.web.socket.server.standard.StandardWebSocketUpgradeStrategy; -import org.springframework.web.socket.server.standard.TomcatRequestUpgradeStrategy; -import org.springframework.web.socket.server.standard.UndertowRequestUpgradeStrategy; -import org.springframework.web.socket.server.standard.WebLogicRequestUpgradeStrategy; -import org.springframework.web.socket.server.standard.WebSphereRequestUpgradeStrategy; /** * A base class for {@link HandshakeHandler} implementations, independent of the Servlet API. * *

    Performs initial validation of the WebSocket handshake request - possibly rejecting it * through the appropriate HTTP status code - while also allowing its subclasses to override - * various parts of the negotiation process (for example, origin validation, sub-protocol negotiation, - * extensions negotiation, etc). + * various parts of the negotiation process: for example, origin validation, sub-protocol + * negotiation, extensions negotiation, etc. * *

    If the negotiation succeeds, the actual upgrade is delegated to a server-specific * {@link org.springframework.web.socket.server.RequestUpgradeStrategy}, which will update - * the response as necessary and initialize the WebSocket. + * the response as necessary and initialize the WebSocket. As of 7.0, this class uses + * {@link StandardWebSocketUpgradeStrategy} unless explicitly configured. * * @author Rossen Stoyanchev * @author Juergen Hoeller * @since 4.2 - * @see org.springframework.web.socket.server.jetty.JettyRequestUpgradeStrategy - * @see org.springframework.web.socket.server.standard.TomcatRequestUpgradeStrategy - * @see org.springframework.web.socket.server.standard.UndertowRequestUpgradeStrategy - * @see org.springframework.web.socket.server.standard.GlassFishRequestUpgradeStrategy */ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Lifecycle { - private static final boolean tomcatWsPresent; - - private static final boolean jettyWsPresent; - - private static final boolean undertowWsPresent; - - private static final boolean glassfishWsPresent; - - private static final boolean weblogicWsPresent; - - private static final boolean websphereWsPresent; - - static { - ClassLoader classLoader = AbstractHandshakeHandler.class.getClassLoader(); - tomcatWsPresent = ClassUtils.isPresent( - "org.apache.tomcat.websocket.server.WsHttpUpgradeHandler", classLoader); - jettyWsPresent = ClassUtils.isPresent( - "org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServerContainer", classLoader); - undertowWsPresent = ClassUtils.isPresent( - "io.undertow.websockets.jsr.ServerWebSocketContainer", classLoader); - glassfishWsPresent = ClassUtils.isPresent( - "org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler", classLoader); - weblogicWsPresent = ClassUtils.isPresent( - "weblogic.websocket.tyrus.TyrusServletWriter", classLoader); - websphereWsPresent = ClassUtils.isPresent( - "com.ibm.websphere.wsoc.WsWsocServerContainer", classLoader); - } - - protected final Log logger = LogFactory.getLog(getClass()); private final RequestUpgradeStrategy requestUpgradeStrategy; @@ -116,12 +77,10 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Life /** - * Default constructor that auto-detects and instantiates a - * {@link RequestUpgradeStrategy} suitable for the runtime container. - * @throws IllegalStateException if no {@link RequestUpgradeStrategy} can be found. + * Default constructor that uses {@link StandardWebSocketUpgradeStrategy}. */ protected AbstractHandshakeHandler() { - this(initRequestUpgradeStrategy()); + this(new StandardWebSocketUpgradeStrategy()); } /** @@ -394,45 +353,4 @@ protected Principal determineUser( return request.getPrincipal(); } - - private static RequestUpgradeStrategy initRequestUpgradeStrategy() { - if (tomcatWsPresent) { - return new TomcatRequestUpgradeStrategy(); - } - else if (jettyWsPresent) { - return new JettyRequestUpgradeStrategy(); - } - else if (undertowWsPresent) { - return new UndertowRequestUpgradeStrategy(); - } - else if (glassfishWsPresent) { - return TyrusStrategyDelegate.forGlassFish(); - } - else if (weblogicWsPresent) { - return TyrusStrategyDelegate.forWebLogic(); - } - else if (websphereWsPresent) { - return new WebSphereRequestUpgradeStrategy(); - } - else { - // Let's assume Jakarta WebSocket API 2.1+ - return new StandardWebSocketUpgradeStrategy(); - } - } - - - /** - * Inner class to avoid a reachable dependency on Tyrus API. - */ - private static class TyrusStrategyDelegate { - - public static RequestUpgradeStrategy forGlassFish() { - return new GlassFishRequestUpgradeStrategy(); - } - - public static RequestUpgradeStrategy forWebLogic() { - return new WebLogicRequestUpgradeStrategy(); - } - } - } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/DefaultHandshakeHandler.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/DefaultHandshakeHandler.java index 1ad972ab9041..cc6d3cb95e34 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/server/support/DefaultHandshakeHandler.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/support/DefaultHandshakeHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -18,13 +18,17 @@ import jakarta.servlet.ServletContext; +import org.springframework.util.ClassUtils; import org.springframework.web.context.ServletContextAware; import org.springframework.web.socket.server.RequestUpgradeStrategy; +import org.springframework.web.socket.server.jetty.JettyRequestUpgradeStrategy; +import org.springframework.web.socket.server.standard.StandardWebSocketUpgradeStrategy; /** * A default {@link org.springframework.web.socket.server.HandshakeHandler} implementation, * extending {@link AbstractHandshakeHandler} with Servlet-specific initialization support. - * See {@link AbstractHandshakeHandler}'s javadoc for details on supported servers etc. + * As of 7.0, this class prefers {@link JettyRequestUpgradeStrategy} when Jetty WebSocket + * is available on the classpath, using {@link StandardWebSocketUpgradeStrategy} otherwise. * * @author Rossen Stoyanchev * @author Juergen Hoeller @@ -32,7 +36,13 @@ */ public class DefaultHandshakeHandler extends AbstractHandshakeHandler implements ServletContextAware { + private static final boolean jettyWsPresent = ClassUtils.isPresent( + "org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServerContainer", + DefaultHandshakeHandler.class.getClassLoader()); + + public DefaultHandshakeHandler() { + super(jettyWsPresent ? new JettyRequestUpgradeStrategy() : new StandardWebSocketUpgradeStrategy()); } public DefaultHandshakeHandler(RequestUpgradeStrategy requestUpgradeStrategy) { diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/AbstractWebSocketIntegrationTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/AbstractWebSocketIntegrationTests.java index e762a0af22d7..9d03d40ba04c 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/AbstractWebSocketIntegrationTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/AbstractWebSocketIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -20,7 +20,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; @@ -34,16 +33,12 @@ import org.junit.jupiter.params.provider.MethodSource; import org.springframework.context.Lifecycle; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.lang.Nullable; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.socket.client.WebSocketClient; import org.springframework.web.socket.client.standard.StandardWebSocketClient; -import org.springframework.web.socket.server.RequestUpgradeStrategy; import org.springframework.web.socket.server.jetty.JettyRequestUpgradeStrategy; -import org.springframework.web.socket.server.standard.TomcatRequestUpgradeStrategy; -import org.springframework.web.socket.server.standard.UndertowRequestUpgradeStrategy; +import org.springframework.web.socket.server.standard.StandardWebSocketUpgradeStrategy; import org.springframework.web.socket.server.support.DefaultHandshakeHandler; import static org.junit.jupiter.api.Named.named; @@ -57,11 +52,6 @@ */ public abstract class AbstractWebSocketIntegrationTests { - private static final Map, Class> upgradeStrategyConfigTypes = Map.of( - JettyWebSocketTestServer.class, JettyUpgradeStrategyConfig.class, - TomcatWebSocketTestServer.class, TomcatUpgradeStrategyConfig.class, - UndertowTestServer.class, UndertowUpgradeStrategyConfig.class); - static Stream argumentsFactory() { return Stream.of( arguments(named("Jetty", new JettyWebSocketTestServer()), named("Standard", new StandardWebSocketClient())), @@ -104,7 +94,8 @@ protected void setup( this.wac = new AnnotationConfigWebApplicationContext(); this.wac.register(getAnnotatedConfigClasses()); - this.wac.register(upgradeStrategyConfigTypes.get(this.server.getClass())); + this.wac.register(this.server instanceof JettyWebSocketTestServer ? JettyHandshakeHandler.class : + StandardHandshakeHandler.class); if (this.webSocketClient instanceof Lifecycle) { ((Lifecycle) this.webSocketClient).start(); @@ -164,46 +155,18 @@ protected CompletableFuture execute(WebSocketHandler clientHan } - abstract static class AbstractRequestUpgradeStrategyConfig { - - @Bean - public DefaultHandshakeHandler handshakeHandler() { - return new DefaultHandshakeHandler(requestUpgradeStrategy()); - } - - public abstract RequestUpgradeStrategy requestUpgradeStrategy(); - } - - - @Configuration(proxyBeanMethods = false) - static class JettyUpgradeStrategyConfig extends AbstractRequestUpgradeStrategyConfig { - - @Override - @Bean - public RequestUpgradeStrategy requestUpgradeStrategy() { - return new JettyRequestUpgradeStrategy(); - } - } - - - @Configuration(proxyBeanMethods = false) - static class TomcatUpgradeStrategyConfig extends AbstractRequestUpgradeStrategyConfig { + private static class JettyHandshakeHandler extends DefaultHandshakeHandler { - @Override - @Bean - public RequestUpgradeStrategy requestUpgradeStrategy() { - return new TomcatRequestUpgradeStrategy(); + public JettyHandshakeHandler() { + super(new JettyRequestUpgradeStrategy()); } } - @Configuration(proxyBeanMethods = false) - static class UndertowUpgradeStrategyConfig extends AbstractRequestUpgradeStrategyConfig { + private static class StandardHandshakeHandler extends DefaultHandshakeHandler { - @Override - @Bean - public RequestUpgradeStrategy requestUpgradeStrategy() { - return new UndertowRequestUpgradeStrategy(); + public StandardHandshakeHandler() { + super(new StandardWebSocketUpgradeStrategy()); } } diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/WebSocketExtensionTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/WebSocketExtensionTests.java index 2ab23568efbd..f9419e641e48 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/WebSocketExtensionTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/WebSocketExtensionTests.java @@ -18,15 +18,13 @@ import java.util.List; -import org.glassfish.tyrus.core.TyrusExtension; import org.junit.jupiter.api.Test; -import org.springframework.web.socket.adapter.standard.StandardToWebSocketExtensionAdapter; - import static org.assertj.core.api.Assertions.assertThat; /** - * Test fixture for {@link WebSocketExtension} + * Test fixture for {@link WebSocketExtension}. + * * @author Brian Clozel */ class WebSocketExtensionTests { @@ -54,11 +52,4 @@ void parseHeaderMultiple() { .containsExactly("x-foo-extension", "x-bar-extension"); } - @Test // gh-26449 - public void equality() { - WebSocketExtension ext1 = new WebSocketExtension("myExtension"); - WebSocketExtension ext2 = new StandardToWebSocketExtensionAdapter(new TyrusExtension("myExtension")); - - assertThat(ext1).isEqualTo(ext2); - } } diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/WebSocketHandshakeTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/WebSocketHandshakeTests.java index 74990097e18d..142a0c18f37d 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/WebSocketHandshakeTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/WebSocketHandshakeTests.java @@ -104,9 +104,9 @@ public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { TestWebSocketHandler handler() { return new TestWebSocketHandler(); } - } + @SuppressWarnings("rawtypes") private static class TestWebSocketHandler extends AbstractWebSocketHandler { diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/messaging/StompWebSocketIntegrationTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/messaging/StompWebSocketIntegrationTests.java index 3db503998a99..f02301abfd93 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/messaging/StompWebSocketIntegrationTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/messaging/StompWebSocketIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/messaging/WebSocketStompClientIntegrationTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/messaging/WebSocketStompClientIntegrationTests.java index 3e464b329aa5..506f90ded338 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/messaging/WebSocketStompClientIntegrationTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/messaging/WebSocketStompClientIntegrationTests.java @@ -44,7 +44,7 @@ import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurationSupport; import org.springframework.web.socket.server.RequestUpgradeStrategy; -import org.springframework.web.socket.server.standard.TomcatRequestUpgradeStrategy; +import org.springframework.web.socket.server.standard.StandardWebSocketUpgradeStrategy; import org.springframework.web.socket.server.support.DefaultHandshakeHandler; import static org.assertj.core.api.Assertions.assertThat; @@ -144,7 +144,7 @@ static class TestConfig extends WebSocketMessageBrokerConfigurationSupport { @Override protected void registerStompEndpoints(StompEndpointRegistry registry) { // Can't rely on classpath detection - RequestUpgradeStrategy upgradeStrategy = new TomcatRequestUpgradeStrategy(); + RequestUpgradeStrategy upgradeStrategy = new StandardWebSocketUpgradeStrategy(); registry.addEndpoint("/stomp") .setHandshakeHandler(new DefaultHandshakeHandler(upgradeStrategy)) .setAllowedOrigins("*"); diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/AbstractSockJsIntegrationTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/AbstractSockJsIntegrationTests.java index 6299ab45c7b9..615baf82925e 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/AbstractSockJsIntegrationTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/AbstractSockJsIntegrationTests.java @@ -62,6 +62,7 @@ import org.springframework.web.socket.handler.TextWebSocketHandler; import org.springframework.web.socket.server.HandshakeHandler; import org.springframework.web.socket.server.RequestUpgradeStrategy; +import org.springframework.web.socket.server.standard.StandardWebSocketUpgradeStrategy; import org.springframework.web.socket.server.support.DefaultHandshakeHandler; import static org.assertj.core.api.Assertions.assertThat; @@ -143,7 +144,9 @@ void teardown() { } - protected abstract Class upgradeStrategyConfigClass(); + protected Class upgradeStrategyConfigClass() { + return StandardWebSocketUpgradeStrategy.class; + } protected abstract WebSocketTestServer createWebSocketTestServer(); diff --git a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/UndertowSockJsIntegrationTests.java b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/UndertowSockJsIntegrationTests.java index 54c9feb4cb7b..57250ba77dfe 100644 --- a/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/UndertowSockJsIntegrationTests.java +++ b/spring-websocket/src/test/java/org/springframework/web/socket/sockjs/client/UndertowSockJsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -18,24 +18,15 @@ import java.io.IOException; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.UndertowTestServer; import org.springframework.web.socket.WebSocketTestServer; import org.springframework.web.socket.client.standard.StandardWebSocketClient; -import org.springframework.web.socket.server.RequestUpgradeStrategy; -import org.springframework.web.socket.server.standard.UndertowRequestUpgradeStrategy; /** * @author Brian Clozel */ class UndertowSockJsIntegrationTests extends AbstractSockJsIntegrationTests { - @Override - protected Class upgradeStrategyConfigClass() { - return UndertowTestConfig.class; - } - @Override protected WebSocketTestServer createWebSocketTestServer() { return new UndertowTestServer(); @@ -56,13 +47,4 @@ protected AbstractXhrTransport createXhrTransport() { } } - - @Configuration(proxyBeanMethods = false) - static class UndertowTestConfig { - @Bean - RequestUpgradeStrategy upgradeStrategy() { - return new UndertowRequestUpgradeStrategy(); - } - } - } From 2b9010c2a2a29fbbe0f84d409885a92477007abd Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 4 Dec 2024 13:19:39 +0100 Subject: [PATCH 030/701] Remove APIs marked as deprecated for removal Closes gh-33809 --- .../ROOT/pages/web/webflux/config.adoc | 7 +- .../webmvc/mvc-config/static-resources.adoc | 7 +- framework-platform/framework-platform.gradle | 2 - .../AsyncExecutionAspectSupport.java | 4 - .../AnnotationAsyncExecutionAspectTests.java | 13 +- .../factory/aot/BeanInstanceSupplier.java | 24 - .../support/SimpleInstantiationStrategy.java | 17 - .../aot/BeanInstanceSupplierTests.java | 23 - .../quartz/SimpleThreadPoolTaskExecutor.java | 22 +- .../ApplicationListenerMethodAdapter.java | 4 - .../scheduling/annotation/AsyncResult.java | 43 +- .../concurrent/ConcurrentTaskExecutor.java | 27 +- .../concurrent/ConcurrentTaskScheduler.java | 12 - .../concurrent/SimpleAsyncTaskScheduler.java | 12 - .../concurrent/ThreadPoolTaskExecutor.java | 34 +- .../concurrent/ThreadPoolTaskScheduler.java | 57 +-- .../method/MethodValidationResult.java | 13 - .../method/ParameterValidationResult.java | 29 -- .../AnnotationDrivenEventListenerTests.java | 19 - ...AsyncAnnotationBeanPostProcessorTests.java | 24 - .../annotation/AsyncExecutionTests.java | 38 -- .../annotation/AsyncResultTests.java | 110 ----- .../AbstractSchedulingTaskExecutorTests.java | 123 +----- .../ConcurrentTaskExecutorTests.java | 4 +- .../ConcurrentTaskSchedulerTests.java | 18 +- .../DecoratedThreadPoolTaskExecutorTests.java | 4 +- .../SimpleAsyncTaskSchedulerTests.java | 17 +- .../ThreadPoolTaskExecutorTests.java | 5 +- .../ThreadPoolTaskSchedulerTests.java | 4 +- .../FilePatternResourceHintsRegistrar.java | 62 +-- .../task/AsyncListenableTaskExecutor.java | 58 --- .../core/task/SimpleAsyncTaskExecutor.java | 22 +- .../task/support/TaskExecutorAdapter.java | 32 +- .../org/springframework/util/MimeType.java | 45 -- .../springframework/util/MimeTypeUtils.java | 11 +- .../util/PropertyPlaceholderHelper.java | 18 - .../CompletableToListenableFutureAdapter.java | 110 ----- .../util/concurrent/FailureCallback.java | 40 -- .../util/concurrent/ListenableFuture.java | 74 ---- .../concurrent/ListenableFutureAdapter.java | 86 ---- .../concurrent/ListenableFutureCallback.java | 36 -- .../ListenableFutureCallbackRegistry.java | 157 ------- .../util/concurrent/ListenableFutureTask.java | 107 ----- .../MonoToListenableFutureAdapter.java | 40 -- .../concurrent/SettableListenableFuture.java | 192 -------- .../util/concurrent/SuccessCallback.java | 43 -- ...ilePatternResourceHintsRegistrarTests.java | 6 +- .../concurrent/ListenableFutureTaskTests.java | 138 ------ .../MonoToListenableFutureAdapterTests.java | 74 ---- .../SettableListenableFutureTests.java | 414 ------------------ .../expression/spel/ExpressionState.java | 58 --- .../expression/spel/ast/Indexer.java | 11 - .../expression/spel/ExpressionStateTests.java | 92 ---- .../AsyncHandlerMethodReturnValueHandler.java | 29 -- .../ListenableFutureReturnValueHandler.java | 49 --- .../SimpAnnotationMethodMessageHandler.java | 2 - .../stomp/ConnectionHandlingStompSession.java | 11 - .../stomp/ReactorNettyTcpStompClient.java | 34 -- .../messaging/tcp/TcpConnection.java | 14 - .../messaging/tcp/TcpOperations.java | 45 -- ...mpAnnotationMethodMessageHandlerTests.java | 63 --- .../test/util/JsonPathExpectationsHelper.java | 12 - .../reactive/server/DefaultWebTestClient.java | 7 - .../web/reactive/server/ExchangeResult.java | 10 - .../reactive/server/JsonPathAssertions.java | 21 - .../web/reactive/server/WebTestClient.java | 13 - .../samples/client/standalone/AsyncTests.java | 19 - .../samples/standalone/AsyncTests.java | 28 -- .../reactive/GenericReactiveTransaction.java | 9 +- .../support/DefaultTransactionStatus.java | 9 +- spring-web/spring-web.gradle | 1 - .../org/springframework/http/HttpHeaders.java | 15 - .../org/springframework/http/MediaType.java | 156 ------- .../http/client/ClientHttpResponse.java | 13 - ...ttpComponentsClientHttpRequestFactory.java | 12 - .../http/client/OkHttp3ClientHttpRequest.java | 142 ------ .../OkHttp3ClientHttpRequestFactory.java | 153 ------- .../client/OkHttp3ClientHttpResponse.java | 94 ---- .../http/client/ReactorClientHttpRequest.java | 18 - .../ReactorClientHttpRequestFactory.java | 30 -- .../client/ReactorClientHttpResponse.java | 17 - .../ReactorNettyClientRequestFactory.java | 58 --- .../SimpleClientHttpRequestFactory.java | 34 +- .../client/reactive/ClientHttpResponse.java | 12 - .../ReactorNetty2ServerHttpResponse.java | 8 - .../reactive/ReactorServerHttpResponse.java | 8 - .../server/reactive/ServerHttpResponse.java | 16 +- .../reactive/ServerHttpResponseDecorator.java | 8 - .../reactive/ServletServerHttpResponse.java | 8 - .../reactive/UndertowServerHttpResponse.java | 8 - ...ssingServletRequestParameterException.java | 21 +- .../client/DefaultResponseErrorHandler.java | 40 -- .../web/client/ResponseErrorHandler.java | 15 +- .../reactive/ServerHttpObservationFilter.java | 176 -------- .../ResponseStatusExceptionHandler.java | 20 +- .../AbstractReactorHttpExchangeAdapter.java | 51 +-- .../service/invoker/HttpClientAdapter.java | 147 ------- .../util/ContentCachingRequestWrapper.java | 15 - .../web/util/UriComponentsBuilder.java | 29 -- .../springframework/http/MediaTypeTests.java | 216 --------- ...ientHttpRequestFactoryWithOkHttpTests.java | 31 -- .../OkHttp3ClientHttpRequestFactoryTests.java | 41 -- .../client/RestClientIntegrationTests.java | 2 - .../client/RestTemplateIntegrationTests.java | 2 - .../ServerHttpObservationFilterTests.java | 142 ------ spring-webflux/spring-webflux.gradle | 1 - .../config/ResourceChainRegistration.java | 16 +- .../ClientRequestObservationContext.java | 10 - .../function/client/ClientResponse.java | 11 - .../server/DefaultServerResponseBuilder.java | 7 - .../function/server/ServerResponse.java | 9 - .../server/support/RouterFunctionMapping.java | 7 +- .../handler/AbstractUrlHandlerMapping.java | 5 +- .../resource/WebJarsResourceResolver.java | 125 ------ .../RequestMappingInfoHandlerMapping.java | 5 +- .../result/view/FragmentsRendering.java | 60 --- ...ientRequestObservationConventionTests.java | 12 - .../DefaultEntityResponseBuilderTests.java | 4 +- .../DefaultServerResponseBuilderTests.java | 4 +- .../function/server/RouterFunctionsTests.java | 10 - .../support/RouterFunctionMappingTests.java | 7 +- .../WebJarsResourceResolverTests.java | 155 ------- spring-webmvc/spring-webmvc.gradle | 1 - .../web/servlet/DispatcherServlet.java | 32 +- .../config/ResourcesBeanDefinitionParser.java | 8 +- .../annotation/ResourceChainRegistration.java | 16 +- .../function/AbstractServerResponse.java | 9 +- .../CompletedAsyncServerResponse.java | 11 +- .../function/DefaultAsyncServerResponse.java | 10 +- .../web/servlet/function/ServerResponse.java | 8 - .../servlet/handler/MappedInterceptor.java | 14 +- ...eferredResultMethodReturnValueHandler.java | 25 +- .../resource/WebJarsResourceResolver.java | 125 ------ .../web/servlet/view/FragmentsRendering.java | 37 -- .../web/servlet/config/MvcNamespaceTests.java | 5 +- .../DefaultEntityResponseBuilderTests.java | 3 +- .../DefaultServerResponseBuilderTests.java | 4 +- ...DeferredResultReturnValueHandlerTests.java | 26 -- .../WebJarsResourceResolverTests.java | 140 ------ .../client/AbstractWebSocketClient.java | 22 - .../web/socket/client/WebSocketClient.java | 35 -- .../config/WebSocketMessageBrokerStats.java | 34 +- .../messaging/WebSocketStompClient.java | 90 +--- .../client/AbstractClientSockJsSession.java | 11 - .../sockjs/client/AbstractXhrTransport.java | 9 - .../client/DefaultTransportRequest.java | 103 ----- .../web/socket/sockjs/client/Transport.java | 16 - .../client/WebSocketClientSockJsSession.java | 12 - .../sockjs/client/XhrClientSockJsSession.java | 21 - .../sockjs/client/XhrTransportTests.java | 4 +- 150 files changed, 137 insertions(+), 5918 deletions(-) delete mode 100644 spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncResultTests.java delete mode 100644 spring-core/src/main/java/org/springframework/core/task/AsyncListenableTaskExecutor.java delete mode 100644 spring-core/src/main/java/org/springframework/util/concurrent/CompletableToListenableFutureAdapter.java delete mode 100644 spring-core/src/main/java/org/springframework/util/concurrent/FailureCallback.java delete mode 100644 spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java delete mode 100644 spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java delete mode 100644 spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java delete mode 100644 spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java delete mode 100644 spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureTask.java delete mode 100644 spring-core/src/main/java/org/springframework/util/concurrent/MonoToListenableFutureAdapter.java delete mode 100644 spring-core/src/main/java/org/springframework/util/concurrent/SettableListenableFuture.java delete mode 100644 spring-core/src/main/java/org/springframework/util/concurrent/SuccessCallback.java delete mode 100644 spring-core/src/test/java/org/springframework/util/concurrent/ListenableFutureTaskTests.java delete mode 100644 spring-core/src/test/java/org/springframework/util/concurrent/MonoToListenableFutureAdapterTests.java delete mode 100644 spring-core/src/test/java/org/springframework/util/concurrent/SettableListenableFutureTests.java delete mode 100644 spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/ListenableFutureReturnValueHandler.java delete mode 100644 spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequest.java delete mode 100644 spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactory.java delete mode 100644 spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpResponse.java delete mode 100644 spring-web/src/main/java/org/springframework/http/client/ReactorNettyClientRequestFactory.java delete mode 100644 spring-web/src/main/java/org/springframework/web/filter/reactive/ServerHttpObservationFilter.java delete mode 100644 spring-web/src/main/java/org/springframework/web/service/invoker/HttpClientAdapter.java delete mode 100644 spring-web/src/test/java/org/springframework/http/client/BufferingClientHttpRequestFactoryWithOkHttpTests.java delete mode 100644 spring-web/src/test/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactoryTests.java delete mode 100644 spring-web/src/test/java/org/springframework/web/filter/reactive/ServerHttpObservationFilterTests.java delete mode 100644 spring-webflux/src/main/java/org/springframework/web/reactive/resource/WebJarsResourceResolver.java delete mode 100644 spring-webflux/src/test/java/org/springframework/web/reactive/resource/WebJarsResourceResolverTests.java delete mode 100644 spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java delete mode 100644 spring-webmvc/src/test/java/org/springframework/web/servlet/resource/WebJarsResourceResolverTests.java diff --git a/framework-docs/modules/ROOT/pages/web/webflux/config.adoc b/framework-docs/modules/ROOT/pages/web/webflux/config.adoc index 15f15e7115db..c67851c129cf 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/config.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/config.adoc @@ -647,13 +647,12 @@ For https://www.webjars.org/documentation[WebJars], versioned URLs like `/webjars/jquery/1.2.0/jquery.min.js` are the recommended and most efficient way to use them. The related resource location is configured out of the box with Spring Boot (or can be configured manually via `ResourceHandlerRegistry`) and does not require to add the -`org.webjars:webjars-locator-core` dependency. +`org.webjars:webjars-locator-lite` dependency. Version-less URLs like `/webjars/jquery/jquery.min.js` are supported through the `WebJarsResourceResolver` which is automatically registered when the -`org.webjars:webjars-locator-core` library is present on the classpath, at the cost of a -classpath scanning that could slow down application startup. The resolver can re-write URLs to -include the version of the jar and can also match against incoming URLs without versions +`org.webjars:webjars-locator-lite` library is present on the classpath. The resolver can re-write +URLs to include the version of the jar and can also match against incoming URLs without versions -- for example, from `/webjars/jquery/jquery.min.js` to `/webjars/jquery/1.2.0/jquery.min.js`. TIP: The Java configuration based on `ResourceHandlerRegistry` provides further options diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/static-resources.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/static-resources.adoc index 362adf674df0..87545bbadbd6 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/static-resources.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-config/static-resources.adoc @@ -48,13 +48,12 @@ For https://www.webjars.org/documentation[WebJars], versioned URLs like `/webjars/jquery/1.2.0/jquery.min.js` are the recommended and most efficient way to use them. The related resource location is configured out of the box with Spring Boot (or can be configured manually via `ResourceHandlerRegistry`) and does not require to add the -`org.webjars:webjars-locator-core` dependency. +`org.webjars:webjars-locator-lite` dependency. Version-less URLs like `/webjars/jquery/jquery.min.js` are supported through the `WebJarsResourceResolver` which is automatically registered when the -`org.webjars:webjars-locator-core` library is present on the classpath, at the cost of a -classpath scanning that could slow down application startup. The resolver can re-write URLs to -include the version of the jar and can also match against incoming URLs without versions +`org.webjars:webjars-locator-lite` library is present on the classpath. The resolver can re-write +URLs to include the version of the jar and can also match against incoming URLs without versions -- for example, from `/webjars/jquery/jquery.min.js` to `/webjars/jquery/1.2.0/jquery.min.js`. TIP: The Java configuration based on `ResourceHandlerRegistry` provides further options diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index 28d1cf7b6116..4c5779824bd4 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -38,7 +38,6 @@ dependencies { api("com.oracle.database.jdbc:ojdbc11:21.9.0.0") api("com.rometools:rome:1.19.0") api("com.squareup.okhttp3:mockwebserver:3.14.9") - api("com.squareup.okhttp3:okhttp:3.14.9") api("com.sun.activation:jakarta.activation:2.0.1") api("com.sun.xml.bind:jaxb-core:3.0.2") api("com.sun.xml.bind:jaxb-impl:3.0.2") @@ -142,7 +141,6 @@ dependencies { api("org.slf4j:slf4j-api:2.0.16") api("org.testng:testng:7.10.2") api("org.webjars:underscorejs:1.8.3") - api("org.webjars:webjars-locator-core:0.55") api("org.webjars:webjars-locator-lite:1.0.0") api("org.xmlunit:xmlunit-assertj:2.10.0") api("org.xmlunit:xmlunit-matchers:2.10.0") diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java index ea5034799622..d9276cef9766 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java @@ -281,15 +281,11 @@ protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) { * @param returnType the declared return type (potentially a {@link Future} variant) * @return the execution result (potentially a corresponding {@link Future} handle) */ - @SuppressWarnings("removal") @Nullable protected Object doSubmit(Callable task, AsyncTaskExecutor executor, Class returnType) { if (CompletableFuture.class.isAssignableFrom(returnType)) { return executor.submitCompletable(task); } - else if (org.springframework.util.concurrent.ListenableFuture.class.isAssignableFrom(returnType)) { - return ((org.springframework.core.task.AsyncListenableTaskExecutor) executor).submitListenable(task); - } else if (Future.class.isAssignableFrom(returnType)) { return executor.submit(task); } diff --git a/spring-aspects/src/test/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspectTests.java b/spring-aspects/src/test/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspectTests.java index 6cf02e31db2c..3b6877b2411d 100644 --- a/spring-aspects/src/test/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspectTests.java +++ b/spring-aspects/src/test/java/org/springframework/scheduling/aspectj/AnnotationAsyncExecutionAspectTests.java @@ -35,7 +35,6 @@ import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.util.ReflectionUtils; -import org.springframework.util.concurrent.ListenableFuture; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.core.testfixture.TestGroup.LONG_RUNNING; @@ -136,10 +135,7 @@ void qualifiedAsyncMethodsAreRoutedToCorrectExecutor() throws InterruptedExcepti assertThat(defaultThread.get()).isNotEqualTo(Thread.currentThread()); assertThat(defaultThread.get().getName()).doesNotStartWith("e1-"); - ListenableFuture e1Thread = obj.e1Work(); - assertThat(e1Thread.get().getName()).startsWith("e1-"); - - CompletableFuture e1OtherThread = obj.e1OtherWork(); + CompletableFuture e1OtherThread = obj.e1Work(); assertThat(e1OtherThread.get().getName()).startsWith("e1-"); } @@ -269,12 +265,7 @@ public Future defaultWork() { } @Async("e1") - public ListenableFuture e1Work() { - return new AsyncResult<>(Thread.currentThread()); - } - - @Async("e1") - public CompletableFuture e1OtherWork() { + public CompletableFuture e1Work() { return CompletableFuture.completedFuture(Thread.currentThread()); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java index 4d6eecec4e36..89cbce160267 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java @@ -171,30 +171,6 @@ public BeanInstanceSupplier withGenerator(ThrowingFunction return new BeanInstanceSupplier<>(this.lookup, generator, null, this.shortcutBeanNames); } - /** - * Return a new {@link BeanInstanceSupplier} instance that uses the specified - * {@code generator} supplier to instantiate the underlying bean. - * @param generator a {@link ThrowingSupplier} to instantiate the underlying bean - * @return a new {@link BeanInstanceSupplier} instance with the specified generator - * @deprecated in favor of {@link #withGenerator(ThrowingFunction)} - */ - @Deprecated(since = "6.0.11", forRemoval = true) - public BeanInstanceSupplier withGenerator(ThrowingSupplier generator) { - Assert.notNull(generator, "'generator' must not be null"); - return new BeanInstanceSupplier<>(this.lookup, registeredBean -> generator.get(), - null, this.shortcutBeanNames); - } - - /** - * Return a new {@link BeanInstanceSupplier} instance - * that uses direct bean name injection shortcuts for specific parameters. - * @deprecated in favor of {@link #withShortcut(String...)} - */ - @Deprecated(since = "6.2", forRemoval = true) - public BeanInstanceSupplier withShortcuts(String... beanNames) { - return withShortcut(beanNames); - } - /** * Return a new {@link BeanInstanceSupplier} instance that uses * direct bean name injection shortcuts for specific parameters. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java index aec5fadd4ab8..1b54829f9075 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java @@ -55,23 +55,6 @@ public static Method getCurrentlyInvokedFactoryMethod() { return currentlyInvokedFactoryMethod.get(); } - /** - * Set the factory method currently being invoked or {@code null} to remove - * the current value, if any. - * @param method the factory method currently being invoked or {@code null} - * @since 6.0 - * @deprecated in favor of {@link #instantiateWithFactoryMethod(Method, Supplier)} - */ - @Deprecated(since = "6.2", forRemoval = true) - public static void setCurrentlyInvokedFactoryMethod(@Nullable Method method) { - if (method != null) { - currentlyInvokedFactoryMethod.set(method); - } - else { - currentlyInvokedFactoryMethod.remove(); - } - } - /** * Invoke the given {@code instanceSupplier} with the factory method exposed * as being invoked. diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanInstanceSupplierTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanInstanceSupplierTests.java index 078bbee216c5..e64159e10f7b 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanInstanceSupplierTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanInstanceSupplierTests.java @@ -62,7 +62,6 @@ import org.springframework.util.ReflectionUtils; import org.springframework.util.function.ThrowingBiFunction; import org.springframework.util.function.ThrowingFunction; -import org.springframework.util.function.ThrowingSupplier; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -180,16 +179,6 @@ void withGeneratorWhenFunctionIsNullThrowsException() { .withMessage("'generator' must not be null"); } - @Test - @Deprecated - @SuppressWarnings("removal") - void withGeneratorWhenSupplierIsNullThrowsException() { - BeanInstanceSupplier resolver = BeanInstanceSupplier.forConstructor(); - assertThatIllegalArgumentException() - .isThrownBy(() -> resolver.withGenerator((ThrowingSupplier) null)) - .withMessage("'generator' must not be null"); - } - @Test void getWithConstructorDoesNotSetResolvedFactoryMethod() { BeanInstanceSupplier resolver = BeanInstanceSupplier.forConstructor(String.class); @@ -236,18 +225,6 @@ void getWithGeneratorCallsFunction() { assertThat(resolver.get(registerBean)).isInstanceOf(String.class).isEqualTo("1"); } - @Test - @Deprecated - @SuppressWarnings("removal") - void getWithGeneratorCallsSupplier() { - BeanRegistrar registrar = new BeanRegistrar(SingleArgConstructor.class); - this.beanFactory.registerSingleton("one", "1"); - RegisteredBean registerBean = registrar.registerBean(this.beanFactory); - BeanInstanceSupplier resolver = BeanInstanceSupplier.forConstructor(String.class) - .withGenerator(() -> "1"); - assertThat(resolver.get(registerBean)).isInstanceOf(String.class).isEqualTo("1"); - } - @Test void getWhenRegisteredBeanIsNullThrowsException() { BeanInstanceSupplier resolver = BeanInstanceSupplier.forConstructor(String.class); diff --git a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java index 9ab8c18cfa34..b728c7e72096 100644 --- a/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java +++ b/spring-context-support/src/main/java/org/springframework/scheduling/quartz/SimpleThreadPoolTaskExecutor.java @@ -25,12 +25,10 @@ import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; -import org.springframework.core.task.AsyncListenableTaskExecutor; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.scheduling.SchedulingException; import org.springframework.scheduling.SchedulingTaskExecutor; import org.springframework.util.Assert; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.ListenableFutureTask; /** * Subclass of Quartz's SimpleThreadPool that implements Spring's @@ -47,9 +45,9 @@ * @see org.springframework.core.task.TaskExecutor * @see SchedulerFactoryBean#setTaskExecutor */ -@SuppressWarnings({"deprecation", "removal"}) +@SuppressWarnings("deprecation") public class SimpleThreadPoolTaskExecutor extends SimpleThreadPool - implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, InitializingBean, DisposableBean { + implements AsyncTaskExecutor, SchedulingTaskExecutor, InitializingBean, DisposableBean { private boolean waitForJobsToCompleteOnShutdown = false; @@ -91,20 +89,6 @@ public Future submit(Callable task) { return future; } - @Override - public ListenableFuture submitListenable(Runnable task) { - ListenableFutureTask future = new ListenableFutureTask<>(task, null); - execute(future); - return future; - } - - @Override - public ListenableFuture submitListenable(Callable task) { - ListenableFutureTask future = new ListenableFutureTask<>(task); - execute(future); - return future; - } - @Override public void destroy() { diff --git a/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java b/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java index e1916d086575..585878490656 100644 --- a/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java +++ b/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java @@ -310,7 +310,6 @@ protected Object[] resolveArguments(ApplicationEvent event) { return new Object[] {event}; } - @SuppressWarnings({"removal", "unchecked", "deprecation"}) protected void handleResult(Object result) { if (reactiveStreamsPresent && new ReactiveResultHandler().subscribeToPublisher(result)) { if (logger.isTraceEnabled()) { @@ -327,9 +326,6 @@ else if (event != null) { } }); } - else if (result instanceof org.springframework.util.concurrent.ListenableFuture listenableFuture) { - listenableFuture.addCallback(this::publishEvents, this::handleAsyncError); - } else { publishEvents(result); } diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java index 3a7b5f0812ca..f168677d087d 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java @@ -22,10 +22,6 @@ import java.util.concurrent.TimeUnit; import org.springframework.lang.Nullable; -import org.springframework.util.concurrent.FailureCallback; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.ListenableFutureCallback; -import org.springframework.util.concurrent.SuccessCallback; /** * A pass-through {@code Future} handle that can be used for method signatures @@ -46,8 +42,7 @@ * @deprecated as of 6.0, in favor of {@link CompletableFuture} */ @Deprecated(since = "6.0") -@SuppressWarnings("removal") -public class AsyncResult implements ListenableFuture { +public class AsyncResult implements Future { @Nullable private final V value; @@ -105,38 +100,6 @@ public V get(long timeout, TimeUnit unit) throws ExecutionException { return get(); } - @Override - public void addCallback(ListenableFutureCallback callback) { - addCallback(callback, callback); - } - - @Override - public void addCallback(SuccessCallback successCallback, FailureCallback failureCallback) { - try { - if (this.executionException != null) { - failureCallback.onFailure(exposedException(this.executionException)); - } - else { - successCallback.onSuccess(this.value); - } - } - catch (Throwable ex) { - // Ignore - } - } - - @Override - public CompletableFuture completable() { - if (this.executionException != null) { - CompletableFuture completable = new CompletableFuture<>(); - completable.completeExceptionally(exposedException(this.executionException)); - return completable; - } - else { - return CompletableFuture.completedFuture(this.value); - } - } - /** * Create a new async result which exposes the given value from {@link Future#get()}. @@ -144,7 +107,7 @@ public CompletableFuture completable() { * @since 4.2 * @see Future#get() */ - public static org.springframework.util.concurrent.ListenableFuture forValue(V value) { + public static Future forValue(V value) { return new AsyncResult<>(value, null); } @@ -156,7 +119,7 @@ public static org.springframework.util.concurrent.ListenableFuture forVal * @since 4.2 * @see ExecutionException */ - public static org.springframework.util.concurrent.ListenableFuture forExecutionException(Throwable ex) { + public static Future forExecutionException(Throwable ex) { return new AsyncResult<>(null, ex); } diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java index d4a503d0c185..7bda93c2e959 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutor.java @@ -26,14 +26,13 @@ import jakarta.enterprise.concurrent.ManagedExecutors; import jakarta.enterprise.concurrent.ManagedTask; -import org.springframework.core.task.AsyncListenableTaskExecutor; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.TaskDecorator; import org.springframework.core.task.support.TaskExecutorAdapter; import org.springframework.lang.Nullable; import org.springframework.scheduling.SchedulingAwareRunnable; import org.springframework.scheduling.SchedulingTaskExecutor; import org.springframework.util.ClassUtils; -import org.springframework.util.concurrent.ListenableFuture; /** * Adapter that takes a {@code java.util.concurrent.Executor} and exposes @@ -62,8 +61,8 @@ * @see DefaultManagedTaskExecutor * @see ThreadPoolTaskExecutor */ -@SuppressWarnings({"deprecation", "removal"}) -public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, SchedulingTaskExecutor { +@SuppressWarnings("deprecation") +public class ConcurrentTaskExecutor implements AsyncTaskExecutor, SchedulingTaskExecutor { private static final Executor STUB_EXECUTOR = (task -> { throw new IllegalStateException("Executor not configured"); @@ -172,16 +171,6 @@ public Future submit(Callable task) { return this.adaptedExecutor.submit(task); } - @Override - public ListenableFuture submitListenable(Runnable task) { - return this.adaptedExecutor.submitListenable(task); - } - - @Override - public ListenableFuture submitListenable(Callable task) { - return this.adaptedExecutor.submitListenable(task); - } - private TaskExecutorAdapter getAdaptedExecutor(Executor originalExecutor) { TaskExecutorAdapter adapter = @@ -224,16 +213,6 @@ public Future submit(Runnable task) { public Future submit(Callable task) { return super.submit(ManagedTaskBuilder.buildManagedTask(task, task.toString())); } - - @Override - public ListenableFuture submitListenable(Runnable task) { - return super.submitListenable(ManagedTaskBuilder.buildManagedTask(task, task.toString())); - } - - @Override - public ListenableFuture submitListenable(Callable task) { - return super.submitListenable(ManagedTaskBuilder.buildManagedTask(task, task.toString())); - } } diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskScheduler.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskScheduler.java index f707d2bcc48e..b004b0d5c3e1 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskScheduler.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ConcurrentTaskScheduler.java @@ -218,18 +218,6 @@ public Future submit(Callable task) { return super.submit(new DelegatingErrorHandlingCallable<>(task, this.errorHandler)); } - @SuppressWarnings({"deprecation", "removal"}) - @Override - public org.springframework.util.concurrent.ListenableFuture submitListenable(Runnable task) { - return super.submitListenable(TaskUtils.decorateTaskWithErrorHandler(task, this.errorHandler, false)); - } - - @SuppressWarnings({"deprecation", "removal"}) - @Override - public org.springframework.util.concurrent.ListenableFuture submitListenable(Callable task) { - return super.submitListenable(new DelegatingErrorHandlingCallable<>(task, this.errorHandler)); - } - @Override @Nullable public ScheduledFuture schedule(Runnable task, Trigger trigger) { diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/SimpleAsyncTaskScheduler.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/SimpleAsyncTaskScheduler.java index bb66d4512f17..ebba61059942 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/SimpleAsyncTaskScheduler.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/SimpleAsyncTaskScheduler.java @@ -269,18 +269,6 @@ public Future submit(Callable task) { return super.submit(new DelegatingErrorHandlingCallable<>(task, this.errorHandler)); } - @SuppressWarnings({"deprecation", "removal"}) - @Override - public org.springframework.util.concurrent.ListenableFuture submitListenable(Runnable task) { - return super.submitListenable(TaskUtils.decorateTaskWithErrorHandler(task, this.errorHandler, false)); - } - - @SuppressWarnings({"deprecation", "removal"}) - @Override - public org.springframework.util.concurrent.ListenableFuture submitListenable(Callable task) { - return super.submitListenable(new DelegatingErrorHandlingCallable<>(task, this.errorHandler)); - } - @Override @Nullable public ScheduledFuture schedule(Runnable task, Trigger trigger) { diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java index 66e54c8fd602..9451b64839b9 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java @@ -30,15 +30,13 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import org.springframework.core.task.AsyncListenableTaskExecutor; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.TaskDecorator; import org.springframework.core.task.TaskRejectedException; import org.springframework.lang.Nullable; import org.springframework.scheduling.SchedulingTaskExecutor; import org.springframework.util.Assert; import org.springframework.util.ConcurrentReferenceHashMap; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.ListenableFutureTask; /** * JavaBean that allows for configuring a {@link java.util.concurrent.ThreadPoolExecutor} @@ -80,9 +78,9 @@ * @see ThreadPoolExecutorFactoryBean * @see ConcurrentTaskExecutor */ -@SuppressWarnings({"serial", "deprecation", "removal"}) +@SuppressWarnings({"serial", "deprecation"}) public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport - implements AsyncListenableTaskExecutor, SchedulingTaskExecutor { + implements AsyncTaskExecutor, SchedulingTaskExecutor { private final Object poolSizeMonitor = new Object(); @@ -414,32 +412,6 @@ public Future submit(Callable task) { } } - @Override - public ListenableFuture submitListenable(Runnable task) { - ExecutorService executor = getThreadPoolExecutor(); - try { - ListenableFutureTask future = new ListenableFutureTask<>(task, null); - executor.execute(future); - return future; - } - catch (RejectedExecutionException ex) { - throw new TaskRejectedException(executor, task, ex); - } - } - - @Override - public ListenableFuture submitListenable(Callable task) { - ExecutorService executor = getThreadPoolExecutor(); - try { - ListenableFutureTask future = new ListenableFutureTask<>(task); - executor.execute(future); - return future; - } - catch (RejectedExecutionException ex) { - throw new TaskRejectedException(executor, task, ex); - } - } - @Override protected void cancelRemainingTask(Runnable task) { super.cancelRemainingTask(task); diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java index 458af46ae743..581e1d485205 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskScheduler.java @@ -19,7 +19,6 @@ import java.time.Clock; import java.time.Duration; import java.time.Instant; -import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.Delayed; import java.util.concurrent.ExecutionException; @@ -36,7 +35,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import org.springframework.core.task.AsyncListenableTaskExecutor; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.TaskDecorator; import org.springframework.core.task.TaskRejectedException; import org.springframework.lang.Nullable; @@ -45,10 +44,7 @@ import org.springframework.scheduling.Trigger; import org.springframework.scheduling.support.TaskUtils; import org.springframework.util.Assert; -import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ErrorHandler; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.ListenableFutureTask; /** * A standard implementation of Spring's {@link TaskScheduler} interface, wrapping @@ -74,9 +70,9 @@ * @see ThreadPoolTaskExecutor * @see SimpleAsyncTaskScheduler */ -@SuppressWarnings({"serial", "deprecation", "removal"}) +@SuppressWarnings({"serial", "deprecation"}) public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport - implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, TaskScheduler { + implements AsyncTaskExecutor, SchedulingTaskExecutor, TaskScheduler { private static final TimeUnit NANO = TimeUnit.NANOSECONDS; @@ -100,10 +96,6 @@ public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport @Nullable private ScheduledExecutorService scheduledExecutor; - // Underlying ScheduledFutureTask to user-level ListenableFuture handle, if any - private final Map> listenableFutureMap = - new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.WEAK); - /** * Set the ScheduledExecutorService's pool size. @@ -357,49 +349,6 @@ public Future submit(Callable task) { } } - @Override - public ListenableFuture submitListenable(Runnable task) { - ExecutorService executor = getScheduledExecutor(); - try { - ListenableFutureTask listenableFuture = new ListenableFutureTask<>(task, null); - executeAndTrack(executor, listenableFuture); - return listenableFuture; - } - catch (RejectedExecutionException ex) { - throw new TaskRejectedException(executor, task, ex); - } - } - - @Override - public ListenableFuture submitListenable(Callable task) { - ExecutorService executor = getScheduledExecutor(); - try { - ListenableFutureTask listenableFuture = new ListenableFutureTask<>(task); - executeAndTrack(executor, listenableFuture); - return listenableFuture; - } - catch (RejectedExecutionException ex) { - throw new TaskRejectedException(executor, task, ex); - } - } - - private void executeAndTrack(ExecutorService executor, ListenableFutureTask listenableFuture) { - Future scheduledFuture = executor.submit(errorHandlingTask(listenableFuture, false)); - this.listenableFutureMap.put(scheduledFuture, listenableFuture); - listenableFuture.addCallback(result -> this.listenableFutureMap.remove(scheduledFuture), - ex -> this.listenableFutureMap.remove(scheduledFuture)); - } - - @Override - protected void cancelRemainingTask(Runnable task) { - super.cancelRemainingTask(task); - // Cancel associated user-level ListenableFuture handle as well - ListenableFuture listenableFuture = this.listenableFutureMap.get(task); - if (listenableFuture != null) { - listenableFuture.cancel(true); - } - } - // TaskScheduler implementation diff --git a/spring-context/src/main/java/org/springframework/validation/method/MethodValidationResult.java b/spring-context/src/main/java/org/springframework/validation/method/MethodValidationResult.java index 69ff06f55c2b..fccb5ad41ca0 100644 --- a/spring-context/src/main/java/org/springframework/validation/method/MethodValidationResult.java +++ b/spring-context/src/main/java/org/springframework/validation/method/MethodValidationResult.java @@ -81,19 +81,6 @@ default List getAllErrors() { */ List getParameterValidationResults(); - /** - * Return all validation results. This includes both method parameters with - * errors directly on them, and Object method parameters with nested errors - * on their fields and properties. - * @see #getValueResults() - * @see #getBeanResults() - * @deprecated deprecated in favor of {@link #getParameterValidationResults()} - */ - @Deprecated(since = "6.2", forRemoval = true) - default List getAllValidationResults() { - return getParameterValidationResults(); - } - /** * Return the subset of {@link #getParameterValidationResults() allValidationResults} * that includes method parameters with validation errors directly on method diff --git a/spring-context/src/main/java/org/springframework/validation/method/ParameterValidationResult.java b/spring-context/src/main/java/org/springframework/validation/method/ParameterValidationResult.java index 128b078d33c6..740796976d07 100644 --- a/spring-context/src/main/java/org/springframework/validation/method/ParameterValidationResult.java +++ b/spring-context/src/main/java/org/springframework/validation/method/ParameterValidationResult.java @@ -85,35 +85,6 @@ public ParameterValidationResult( this.sourceLookup = sourceLookup; } - /** - * Create a {@code ParameterValidationResult}. - * @deprecated in favor of - * {@link ParameterValidationResult#ParameterValidationResult(MethodParameter, Object, Collection, Object, Integer, Object, BiFunction)} - */ - @Deprecated(since = "6.2", forRemoval = true) - public ParameterValidationResult( - MethodParameter param, @Nullable Object arg, Collection errors, - @Nullable Object container, @Nullable Integer index, @Nullable Object key) { - - this(param, arg, errors, container, index, key, (error, sourceType) -> { - throw new IllegalArgumentException("No source object of the given type"); - }); - } - - /** - * Create a {@code ParameterValidationResult}. - * @deprecated in favor of - * {@link ParameterValidationResult#ParameterValidationResult(MethodParameter, Object, Collection, Object, Integer, Object, BiFunction)} - */ - @Deprecated(since = "6.1.3", forRemoval = true) - public ParameterValidationResult( - MethodParameter param, @Nullable Object arg, Collection errors) { - - this(param, arg, errors, null, null, null, (error, sourceType) -> { - throw new IllegalArgumentException("No source object of the given type"); - }); - } - /** * The method parameter the validation results are for. diff --git a/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java b/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java index ac511516963f..5578213184e8 100644 --- a/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java +++ b/spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java @@ -279,25 +279,6 @@ void collectionReplyNullValue() { this.eventCollector.assertTotalEventsCount(2); } - @Test - @SuppressWarnings({"deprecation", "removal"}) - void listenableFutureReply() { - load(TestEventListener.class, ReplyEventListener.class); - org.springframework.util.concurrent.SettableListenableFuture future = - new org.springframework.util.concurrent.SettableListenableFuture<>(); - future.set("dummy"); - AnotherTestEvent event = new AnotherTestEvent(this, future); - ReplyEventListener replyEventListener = this.context.getBean(ReplyEventListener.class); - TestEventListener listener = this.context.getBean(TestEventListener.class); - - this.eventCollector.assertNoEventReceived(listener); - this.eventCollector.assertNoEventReceived(replyEventListener); - this.context.publishEvent(event); - this.eventCollector.assertEvent(replyEventListener, event); - this.eventCollector.assertEvent(listener, "dummy"); // reply - this.eventCollector.assertTotalEventsCount(2); - } - @Test void completableFutureReply() { load(TestEventListener.class, ReplyEventListener.class); diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java index 3d73ac0bea83..42d5851eec2d 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessorTests.java @@ -200,20 +200,6 @@ public void handleExceptionWithFuture() { assertFutureWithException(result, exceptionHandler); } - @Test - @SuppressWarnings("resource") - public void handleExceptionWithListenableFuture() { - ConfigurableApplicationContext context = - new AnnotationConfigApplicationContext(ConfigWithExceptionHandler.class); - ITestBean testBean = context.getBean("target", ITestBean.class); - - TestableAsyncUncaughtExceptionHandler exceptionHandler = - context.getBean("exceptionHandler", TestableAsyncUncaughtExceptionHandler.class); - assertThat(exceptionHandler.isCalled()).as("handler should not have been called yet").isFalse(); - Future result = testBean.failWithListenableFuture(); - assertFutureWithException(result, exceptionHandler); - } - private void assertFutureWithException(Future result, TestableAsyncUncaughtExceptionHandler exceptionHandler) { assertThatExceptionOfType(ExecutionException.class).isThrownBy( @@ -275,9 +261,6 @@ private interface ITestBean { Future failWithFuture(); - @SuppressWarnings({"deprecation", "removal"}) - org.springframework.util.concurrent.ListenableFuture failWithListenableFuture(); - void failWithVoid(); void await(long timeout); @@ -308,13 +291,6 @@ public Future failWithFuture() { throw new UnsupportedOperationException("failWithFuture"); } - @Async - @Override - @SuppressWarnings({"deprecation", "removal"}) - public org.springframework.util.concurrent.ListenableFuture failWithListenableFuture() { - throw new UnsupportedOperationException("failWithListenableFuture"); - } - @Async @Override public void failWithVoid() { diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java index 5be5a1214b58..c64a14d14adc 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncExecutionTests.java @@ -42,7 +42,6 @@ import org.springframework.context.ApplicationListener; import org.springframework.context.support.GenericApplicationContext; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import org.springframework.util.concurrent.ListenableFuture; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -51,7 +50,6 @@ * @author Juergen Hoeller * @author Chris Beams */ -@SuppressWarnings({"resource", "deprecation", "removal"}) class AsyncExecutionTests { private static String originalThreadName; @@ -81,8 +79,6 @@ void asyncMethods() throws Exception { asyncTest.doSomething(10); Future future = asyncTest.returnSomething(20); assertThat(future.get()).isEqualTo("20"); - ListenableFuture listenableFuture = asyncTest.returnSomethingListenable(20); - assertThat(listenableFuture.get()).isEqualTo("20"); CompletableFuture completableFuture = asyncTest.returnSomethingCompletable(20); assertThat(completableFuture.get()).isEqualTo("20"); @@ -94,14 +90,6 @@ void asyncMethods() throws Exception { asyncTest.returnSomething(-1).get()) .withCauseInstanceOf(IOException.class); - assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> - asyncTest.returnSomethingListenable(0).get()) - .withCauseInstanceOf(IllegalArgumentException.class); - - assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> - asyncTest.returnSomethingListenable(-1).get()) - .withCauseInstanceOf(IOException.class); - assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> asyncTest.returnSomethingCompletable(0).get()) .withCauseInstanceOf(IllegalArgumentException.class); @@ -174,8 +162,6 @@ void asyncClass() throws Exception { asyncTest.doSomething(10); Future future = asyncTest.returnSomething(20); assertThat(future.get()).isEqualTo("20"); - ListenableFuture listenableFuture = asyncTest.returnSomethingListenable(20); - assertThat(listenableFuture.get()).isEqualTo("20"); CompletableFuture completableFuture = asyncTest.returnSomethingCompletable(20); assertThat(completableFuture.get()).isEqualTo("20"); @@ -183,10 +169,6 @@ void asyncClass() throws Exception { asyncTest.returnSomething(0).get()) .withCauseInstanceOf(IllegalArgumentException.class); - assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> - asyncTest.returnSomethingListenable(0).get()) - .withCauseInstanceOf(IllegalArgumentException.class); - assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> asyncTest.returnSomethingCompletable(0).get()) .withCauseInstanceOf(IllegalArgumentException.class); @@ -419,18 +401,6 @@ else if (i < 0) { return AsyncResult.forValue(Integer.toString(i)); } - @Async - public ListenableFuture returnSomethingListenable(int i) { - assertThat(Thread.currentThread().getName()).isNotEqualTo(originalThreadName); - if (i == 0) { - throw new IllegalArgumentException(); - } - else if (i < 0) { - return AsyncResult.forExecutionException(new IOException()); - } - return new AsyncResult<>(Integer.toString(i)); - } - @Async public CompletableFuture returnSomethingCompletable(int i) { assertThat(Thread.currentThread().getName()).isNotEqualTo(originalThreadName); @@ -505,14 +475,6 @@ public Future returnSomething(int i) { return new AsyncResult<>(Integer.toString(i)); } - public ListenableFuture returnSomethingListenable(int i) { - assertThat(Thread.currentThread().getName()).isNotEqualTo(originalThreadName); - if (i == 0) { - throw new IllegalArgumentException(); - } - return new AsyncResult<>(Integer.toString(i)); - } - @Async public CompletableFuture returnSomethingCompletable(int i) { assertThat(Thread.currentThread().getName()).isNotEqualTo(originalThreadName); diff --git a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncResultTests.java b/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncResultTests.java deleted file mode 100644 index b4ac333da9cd..000000000000 --- a/spring-context/src/test/java/org/springframework/scheduling/annotation/AsyncResultTests.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2002-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.scheduling.annotation; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.ExecutionException; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Juergen Hoeller - */ -class AsyncResultTests { - - @Test - @SuppressWarnings({ "deprecation", "removal" }) - public void asyncResultWithCallbackAndValue() throws Exception { - String value = "val"; - final Set values = new HashSet<>(1); - org.springframework.util.concurrent.ListenableFuture future = AsyncResult.forValue(value); - future.addCallback(new org.springframework.util.concurrent.ListenableFutureCallback<>() { - @Override - public void onSuccess(String result) { - values.add(result); - } - @Override - public void onFailure(Throwable ex) { - throw new AssertionError("Failure callback not expected: " + ex, ex); - } - }); - assertThat(values).singleElement().isSameAs(value); - assertThat(future.get()).isSameAs(value); - assertThat(future.completable().get()).isSameAs(value); - future.completable().thenAccept(v -> assertThat(v).isSameAs(value)); - } - - @Test - @SuppressWarnings({ "deprecation", "removal" }) - public void asyncResultWithCallbackAndException() { - IOException ex = new IOException(); - final Set values = new HashSet<>(1); - org.springframework.util.concurrent.ListenableFuture future = AsyncResult.forExecutionException(ex); - future.addCallback(new org.springframework.util.concurrent.ListenableFutureCallback<>() { - @Override - public void onSuccess(String result) { - throw new AssertionError("Success callback not expected: " + result); - } - @Override - public void onFailure(Throwable ex) { - values.add(ex); - } - }); - assertThat(values).singleElement().isSameAs(ex); - assertThatExceptionOfType(ExecutionException.class) - .isThrownBy(future::get) - .withCause(ex); - assertThatExceptionOfType(ExecutionException.class) - .isThrownBy(future.completable()::get) - .withCause(ex); - } - - @Test - @SuppressWarnings({ "deprecation", "removal" }) - public void asyncResultWithSeparateCallbacksAndValue() throws Exception { - String value = "val"; - final Set values = new HashSet<>(1); - org.springframework.util.concurrent.ListenableFuture future = AsyncResult.forValue(value); - future.addCallback(values::add, ex -> new AssertionError("Failure callback not expected: " + ex)); - assertThat(values).singleElement().isSameAs(value); - assertThat(future.get()).isSameAs(value); - assertThat(future.completable().get()).isSameAs(value); - future.completable().thenAccept(v -> assertThat(v).isSameAs(value)); - } - - @Test - @SuppressWarnings({ "deprecation", "removal" }) - public void asyncResultWithSeparateCallbacksAndException() { - IOException ex = new IOException(); - final Set values = new HashSet<>(1); - org.springframework.util.concurrent.ListenableFuture future = AsyncResult.forExecutionException(ex); - future.addCallback(result -> new AssertionError("Success callback not expected: " + result), values::add); - assertThat(values).singleElement().isSameAs(ex); - assertThatExceptionOfType(ExecutionException.class) - .isThrownBy(future::get) - .withCause(ex); - assertThatExceptionOfType(ExecutionException.class) - .isThrownBy(future.completable()::get) - .withCause(ex); - } - -} diff --git a/spring-context/src/test/java/org/springframework/scheduling/concurrent/AbstractSchedulingTaskExecutorTests.java b/spring-context/src/test/java/org/springframework/scheduling/concurrent/AbstractSchedulingTaskExecutorTests.java index e6c01beaaa1a..4e7fc7c470b1 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/concurrent/AbstractSchedulingTaskExecutorTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/concurrent/AbstractSchedulingTaskExecutorTests.java @@ -35,6 +35,7 @@ import org.junit.jupiter.api.TestInfo; import org.springframework.beans.factory.DisposableBean; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.lang.Nullable; import static org.assertj.core.api.Assertions.assertThat; @@ -47,8 +48,7 @@ */ abstract class AbstractSchedulingTaskExecutorTests { - @SuppressWarnings("removal") - private org.springframework.core.task.AsyncListenableTaskExecutor executor; + private AsyncTaskExecutor executor; protected String testName; @@ -64,8 +64,7 @@ void setup(TestInfo testInfo) { this.executor = buildExecutor(); } - @SuppressWarnings("removal") - protected abstract org.springframework.core.task.AsyncListenableTaskExecutor buildExecutor(); + protected abstract AsyncTaskExecutor buildExecutor(); @AfterEach void shutdownExecutor() throws Exception { @@ -124,22 +123,6 @@ void submitRunnableWithGetAfterShutdown() throws Exception { }); } - @Test - @SuppressWarnings({ "deprecation", "removal" }) - void submitListenableRunnable() { - TestTask task = new TestTask(this.testName, 1); - // Act - org.springframework.util.concurrent.ListenableFuture future = executor.submitListenable(task); - future.addCallback(result -> outcome = result, ex -> outcome = ex); - // Assert - Awaitility.await() - .atMost(5, TimeUnit.SECONDS) - .pollInterval(10, TimeUnit.MILLISECONDS) - .until(future::isDone); - assertThat(outcome).isNull(); - assertThreadNamePrefix(task); - } - @Test void submitCompletableRunnable() { TestTask task = new TestTask(this.testName, 1); @@ -155,21 +138,6 @@ void submitCompletableRunnable() { assertThreadNamePrefix(task); } - @Test - @SuppressWarnings({ "deprecation", "removal" }) - void submitFailingListenableRunnable() { - TestTask task = new TestTask(this.testName, 0); - org.springframework.util.concurrent.ListenableFuture future = executor.submitListenable(task); - future.addCallback(result -> outcome = result, ex -> outcome = ex); - - Awaitility.await() - .dontCatchUncaughtExceptions() - .atMost(5, TimeUnit.SECONDS) - .pollInterval(10, TimeUnit.MILLISECONDS) - .until(() -> future.isDone() && outcome != null); - assertThat(outcome.getClass()).isSameAs(RuntimeException.class); - } - @Test void submitFailingCompletableRunnable() { TestTask task = new TestTask(this.testName, 0); @@ -184,43 +152,15 @@ void submitFailingCompletableRunnable() { assertThat(outcome.getClass()).isSameAs(CompletionException.class); } - @Test - @SuppressWarnings({ "deprecation", "removal" }) - void submitListenableRunnableWithGetAfterShutdown() throws Exception { - org.springframework.util.concurrent.ListenableFuture future1 = executor.submitListenable(new TestTask(this.testName, -1)); - org.springframework.util.concurrent.ListenableFuture future2 = executor.submitListenable(new TestTask(this.testName, -1)); - shutdownExecutor(); - - try { - future1.get(1000, TimeUnit.MILLISECONDS); - } - catch (Exception ex) { - // ignore - } - Awaitility.await() - .atMost(5, TimeUnit.SECONDS) - .pollInterval(10, TimeUnit.MILLISECONDS) - .untilAsserted(() -> assertThatExceptionOfType(CancellationException.class) - .isThrownBy(() -> future2.get(1000, TimeUnit.MILLISECONDS))); - } - @Test void submitCompletableRunnableWithGetAfterShutdown() throws Exception { CompletableFuture future1 = executor.submitCompletable(new TestTask(this.testName, -1)); CompletableFuture future2 = executor.submitCompletable(new TestTask(this.testName, -1)); shutdownExecutor(); - - try { + assertThatExceptionOfType(TimeoutException.class).isThrownBy(() -> { future1.get(1000, TimeUnit.MILLISECONDS); - } - catch (Exception ex) { - // ignore - } - Awaitility.await() - .atMost(5, TimeUnit.SECONDS) - .pollInterval(10, TimeUnit.MILLISECONDS) - .untilAsserted(() -> assertThatExceptionOfType(TimeoutException.class) - .isThrownBy(() -> future2.get(1000, TimeUnit.MILLISECONDS))); + future2.get(1000, TimeUnit.MILLISECONDS); + }); } @Test @@ -245,57 +185,6 @@ void submitCallableWithGetAfterShutdown() throws Exception { Future future1 = executor.submit(new TestCallable(this.testName, -1)); Future future2 = executor.submit(new TestCallable(this.testName, -1)); shutdownExecutor(); - - try { - future1.get(1000, TimeUnit.MILLISECONDS); - } - catch (Exception ex) { - // ignore - } - Awaitility.await() - .atMost(5, TimeUnit.SECONDS) - .pollInterval(10, TimeUnit.MILLISECONDS) - .untilAsserted(() -> assertThatExceptionOfType(CancellationException.class) - .isThrownBy(() -> future2.get(1000, TimeUnit.MILLISECONDS))); - } - - @Test - @SuppressWarnings({ "deprecation", "removal" }) - void submitListenableCallable() { - TestCallable task = new TestCallable(this.testName, 1); - // Act - org.springframework.util.concurrent.ListenableFuture future = executor.submitListenable(task); - future.addCallback(result -> outcome = result, ex -> outcome = ex); - // Assert - Awaitility.await() - .atMost(5, TimeUnit.SECONDS) - .pollInterval(10, TimeUnit.MILLISECONDS) - .until(() -> future.isDone() && outcome != null); - assertThat(outcome.toString().substring(0, this.threadNamePrefix.length())).isEqualTo(this.threadNamePrefix); - } - - @Test - @SuppressWarnings({ "deprecation", "removal" }) - void submitFailingListenableCallable() { - TestCallable task = new TestCallable(this.testName, 0); - // Act - org.springframework.util.concurrent.ListenableFuture future = executor.submitListenable(task); - future.addCallback(result -> outcome = result, ex -> outcome = ex); - // Assert - Awaitility.await() - .dontCatchUncaughtExceptions() - .atMost(5, TimeUnit.SECONDS) - .pollInterval(10, TimeUnit.MILLISECONDS) - .until(() -> future.isDone() && outcome != null); - assertThat(outcome.getClass()).isSameAs(RuntimeException.class); - } - - @Test - @SuppressWarnings({ "deprecation", "removal" }) - void submitListenableCallableWithGetAfterShutdown() throws Exception { - org.springframework.util.concurrent.ListenableFuture future1 = executor.submitListenable(new TestCallable(this.testName, -1)); - org.springframework.util.concurrent.ListenableFuture future2 = executor.submitListenable(new TestCallable(this.testName, -1)); - shutdownExecutor(); assertThatExceptionOfType(CancellationException.class).isThrownBy(() -> { future1.get(1000, TimeUnit.MILLISECONDS); future2.get(1000, TimeUnit.MILLISECONDS); diff --git a/spring-context/src/test/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutorTests.java b/spring-context/src/test/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutorTests.java index b1357b121780..ab37b9ca949b 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutorTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/concurrent/ConcurrentTaskExecutorTests.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.NoOpRunnable; import org.springframework.core.task.TaskDecorator; import org.springframework.util.Assert; @@ -42,8 +43,7 @@ class ConcurrentTaskExecutorTests extends AbstractSchedulingTaskExecutorTests { @Override - @SuppressWarnings("removal") - protected org.springframework.core.task.AsyncListenableTaskExecutor buildExecutor() { + protected AsyncTaskExecutor buildExecutor() { concurrentExecutor.setThreadFactory(new CustomizableThreadFactory(this.threadNamePrefix)); return new ConcurrentTaskExecutor(concurrentExecutor); } diff --git a/spring-context/src/test/java/org/springframework/scheduling/concurrent/ConcurrentTaskSchedulerTests.java b/spring-context/src/test/java/org/springframework/scheduling/concurrent/ConcurrentTaskSchedulerTests.java index 11711fbc7192..e2380969f63c 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/concurrent/ConcurrentTaskSchedulerTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/concurrent/ConcurrentTaskSchedulerTests.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.scheduling.Trigger; import org.springframework.scheduling.TriggerContext; import org.springframework.util.ErrorHandler; @@ -52,8 +53,7 @@ class ConcurrentTaskSchedulerTests extends AbstractSchedulingTaskExecutorTests { @Override - @SuppressWarnings("removal") - protected org.springframework.core.task.AsyncListenableTaskExecutor buildExecutor() { + protected AsyncTaskExecutor buildExecutor() { threadFactory.setThreadNamePrefix(this.threadNamePrefix); scheduler.setTaskDecorator(runnable -> () -> { taskRun.set(true); @@ -79,26 +79,12 @@ void submitRunnableWithGetAfterShutdown() { // decorated Future cannot be cancelled on shutdown with ConcurrentTaskScheduler (see above) } - @Test - @SuppressWarnings("deprecation") - @Override - void submitListenableRunnableWithGetAfterShutdown() { - // decorated Future cannot be cancelled on shutdown with ConcurrentTaskScheduler (see above) - } - @Test @Override void submitCallableWithGetAfterShutdown() { // decorated Future cannot be cancelled on shutdown with ConcurrentTaskScheduler (see above) } - @Test - @SuppressWarnings("deprecation") - @Override - void submitListenableCallableWithGetAfterShutdown() { - // decorated Future cannot be cancelled on shutdown with ConcurrentTaskScheduler (see above) - } - @Test void executeFailingRunnableWithErrorHandler() { diff --git a/spring-context/src/test/java/org/springframework/scheduling/concurrent/DecoratedThreadPoolTaskExecutorTests.java b/spring-context/src/test/java/org/springframework/scheduling/concurrent/DecoratedThreadPoolTaskExecutorTests.java index d69ac85a642f..abd600226abe 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/concurrent/DecoratedThreadPoolTaskExecutorTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/concurrent/DecoratedThreadPoolTaskExecutorTests.java @@ -16,6 +16,7 @@ package org.springframework.scheduling.concurrent; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.scheduling.support.DelegatingErrorHandlingRunnable; import org.springframework.scheduling.support.TaskUtils; @@ -26,8 +27,7 @@ class DecoratedThreadPoolTaskExecutorTests extends AbstractSchedulingTaskExecutorTests { @Override - @SuppressWarnings("removal") - protected org.springframework.core.task.AsyncListenableTaskExecutor buildExecutor() { + protected AsyncTaskExecutor buildExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setTaskDecorator(runnable -> new DelegatingErrorHandlingRunnable(runnable, TaskUtils.LOG_AND_PROPAGATE_ERROR_HANDLER)); diff --git a/spring-context/src/test/java/org/springframework/scheduling/concurrent/SimpleAsyncTaskSchedulerTests.java b/spring-context/src/test/java/org/springframework/scheduling/concurrent/SimpleAsyncTaskSchedulerTests.java index b3cd12d52e3e..7bcab7833585 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/concurrent/SimpleAsyncTaskSchedulerTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/concurrent/SimpleAsyncTaskSchedulerTests.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.scheduling.Trigger; import org.springframework.scheduling.TriggerContext; import org.springframework.util.ErrorHandler; @@ -45,8 +46,7 @@ class SimpleAsyncTaskSchedulerTests extends AbstractSchedulingTaskExecutorTests @Override - @SuppressWarnings("removal") - protected org.springframework.core.task.AsyncListenableTaskExecutor buildExecutor() { + protected AsyncTaskExecutor buildExecutor() { scheduler.setTaskDecorator(runnable -> () -> { taskRun.set(true); runnable.run(); @@ -62,12 +62,6 @@ void submitRunnableWithGetAfterShutdown() { // decorated Future cannot be cancelled on shutdown with SimpleAsyncTaskScheduler } - @Test - @Override - void submitListenableRunnableWithGetAfterShutdown() { - // decorated Future cannot be cancelled on shutdown with SimpleAsyncTaskScheduler - } - @Test @Override void submitCompletableRunnableWithGetAfterShutdown() { @@ -80,13 +74,6 @@ void submitCallableWithGetAfterShutdown() { // decorated Future cannot be cancelled on shutdown with SimpleAsyncTaskScheduler } - @Test - @SuppressWarnings("deprecation") - @Override - void submitListenableCallableWithGetAfterShutdown() { - // decorated Future cannot be cancelled on shutdown with SimpleAsyncTaskScheduler - } - @Test @Override void submitCompletableCallableWithGetAfterShutdown() { diff --git a/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutorTests.java b/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutorTests.java index 5201af2a34ca..35a62f44b331 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutorTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutorTests.java @@ -23,6 +23,8 @@ import org.junit.jupiter.api.Test; +import org.springframework.core.task.AsyncTaskExecutor; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; @@ -41,8 +43,7 @@ class ThreadPoolTaskExecutorTests extends AbstractSchedulingTaskExecutorTests { @Override - @SuppressWarnings("removal") - protected org.springframework.core.task.AsyncListenableTaskExecutor buildExecutor() { + protected AsyncTaskExecutor buildExecutor() { executor.setThreadNamePrefix(this.threadNamePrefix); executor.setMaxPoolSize(1); executor.afterPropertiesSet(); diff --git a/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolTaskSchedulerTests.java b/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolTaskSchedulerTests.java index 63cbeafbcb22..712d69ba5a6b 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolTaskSchedulerTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolTaskSchedulerTests.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.scheduling.Trigger; import org.springframework.scheduling.TriggerContext; import org.springframework.util.ErrorHandler; @@ -49,8 +50,7 @@ class ThreadPoolTaskSchedulerTests extends AbstractSchedulingTaskExecutorTests { @Override - @SuppressWarnings("removal") - protected org.springframework.core.task.AsyncListenableTaskExecutor buildExecutor() { + protected AsyncTaskExecutor buildExecutor() { scheduler.setTaskDecorator(runnable -> () -> { taskRun.set(true); runnable.run(); diff --git a/spring-core/src/main/java/org/springframework/aot/hint/support/FilePatternResourceHintsRegistrar.java b/spring-core/src/main/java/org/springframework/aot/hint/support/FilePatternResourceHintsRegistrar.java index f5e3525a1e80..22119ee762e8 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/support/FilePatternResourceHintsRegistrar.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/support/FilePatternResourceHintsRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -35,9 +35,10 @@ * * @author Stephane Nicoll * @author Sam Brannen + * @author Juergen Hoeller * @since 6.0 */ -public class FilePatternResourceHintsRegistrar { +public final class FilePatternResourceHintsRegistrar { private final List classpathLocations; @@ -46,26 +47,16 @@ public class FilePatternResourceHintsRegistrar { private final List fileExtensions; - /** - * Create a new instance for the specified file prefixes, classpath locations, - * and file extensions. - * @param filePrefixes the file prefixes - * @param classpathLocations the classpath locations - * @param fileExtensions the file extensions (starting with a dot) - * @deprecated as of 6.0.12 in favor of {@linkplain #forClassPathLocations(String...) the builder} - */ - @Deprecated(since = "6.0.12", forRemoval = true) - public FilePatternResourceHintsRegistrar(List filePrefixes, List classpathLocations, + private FilePatternResourceHintsRegistrar(List filePrefixes, List classpathLocations, List fileExtensions) { - this.classpathLocations = validateClasspathLocations(classpathLocations); + this.classpathLocations = validateClassPathLocations(classpathLocations); this.filePrefixes = validateFilePrefixes(filePrefixes); this.fileExtensions = validateFileExtensions(fileExtensions); } - @Deprecated(since = "6.0.12", forRemoval = true) - public void registerHints(ResourceHints hints, @Nullable ClassLoader classLoader) { + private void registerHints(ResourceHints hints, @Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = (classLoader != null ? classLoader : getClass().getClassLoader()); List includes = new ArrayList<>(); for (String location : this.classpathLocations) { @@ -85,7 +76,7 @@ public void registerHints(ResourceHints hints, @Nullable ClassLoader classLoader /** * Configure the registrar with the specified - * {@linkplain Builder#withClasspathLocations(String...) classpath locations}. + * {@linkplain Builder#withClassPathLocations(String...) classpath locations}. * @param classpathLocations the classpath locations * @return a {@link Builder} to further configure the registrar * @since 6.0.12 @@ -97,17 +88,17 @@ public static Builder forClassPathLocations(String... classpathLocations) { /** * Configure the registrar with the specified - * {@linkplain Builder#withClasspathLocations(List) classpath locations}. + * {@linkplain Builder#withClassPathLocations(List) classpath locations}. * @param classpathLocations the classpath locations * @return a {@link Builder} to further configure the registrar * @since 6.0.12 * @see #forClassPathLocations(String...) */ public static Builder forClassPathLocations(List classpathLocations) { - return new Builder().withClasspathLocations(classpathLocations); + return new Builder().withClassPathLocations(classpathLocations); } - private static List validateClasspathLocations(List classpathLocations) { + private static List validateClassPathLocations(List classpathLocations) { Assert.notEmpty(classpathLocations, "At least one classpath location must be specified"); List parsedLocations = new ArrayList<>(); for (String location : classpathLocations) { @@ -160,6 +151,24 @@ private Builder() { // no-op } + /** + * Consider the specified classpath locations. + * @deprecated in favor of {@link #withClassPathLocations(String...)} + */ + @Deprecated(since = "7.0", forRemoval = true) + public Builder withClasspathLocations(String... classpathLocations) { + return withClassPathLocations(Arrays.asList(classpathLocations)); + } + + /** + * Consider the specified classpath locations. + * @deprecated in favor of {@link #withClassPathLocations(List)} + */ + @Deprecated(since = "7.0", forRemoval = true) + public Builder withClasspathLocations(List classpathLocations) { + return withClassPathLocations(classpathLocations); + } + /** * Consider the specified classpath locations. *

    A location can either be a special {@value ResourceUtils#CLASSPATH_URL_PREFIX} @@ -167,10 +176,11 @@ private Builder() { * An empty String represents the root of the classpath. * @param classpathLocations the classpath locations to consider * @return this builder - * @see #withClasspathLocations(List) + * @since 7.0 + * @see #withClassPathLocations(List) */ - public Builder withClasspathLocations(String... classpathLocations) { - return withClasspathLocations(Arrays.asList(classpathLocations)); + public Builder withClassPathLocations(String... classpathLocations) { + return withClassPathLocations(Arrays.asList(classpathLocations)); } /** @@ -180,10 +190,11 @@ public Builder withClasspathLocations(String... classpathLocations) { * An empty String represents the root of the classpath. * @param classpathLocations the classpath locations to consider * @return this builder - * @see #withClasspathLocations(String...) + * @since 7.0 + * @see #withClassPathLocations(String...) */ - public Builder withClasspathLocations(List classpathLocations) { - this.classpathLocations.addAll(validateClasspathLocations(classpathLocations)); + public Builder withClassPathLocations(List classpathLocations) { + this.classpathLocations.addAll(validateClassPathLocations(classpathLocations)); return this; } @@ -235,7 +246,6 @@ public Builder withFileExtensions(List fileExtensions) { return this; } - private FilePatternResourceHintsRegistrar build() { return new FilePatternResourceHintsRegistrar(this.filePrefixes, this.classpathLocations, this.fileExtensions); diff --git a/spring-core/src/main/java/org/springframework/core/task/AsyncListenableTaskExecutor.java b/spring-core/src/main/java/org/springframework/core/task/AsyncListenableTaskExecutor.java deleted file mode 100644 index a89a7efd126b..000000000000 --- a/spring-core/src/main/java/org/springframework/core/task/AsyncListenableTaskExecutor.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2002-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.core.task; - -import java.util.concurrent.Callable; - -/** - * Extension of the {@link AsyncTaskExecutor} interface, adding the capability to submit - * tasks for {@code ListenableFutures}. - * - * @author Arjen Poutsma - * @since 4.0 - * @deprecated as of 6.0, in favor of - * {@link AsyncTaskExecutor#submitCompletable(Runnable)} and - * {@link AsyncTaskExecutor#submitCompletable(Callable)} - */ -@Deprecated(since = "6.0", forRemoval = true) -@SuppressWarnings("removal") -public interface AsyncListenableTaskExecutor extends AsyncTaskExecutor { - - /** - * Submit a {@code Runnable} task for execution, receiving a {@code ListenableFuture} - * representing that task. The Future will return a {@code null} result upon completion. - * @param task the {@code Runnable} to execute (never {@code null}) - * @return a {@code ListenableFuture} representing pending completion of the task - * @throws TaskRejectedException if the given task was not accepted - * @deprecated in favor of {@link AsyncTaskExecutor#submitCompletable(Runnable)} - */ - @Deprecated(since = "6.0", forRemoval = true) - org.springframework.util.concurrent.ListenableFuture submitListenable(Runnable task); - - /** - * Submit a {@code Callable} task for execution, receiving a {@code ListenableFuture} - * representing that task. The Future will return the Callable's result upon - * completion. - * @param task the {@code Callable} to execute (never {@code null}) - * @return a {@code ListenableFuture} representing pending completion of the task - * @throws TaskRejectedException if the given task was not accepted - * @deprecated in favor of {@link AsyncTaskExecutor#submitCompletable(Callable)} - */ - @Deprecated(since = "6.0", forRemoval = true) - org.springframework.util.concurrent.ListenableFuture submitListenable(Callable task); - -} diff --git a/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java b/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java index e2d2363373fb..413a80367d1f 100644 --- a/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java +++ b/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java @@ -28,8 +28,6 @@ import org.springframework.util.Assert; import org.springframework.util.ConcurrencyThrottleSupport; import org.springframework.util.CustomizableThreadCreator; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.ListenableFutureTask; /** * {@link TaskExecutor} implementation that fires up a new Thread for each task, @@ -58,9 +56,9 @@ * @see org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler * @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor */ -@SuppressWarnings({"serial", "removal"}) +@SuppressWarnings("serial") public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator - implements AsyncListenableTaskExecutor, Serializable, AutoCloseable { + implements AsyncTaskExecutor, Serializable, AutoCloseable { /** * Permit any number of concurrent invocations: that is, don't throttle concurrency. @@ -294,22 +292,6 @@ public Future submit(Callable task) { return future; } - @SuppressWarnings("deprecation") - @Override - public ListenableFuture submitListenable(Runnable task) { - ListenableFutureTask future = new ListenableFutureTask<>(task, null); - execute(future, TIMEOUT_INDEFINITE); - return future; - } - - @SuppressWarnings("deprecation") - @Override - public ListenableFuture submitListenable(Callable task) { - ListenableFutureTask future = new ListenableFutureTask<>(task); - execute(future, TIMEOUT_INDEFINITE); - return future; - } - /** * Template method for the actual execution of a task. *

    The default implementation creates a new Thread and starts it. diff --git a/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java b/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java index 0669f7ea7c81..a9878aab2e23 100644 --- a/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java +++ b/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java @@ -23,13 +23,11 @@ import java.util.concurrent.FutureTask; import java.util.concurrent.RejectedExecutionException; -import org.springframework.core.task.AsyncListenableTaskExecutor; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.TaskDecorator; import org.springframework.core.task.TaskRejectedException; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.concurrent.ListenableFuture; -import org.springframework.util.concurrent.ListenableFutureTask; /** * Adapter that takes a JDK {@code java.util.concurrent.Executor} and @@ -43,8 +41,8 @@ * @see java.util.concurrent.ExecutorService * @see java.util.concurrent.Executors */ -@SuppressWarnings({"deprecation", "removal"}) -public class TaskExecutorAdapter implements AsyncListenableTaskExecutor { +@SuppressWarnings("deprecation") +public class TaskExecutorAdapter implements AsyncTaskExecutor { private final Executor concurrentExecutor; @@ -133,30 +131,6 @@ public Future submit(Callable task) { } } - @Override - public ListenableFuture submitListenable(Runnable task) { - try { - ListenableFutureTask future = new ListenableFutureTask<>(task, null); - doExecute(this.concurrentExecutor, this.taskDecorator, future); - return future; - } - catch (RejectedExecutionException ex) { - throw new TaskRejectedException(this.concurrentExecutor, task, ex); - } - } - - @Override - public ListenableFuture submitListenable(Callable task) { - try { - ListenableFutureTask future = new ListenableFutureTask<>(task); - doExecute(this.concurrentExecutor, this.taskDecorator, future); - return future; - } - catch (RejectedExecutionException ex) { - throw new TaskRejectedException(this.concurrentExecutor, task, ex); - } - } - /** * Actually execute the given {@code Runnable} (which may be a user-supplied task diff --git a/spring-core/src/main/java/org/springframework/util/MimeType.java b/spring-core/src/main/java/org/springframework/util/MimeType.java index d4b670718c7f..5a328468429b 100644 --- a/spring-core/src/main/java/org/springframework/util/MimeType.java +++ b/spring-core/src/main/java/org/springframework/util/MimeType.java @@ -23,7 +23,6 @@ import java.util.BitSet; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Locale; @@ -695,48 +694,4 @@ private static Map addCharsetParameter(Charset charset, Map the type of mime types that may be compared by this comparator - * @deprecated As of 6.0, with no direct replacement - */ - @Deprecated(since = "6.0", forRemoval = true) - public static class SpecificityComparator implements Comparator { - - @Override - public int compare(T mimeType1, T mimeType2) { - if (mimeType1.isWildcardType() && !mimeType2.isWildcardType()) { // */* < audio/* - return 1; - } - else if (mimeType2.isWildcardType() && !mimeType1.isWildcardType()) { // audio/* > */* - return -1; - } - else if (!mimeType1.getType().equals(mimeType2.getType())) { // audio/basic == text/html - return 0; - } - else { // mediaType1.getType().equals(mediaType2.getType()) - if (mimeType1.isWildcardSubtype() && !mimeType2.isWildcardSubtype()) { // audio/* < audio/basic - return 1; - } - else if (mimeType2.isWildcardSubtype() && !mimeType1.isWildcardSubtype()) { // audio/basic > audio/* - return -1; - } - else if (!mimeType1.getSubtype().equals(mimeType2.getSubtype())) { // audio/basic == audio/wave - return 0; - } - else { // mediaType2.getSubtype().equals(mediaType2.getSubtype()) - return compareParameters(mimeType1, mimeType2); - } - } - } - - protected int compareParameters(T mimeType1, T mimeType2) { - int paramsSize1 = mimeType1.getParameters().size(); - int paramsSize2 = mimeType2.getParameters().size(); - return Integer.compare(paramsSize2, paramsSize1); // audio/basic;level=1 < audio/basic - } - } - } diff --git a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java index 93c946ec6760..f3a9f2b688c8 100644 --- a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java +++ b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -22,7 +22,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -51,14 +50,6 @@ public abstract class MimeTypeUtils { 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; - /** - * Comparator formally used by {@link #sortBySpecificity(List)}. - * @deprecated As of 6.0, with no direct replacement - */ - @SuppressWarnings("removal") - @Deprecated(since = "6.0", forRemoval = true) - public static final Comparator SPECIFICITY_COMPARATOR = new MimeType.SpecificityComparator<>(); - /** * Public constant mime type that includes all media ranges (i.e. "*/*"). */ diff --git a/spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java b/spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java index 78cb9a2c1ffc..8e1d6094c642 100644 --- a/spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java +++ b/spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java @@ -49,24 +49,6 @@ public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuf this(placeholderPrefix, placeholderSuffix, null, null, true); } - /** - * Create a new {@code PropertyPlaceholderHelper} that uses the supplied prefix and suffix. - * @param placeholderPrefix the prefix that denotes the start of a placeholder - * @param placeholderSuffix the suffix that denotes the end of a placeholder - * @param valueSeparator the separating character between the placeholder variable - * and the associated default value, if any - * @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders should - * be ignored ({@code true}) or cause an exception ({@code false}) - * @deprecated as of 6.2, in favor of - * {@link PropertyPlaceholderHelper#PropertyPlaceholderHelper(String, String, String, Character, boolean)} - */ - @Deprecated(since = "6.2", forRemoval = true) - public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, - @Nullable String valueSeparator, boolean ignoreUnresolvablePlaceholders) { - - this(placeholderPrefix, placeholderSuffix, valueSeparator, null, ignoreUnresolvablePlaceholders); - } - /** * Create a new {@code PropertyPlaceholderHelper} that uses the supplied prefix and suffix. * @param placeholderPrefix the prefix that denotes the start of a placeholder diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/CompletableToListenableFutureAdapter.java b/spring-core/src/main/java/org/springframework/util/concurrent/CompletableToListenableFutureAdapter.java deleted file mode 100644 index 0c50053c327f..000000000000 --- a/spring-core/src/main/java/org/springframework/util/concurrent/CompletableToListenableFutureAdapter.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2002-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.util.concurrent; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * Adapts a {@link CompletableFuture} or {@link CompletionStage} into a - * Spring {@link ListenableFuture}. - * - * @author Sebastien Deleuze - * @author Juergen Hoeller - * @since 4.2 - * @param the result type returned by this Future's {@code get} method - * @deprecated as of 6.0, with no concrete replacement - */ -@Deprecated(since = "6.0", forRemoval = true) -@SuppressWarnings("removal") -public class CompletableToListenableFutureAdapter implements ListenableFuture { - - private final CompletableFuture completableFuture; - - private final ListenableFutureCallbackRegistry callbacks = new ListenableFutureCallbackRegistry<>(); - - - /** - * Create a new adapter for the given {@link CompletionStage}. - * @since 4.3.7 - */ - public CompletableToListenableFutureAdapter(CompletionStage completionStage) { - this(completionStage.toCompletableFuture()); - } - - /** - * Create a new adapter for the given {@link CompletableFuture}. - */ - public CompletableToListenableFutureAdapter(CompletableFuture completableFuture) { - this.completableFuture = completableFuture; - this.completableFuture.whenComplete((result, ex) -> { - if (ex != null) { - this.callbacks.failure(ex); - } - else { - this.callbacks.success(result); - } - }); - } - - - @Override - public void addCallback(ListenableFutureCallback callback) { - this.callbacks.addCallback(callback); - } - - @Override - public void addCallback(SuccessCallback successCallback, FailureCallback failureCallback) { - this.callbacks.addSuccessCallback(successCallback); - this.callbacks.addFailureCallback(failureCallback); - } - - @Override - public CompletableFuture completable() { - return this.completableFuture; - } - - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return this.completableFuture.cancel(mayInterruptIfRunning); - } - - @Override - public boolean isCancelled() { - return this.completableFuture.isCancelled(); - } - - @Override - public boolean isDone() { - return this.completableFuture.isDone(); - } - - @Override - public T get() throws InterruptedException, ExecutionException { - return this.completableFuture.get(); - } - - @Override - public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - return this.completableFuture.get(timeout, unit); - } - -} diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/FailureCallback.java b/spring-core/src/main/java/org/springframework/util/concurrent/FailureCallback.java deleted file mode 100644 index e24adfdd6f78..000000000000 --- a/spring-core/src/main/java/org/springframework/util/concurrent/FailureCallback.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2002-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.util.concurrent; - -import java.util.function.BiConsumer; - -/** - * Failure callback for a {@link ListenableFuture}. - * - * @author Sebastien Deleuze - * @since 4.1 - * @deprecated as of 6.0, in favor of - * {@link java.util.concurrent.CompletableFuture#whenComplete(BiConsumer)} - */ -@Deprecated(since = "6.0", forRemoval = true) -@FunctionalInterface -public interface FailureCallback { - - /** - * Called when the {@link ListenableFuture} completes with failure. - *

    Note that Exceptions raised by this method are ignored. - * @param ex the failure - */ - void onFailure(Throwable ex); - -} diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java deleted file mode 100644 index fc8c0e5f2072..000000000000 --- a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2002-2022 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.util.concurrent; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; -import java.util.function.BiConsumer; - -/** - * Extend {@link Future} with the capability to accept completion callbacks. - * If the future has completed when the callback is added, the callback is - * triggered immediately. - * - *

    Inspired by {@code com.google.common.util.concurrent.ListenableFuture}. - * - * @author Arjen Poutsma - * @author Sebastien Deleuze - * @author Juergen Hoeller - * @since 4.0 - * @param the result type returned by this Future's {@code get} method - * @deprecated as of 6.0, in favor of {@link CompletableFuture} - */ -@Deprecated(since = "6.0", forRemoval = true) -public interface ListenableFuture extends Future { - - /** - * Register the given {@code ListenableFutureCallback}. - * @param callback the callback to register - * @deprecated as of 6.0, in favor of - * {@link CompletableFuture#whenComplete(BiConsumer)} - */ - @Deprecated(since = "6.0", forRemoval = true) - @SuppressWarnings("removal") - void addCallback(ListenableFutureCallback callback); - - /** - * Java 8 lambda-friendly alternative with success and failure callbacks. - * @param successCallback the success callback - * @param failureCallback the failure callback - * @since 4.1 - * @deprecated as of 6.0, in favor of - * {@link CompletableFuture#whenComplete(BiConsumer)} - */ - @Deprecated(since = "6.0", forRemoval = true) - @SuppressWarnings("removal") - void addCallback(SuccessCallback successCallback, FailureCallback failureCallback); - - - /** - * Expose this {@link ListenableFuture} as a JDK {@link CompletableFuture}. - * @since 5.0 - */ - @SuppressWarnings("NullAway") - default CompletableFuture completable() { - CompletableFuture completable = new DelegatingCompletableFuture<>(this); - addCallback(completable::complete, completable::completeExceptionally); - return completable; - } - -} diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java deleted file mode 100644 index 0ddc5bc42908..000000000000 --- a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2002-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.util.concurrent; - -import java.util.concurrent.ExecutionException; - -import org.springframework.lang.Nullable; - -/** - * Abstract class that adapts a {@link ListenableFuture} parameterized over S into a - * {@code ListenableFuture} parameterized over T. All methods are delegated to the - * adaptee, where {@link #get()}, {@link #get(long, java.util.concurrent.TimeUnit)}, - * and {@link ListenableFutureCallback#onSuccess(Object)} call {@link #adapt(Object)} - * on the adaptee's result. - * - * @author Arjen Poutsma - * @since 4.0 - * @param the type of this {@code Future} - * @param the type of the adaptee's {@code Future} - * @deprecated as of 6.0, in favor of - * {@link java.util.concurrent.CompletableFuture} - */ -@Deprecated(since = "6.0", forRemoval = true) -@SuppressWarnings("removal") -public abstract class ListenableFutureAdapter extends FutureAdapter implements ListenableFuture { - - /** - * Construct a new {@code ListenableFutureAdapter} with the given adaptee. - * @param adaptee the future to adapt to - */ - protected ListenableFutureAdapter(ListenableFuture adaptee) { - super(adaptee); - } - - - @Override - public void addCallback(final ListenableFutureCallback callback) { - addCallback(callback, callback); - } - - @Override - public void addCallback(final SuccessCallback successCallback, final FailureCallback failureCallback) { - ListenableFuture listenableAdaptee = (ListenableFuture) getAdaptee(); - listenableAdaptee.addCallback(new ListenableFutureCallback<>() { - @Override - public void onSuccess(@Nullable S result) { - T adapted = null; - if (result != null) { - try { - adapted = adaptInternal(result); - } - catch (ExecutionException ex) { - Throwable cause = ex.getCause(); - onFailure(cause != null ? cause : ex); - return; - } - catch (Throwable ex) { - onFailure(ex); - return; - } - } - successCallback.onSuccess(adapted); - } - - @Override - public void onFailure(Throwable ex) { - failureCallback.onFailure(ex); - } - }); - } - -} diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java deleted file mode 100644 index dfd0f4e24480..000000000000 --- a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2002-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.util.concurrent; - -import java.util.function.BiConsumer; - -/** - * Callback mechanism for the outcome, success or failure, from a - * {@link ListenableFuture}. - * - * @author Arjen Poutsma - * @author Sebastien Deleuze - * @since 4.0 - * @param the result type - * @deprecated as of 6.0, in favor of - * {@link java.util.concurrent.CompletableFuture#whenComplete(BiConsumer)} - */ -@Deprecated(since = "6.0", forRemoval = true) -@SuppressWarnings("removal") -public interface ListenableFutureCallback extends SuccessCallback, FailureCallback { - -} diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java deleted file mode 100644 index 178d549c162c..000000000000 --- a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright 2002-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.util.concurrent; - -import java.util.ArrayDeque; -import java.util.Queue; - -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * Helper class for {@link ListenableFuture} implementations that maintains a queue - * of success and failure callbacks and helps to notify them. - * - *

    Inspired by {@code com.google.common.util.concurrent.ExecutionList}. - * - * @author Arjen Poutsma - * @author Sebastien Deleuze - * @author Rossen Stoyanchev - * @since 4.0 - * @param the callback result type - * @deprecated as of 6.0, with no concrete replacement - */ -@Deprecated(since = "6.0", forRemoval = true) -@SuppressWarnings("removal") -public class ListenableFutureCallbackRegistry { - - private final Queue> successCallbacks = new ArrayDeque<>(1); - - private final Queue failureCallbacks = new ArrayDeque<>(1); - - private State state = State.NEW; - - @Nullable - private Object result; - - private final Object mutex = new Object(); - - - /** - * Add the given callback to this registry. - * @param callback the callback to add - */ - public void addCallback(ListenableFutureCallback callback) { - Assert.notNull(callback, "'callback' must not be null"); - synchronized (this.mutex) { - switch (this.state) { - case NEW -> { - this.successCallbacks.add(callback); - this.failureCallbacks.add(callback); - } - case SUCCESS -> notifySuccess(callback); - case FAILURE -> notifyFailure(callback); - } - } - } - - @SuppressWarnings("unchecked") - private void notifySuccess(SuccessCallback callback) { - try { - callback.onSuccess((T) this.result); - } - catch (Throwable ex) { - // Ignore - } - } - - private void notifyFailure(FailureCallback callback) { - Assert.state(this.result instanceof Throwable, "No Throwable result for failure state"); - try { - callback.onFailure((Throwable) this.result); - } - catch (Throwable ex) { - // Ignore - } - } - - /** - * Add the given success callback to this registry. - * @param callback the success callback to add - * @since 4.1 - */ - public void addSuccessCallback(SuccessCallback callback) { - Assert.notNull(callback, "'callback' must not be null"); - synchronized (this.mutex) { - switch (this.state) { - case NEW -> this.successCallbacks.add(callback); - case SUCCESS -> notifySuccess(callback); - } - } - } - - /** - * Add the given failure callback to this registry. - * @param callback the failure callback to add - * @since 4.1 - */ - public void addFailureCallback(FailureCallback callback) { - Assert.notNull(callback, "'callback' must not be null"); - synchronized (this.mutex) { - switch (this.state) { - case NEW -> this.failureCallbacks.add(callback); - case FAILURE -> notifyFailure(callback); - } - } - } - - /** - * Trigger a {@link ListenableFutureCallback#onSuccess(Object)} call on all - * added callbacks with the given result. - * @param result the result to trigger the callbacks with - */ - public void success(@Nullable T result) { - synchronized (this.mutex) { - this.state = State.SUCCESS; - this.result = result; - SuccessCallback callback; - while ((callback = this.successCallbacks.poll()) != null) { - notifySuccess(callback); - } - } - } - - /** - * Trigger a {@link ListenableFutureCallback#onFailure(Throwable)} call on all - * added callbacks with the given {@code Throwable}. - * @param ex the exception to trigger the callbacks with - */ - public void failure(Throwable ex) { - synchronized (this.mutex) { - this.state = State.FAILURE; - this.result = ex; - FailureCallback callback; - while ((callback = this.failureCallbacks.poll()) != null) { - notifyFailure(callback); - } - } - } - - - private enum State {NEW, SUCCESS, FAILURE} - -} diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureTask.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureTask.java deleted file mode 100644 index 271fc083263c..000000000000 --- a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureTask.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2002-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.util.concurrent; - -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.FutureTask; - -import org.springframework.lang.Nullable; - -/** - * Extension of {@link FutureTask} that implements {@link ListenableFuture}. - * - * @author Arjen Poutsma - * @since 4.0 - * @param the result type returned by this Future's {@code get} method - * @deprecated as of 6.0, with no concrete replacement - */ -@Deprecated(since = "6.0", forRemoval = true) -@SuppressWarnings("removal") -public class ListenableFutureTask extends FutureTask implements ListenableFuture { - - private final ListenableFutureCallbackRegistry callbacks = new ListenableFutureCallbackRegistry<>(); - - - /** - * Create a new {@code ListenableFutureTask} that will, upon running, - * execute the given {@link Callable}. - * @param callable the callable task - */ - public ListenableFutureTask(Callable callable) { - super(callable); - } - - /** - * Create a {@code ListenableFutureTask} that will, upon running, - * execute the given {@link Runnable}, and arrange that {@link #get()} - * will return the given result on successful completion. - * @param runnable the runnable task - * @param result the result to return on successful completion - */ - public ListenableFutureTask(Runnable runnable, @Nullable T result) { - super(runnable, result); - } - - - @Override - public void addCallback(ListenableFutureCallback callback) { - this.callbacks.addCallback(callback); - } - - @Override - public void addCallback(SuccessCallback successCallback, FailureCallback failureCallback) { - this.callbacks.addSuccessCallback(successCallback); - this.callbacks.addFailureCallback(failureCallback); - } - - @Override - @SuppressWarnings("NullAway") - public CompletableFuture completable() { - CompletableFuture completable = new DelegatingCompletableFuture<>(this); - this.callbacks.addSuccessCallback(completable::complete); - this.callbacks.addFailureCallback(completable::completeExceptionally); - return completable; - } - - - @Override - protected void done() { - Throwable cause; - try { - T result = get(); - this.callbacks.success(result); - return; - } - catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - return; - } - catch (ExecutionException ex) { - cause = ex.getCause(); - if (cause == null) { - cause = ex; - } - } - catch (Throwable ex) { - cause = ex; - } - this.callbacks.failure(cause); - } - -} diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/MonoToListenableFutureAdapter.java b/spring-core/src/main/java/org/springframework/util/concurrent/MonoToListenableFutureAdapter.java deleted file mode 100644 index f55162a08569..000000000000 --- a/spring-core/src/main/java/org/springframework/util/concurrent/MonoToListenableFutureAdapter.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2002-2022 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.util.concurrent; - -import reactor.core.publisher.Mono; - -/** - * Adapts a {@link Mono} into a {@link ListenableFuture} by obtaining a - * {@code CompletableFuture} from the {@code Mono} via {@link Mono#toFuture()} - * and then adapting it with {@link CompletableToListenableFutureAdapter}. - * - * @author Rossen Stoyanchev - * @author Stephane Maldini - * @since 5.1 - * @param the object type - * @deprecated as of 6.0, in favor of {@link Mono#toFuture()} - */ -@Deprecated(since = "6.0") -@SuppressWarnings("removal") -public class MonoToListenableFutureAdapter extends CompletableToListenableFutureAdapter { - - public MonoToListenableFutureAdapter(Mono mono) { - super(mono.toFuture()); - } - -} diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/SettableListenableFuture.java b/spring-core/src/main/java/org/springframework/util/concurrent/SettableListenableFuture.java deleted file mode 100644 index e7cb0c755672..000000000000 --- a/spring-core/src/main/java/org/springframework/util/concurrent/SettableListenableFuture.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2002-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.util.concurrent; - -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * A {@link ListenableFuture} whose value can be set via {@link #set(Object)} - * or {@link #setException(Throwable)}. It may also get cancelled. - * - *

    Inspired by {@code com.google.common.util.concurrent.SettableFuture}. - * - * @author Mattias Severson - * @author Rossen Stoyanchev - * @author Juergen Hoeller - * @since 4.1 - * @param the result type returned by this Future's {@code get} method - * @deprecated as of 6.0, in favor of {@link CompletableFuture} - */ -@Deprecated(since = "6.0", forRemoval = true) -@SuppressWarnings("removal") -public class SettableListenableFuture implements ListenableFuture { - - private static final Callable DUMMY_CALLABLE = () -> { - throw new IllegalStateException("Should never be called"); - }; - - - private final SettableTask settableTask = new SettableTask<>(); - - - /** - * Set the value of this future. This method will return {@code true} if the - * value was set successfully, or {@code false} if the future has already been - * set or cancelled. - * @param value the value that will be set - * @return {@code true} if the value was successfully set, else {@code false} - */ - public boolean set(@Nullable T value) { - return this.settableTask.setResultValue(value); - } - - /** - * Set the exception of this future. This method will return {@code true} if the - * exception was set successfully, or {@code false} if the future has already been - * set or cancelled. - * @param exception the value that will be set - * @return {@code true} if the exception was successfully set, else {@code false} - */ - public boolean setException(Throwable exception) { - Assert.notNull(exception, "Exception must not be null"); - return this.settableTask.setExceptionResult(exception); - } - - - @Override - public void addCallback(ListenableFutureCallback callback) { - this.settableTask.addCallback(callback); - } - - @Override - public void addCallback(SuccessCallback successCallback, FailureCallback failureCallback) { - this.settableTask.addCallback(successCallback, failureCallback); - } - - @Override - public CompletableFuture completable() { - return this.settableTask.completable(); - } - - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - boolean cancelled = this.settableTask.cancel(mayInterruptIfRunning); - if (cancelled && mayInterruptIfRunning) { - interruptTask(); - } - return cancelled; - } - - @Override - public boolean isCancelled() { - return this.settableTask.isCancelled(); - } - - @Override - public boolean isDone() { - return this.settableTask.isDone(); - } - - /** - * Retrieve the value. - *

    This method returns the value if it has been set via {@link #set(Object)}, - * throws an {@link java.util.concurrent.ExecutionException} if an exception has - * been set via {@link #setException(Throwable)}, or throws a - * {@link java.util.concurrent.CancellationException} if the future has been cancelled. - * @return the value associated with this future - */ - @Nullable - @Override - public T get() throws InterruptedException, ExecutionException { - return this.settableTask.get(); - } - - /** - * Retrieve the value. - *

    This method returns the value if it has been set via {@link #set(Object)}, - * throws an {@link java.util.concurrent.ExecutionException} if an exception has - * been set via {@link #setException(Throwable)}, or throws a - * {@link java.util.concurrent.CancellationException} if the future has been cancelled. - * @param timeout the maximum time to wait - * @param unit the unit of the timeout argument - * @return the value associated with this future - */ - @Nullable - @Override - public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - return this.settableTask.get(timeout, unit); - } - - /** - * Subclasses can override this method to implement interruption of the future's - * computation. The method is invoked automatically by a successful call to - * {@link #cancel(boolean) cancel(true)}. - *

    The default implementation is empty. - */ - protected void interruptTask() { - } - - - private static class SettableTask extends ListenableFutureTask { - - @Nullable - private volatile Thread completingThread; - - @SuppressWarnings("unchecked") - public SettableTask() { - super((Callable) DUMMY_CALLABLE); - } - - public boolean setResultValue(@Nullable T value) { - set(value); - return checkCompletingThread(); - } - - public boolean setExceptionResult(Throwable exception) { - setException(exception); - return checkCompletingThread(); - } - - @Override - protected void done() { - if (!isCancelled()) { - // Implicitly invoked by set/setException: store current thread for - // determining whether the given result has actually triggered completion - // (since FutureTask.set/setException unfortunately don't expose that) - this.completingThread = Thread.currentThread(); - } - super.done(); - } - - private boolean checkCompletingThread() { - boolean check = (this.completingThread == Thread.currentThread()); - if (check) { - this.completingThread = null; // only first match actually counts - } - return check; - } - } - -} diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/SuccessCallback.java b/spring-core/src/main/java/org/springframework/util/concurrent/SuccessCallback.java deleted file mode 100644 index 8ecba36eb61f..000000000000 --- a/spring-core/src/main/java/org/springframework/util/concurrent/SuccessCallback.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2002-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.util.concurrent; - -import java.util.function.BiConsumer; - -import org.springframework.lang.Nullable; - -/** - * Success callback for a {@link ListenableFuture}. - * - * @author Sebastien Deleuze - * @since 4.1 - * @param the result type - * @deprecated as of 6.0, in favor of - * {@link java.util.concurrent.CompletableFuture#whenComplete(BiConsumer)} - */ -@Deprecated(since = "6.0", forRemoval = true) -@FunctionalInterface -public interface SuccessCallback { - - /** - * Called when the {@link ListenableFuture} completes with success. - *

    Note that Exceptions raised by this method are ignored. - * @param result the result - */ - void onSuccess(@Nullable T result); - -} diff --git a/spring-core/src/test/java/org/springframework/aot/hint/support/FilePatternResourceHintsRegistrarTests.java b/spring-core/src/test/java/org/springframework/aot/hint/support/FilePatternResourceHintsRegistrarTests.java index a8c93b1ec9da..a781a6cb873b 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/support/FilePatternResourceHintsRegistrarTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/support/FilePatternResourceHintsRegistrarTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -78,7 +78,7 @@ void registerWithMultipleFilePrefixes() { @Test void registerWithMultipleClasspathLocations() { - FilePatternResourceHintsRegistrar.forClassPathLocations("").withClasspathLocations("META-INF") + FilePatternResourceHintsRegistrar.forClassPathLocations("").withClassPathLocations("META-INF") .withFilePrefixes("test").withFileExtensions(".txt") .registerHints(this.hints, null); assertThat(this.hints.resourcePatternHints()).singleElement() @@ -133,7 +133,7 @@ void registerWithClasspathLocationUsingResourceClasspathPrefixAndTrailingSlash() @Test void registerWithNonExistingLocationDoesNotRegisterHint() { FilePatternResourceHintsRegistrar.forClassPathLocations("does-not-exist/") - .withClasspathLocations("another-does-not-exist/") + .withClassPathLocations("another-does-not-exist/") .withFilePrefixes("test").withFileExtensions(".txt") .registerHints(this.hints, null); assertThat(this.hints.resourcePatternHints()).isEmpty(); diff --git a/spring-core/src/test/java/org/springframework/util/concurrent/ListenableFutureTaskTests.java b/spring-core/src/test/java/org/springframework/util/concurrent/ListenableFutureTaskTests.java deleted file mode 100644 index 920b5ebd448d..000000000000 --- a/spring-core/src/test/java/org/springframework/util/concurrent/ListenableFutureTaskTests.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2002-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.util.concurrent; - -import java.io.IOException; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * @author Arjen Poutsma - * @author Sebastien Deleuze - */ -@SuppressWarnings({"deprecation", "removal"}) -class ListenableFutureTaskTests { - - @Test - void success() throws Exception { - final String s = "Hello World"; - Callable callable = () -> s; - - ListenableFutureTask task = new ListenableFutureTask<>(callable); - task.addCallback(new ListenableFutureCallback<>() { - @Override - public void onSuccess(String result) { - assertThat(result).isEqualTo(s); - } - - @Override - public void onFailure(Throwable ex) { - throw new AssertionError(ex.getMessage(), ex); - } - }); - task.run(); - - assertThat(task.get()).isSameAs(s); - assertThat(task.completable().get()).isSameAs(s); - task.completable().thenAccept(v -> assertThat(v).isSameAs(s)); - } - - @Test - void failure() { - final String s = "Hello World"; - Callable callable = () -> { - throw new IOException(s); - }; - - ListenableFutureTask task = new ListenableFutureTask<>(callable); - task.addCallback(new ListenableFutureCallback<>() { - @Override - public void onSuccess(String result) { - fail("onSuccess not expected"); - } - - @Override - public void onFailure(Throwable ex) { - assertThat(ex.getMessage()).isEqualTo(s); - } - }); - task.run(); - - assertThatExceptionOfType(ExecutionException.class) - .isThrownBy(task::get) - .havingCause() - .withMessage(s); - assertThatExceptionOfType(ExecutionException.class) - .isThrownBy(task.completable()::get) - .havingCause() - .withMessage(s); - } - - @Test - void successWithLambdas() throws Exception { - final String s = "Hello World"; - Callable callable = () -> s; - - SuccessCallback successCallback = mock(); - FailureCallback failureCallback = mock(); - ListenableFutureTask task = new ListenableFutureTask<>(callable); - task.addCallback(successCallback, failureCallback); - task.run(); - verify(successCallback).onSuccess(s); - verifyNoInteractions(failureCallback); - - assertThat(task.get()).isSameAs(s); - assertThat(task.completable().get()).isSameAs(s); - task.completable().thenAccept(v -> assertThat(v).isSameAs(s)); - } - - @Test - void failureWithLambdas() { - final String s = "Hello World"; - IOException ex = new IOException(s); - Callable callable = () -> { - throw ex; - }; - - SuccessCallback successCallback = mock(); - FailureCallback failureCallback = mock(); - ListenableFutureTask task = new ListenableFutureTask<>(callable); - task.addCallback(successCallback, failureCallback); - task.run(); - verify(failureCallback).onFailure(ex); - verifyNoInteractions(successCallback); - - assertThatExceptionOfType(ExecutionException.class) - .isThrownBy(task::get) - .havingCause() - .withMessage(s); - assertThatExceptionOfType(ExecutionException.class) - .isThrownBy(task.completable()::get) - .havingCause() - .withMessage(s); - } - -} diff --git a/spring-core/src/test/java/org/springframework/util/concurrent/MonoToListenableFutureAdapterTests.java b/spring-core/src/test/java/org/springframework/util/concurrent/MonoToListenableFutureAdapterTests.java deleted file mode 100644 index 5b7a139f8407..000000000000 --- a/spring-core/src/test/java/org/springframework/util/concurrent/MonoToListenableFutureAdapterTests.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2002-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.util.concurrent; - -import java.time.Duration; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicReference; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link MonoToListenableFutureAdapter}. - * - * @author Rossen Stoyanchev - */ -@SuppressWarnings({"deprecation", "removal"}) -class MonoToListenableFutureAdapterTests { - - @Test - void success() { - String expected = "one"; - AtomicReference actual = new AtomicReference<>(); - ListenableFuture future = new MonoToListenableFutureAdapter<>(Mono.just(expected)); - future.addCallback(actual::set, actual::set); - - assertThat(actual.get()).isEqualTo(expected); - } - - @Test - @SuppressWarnings("deprecation") - void failure() { - Throwable expected = new IllegalStateException("oops"); - AtomicReference actual = new AtomicReference<>(); - ListenableFuture future = new MonoToListenableFutureAdapter<>(Mono.error(expected)); - future.addCallback(actual::set, actual::set); - - assertThat(actual.get()).isEqualTo(expected); - } - - @Test - void cancellation() { - Mono mono = Mono.delay(Duration.ofSeconds(60)); - Future future = new MonoToListenableFutureAdapter<>(mono); - - assertThat(future.cancel(true)).isTrue(); - assertThat(future.isCancelled()).isTrue(); - } - - @Test - void cancellationAfterTerminated() { - Future future = new MonoToListenableFutureAdapter<>(Mono.empty()); - - assertThat(future.cancel(true)).as("Should return false if task already completed").isFalse(); - assertThat(future.isCancelled()).isFalse(); - } - -} diff --git a/spring-core/src/test/java/org/springframework/util/concurrent/SettableListenableFutureTests.java b/spring-core/src/test/java/org/springframework/util/concurrent/SettableListenableFutureTests.java deleted file mode 100644 index d79296dd0990..000000000000 --- a/spring-core/src/test/java/org/springframework/util/concurrent/SettableListenableFutureTests.java +++ /dev/null @@ -1,414 +0,0 @@ -/* - * Copyright 2002-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.util.concurrent; - -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -/** - * @author Mattias Severson - * @author Juergen Hoeller - */ -@SuppressWarnings({"deprecation", "removal"}) -class SettableListenableFutureTests { - - private final SettableListenableFuture settableListenableFuture = new SettableListenableFuture<>(); - - - @Test - void validateInitialValues() { - assertThat(settableListenableFuture.isCancelled()).isFalse(); - assertThat(settableListenableFuture.isDone()).isFalse(); - } - - @Test - void returnsSetValue() throws ExecutionException, InterruptedException { - String string = "hello"; - assertThat(settableListenableFuture.set(string)).isTrue(); - assertThat(settableListenableFuture.get()).isEqualTo(string); - assertThat(settableListenableFuture.isCancelled()).isFalse(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - void returnsSetValueFromCompletable() throws ExecutionException, InterruptedException { - String string = "hello"; - assertThat(settableListenableFuture.set(string)).isTrue(); - Future completable = settableListenableFuture.completable(); - assertThat(completable.get()).isEqualTo(string); - assertThat(completable.isCancelled()).isFalse(); - assertThat(completable.isDone()).isTrue(); - } - - @Test - void setValueUpdatesDoneStatus() { - settableListenableFuture.set("hello"); - assertThat(settableListenableFuture.isCancelled()).isFalse(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - void throwsSetExceptionWrappedInExecutionException() { - Throwable exception = new RuntimeException(); - assertThat(settableListenableFuture.setException(exception)).isTrue(); - - assertThatExceptionOfType(ExecutionException.class).isThrownBy( - settableListenableFuture::get) - .withCause(exception); - - assertThat(settableListenableFuture.isCancelled()).isFalse(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - void throwsSetExceptionWrappedInExecutionExceptionFromCompletable() { - Throwable exception = new RuntimeException(); - assertThat(settableListenableFuture.setException(exception)).isTrue(); - Future completable = settableListenableFuture.completable(); - - assertThatExceptionOfType(ExecutionException.class).isThrownBy( - completable::get) - .withCause(exception); - - assertThat(completable.isCancelled()).isFalse(); - assertThat(completable.isDone()).isTrue(); - } - - @Test - void throwsSetErrorWrappedInExecutionException() { - Throwable exception = new OutOfMemoryError(); - assertThat(settableListenableFuture.setException(exception)).isTrue(); - - assertThatExceptionOfType(ExecutionException.class).isThrownBy( - settableListenableFuture::get) - .withCause(exception); - - assertThat(settableListenableFuture.isCancelled()).isFalse(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - void throwsSetErrorWrappedInExecutionExceptionFromCompletable() { - Throwable exception = new OutOfMemoryError(); - assertThat(settableListenableFuture.setException(exception)).isTrue(); - Future completable = settableListenableFuture.completable(); - - assertThatExceptionOfType(ExecutionException.class).isThrownBy( - completable::get) - .withCause(exception); - - assertThat(completable.isCancelled()).isFalse(); - assertThat(completable.isDone()).isTrue(); - } - - @Test - void setValueTriggersCallback() { - String string = "hello"; - final String[] callbackHolder = new String[1]; - - settableListenableFuture.addCallback(new ListenableFutureCallback<>() { - @Override - public void onSuccess(String result) { - callbackHolder[0] = result; - } - - @Override - public void onFailure(Throwable ex) { - throw new AssertionError("Expected onSuccess() to be called", ex); - } - }); - - settableListenableFuture.set(string); - assertThat(callbackHolder[0]).isEqualTo(string); - assertThat(settableListenableFuture.isCancelled()).isFalse(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - void setValueTriggersCallbackOnlyOnce() { - String string = "hello"; - final String[] callbackHolder = new String[1]; - - settableListenableFuture.addCallback(new ListenableFutureCallback<>() { - @Override - public void onSuccess(String result) { - callbackHolder[0] = result; - } - - @Override - public void onFailure(Throwable ex) { - throw new AssertionError("Expected onSuccess() to be called", ex); - } - }); - - settableListenableFuture.set(string); - assertThat(settableListenableFuture.set("good bye")).isFalse(); - assertThat(callbackHolder[0]).isEqualTo(string); - assertThat(settableListenableFuture.isCancelled()).isFalse(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - void setExceptionTriggersCallback() { - Throwable exception = new RuntimeException(); - final Throwable[] callbackHolder = new Throwable[1]; - - settableListenableFuture.addCallback(new ListenableFutureCallback<>() { - @Override - public void onSuccess(String result) { - fail("Expected onFailure() to be called"); - } - - @Override - public void onFailure(Throwable ex) { - callbackHolder[0] = ex; - } - }); - - settableListenableFuture.setException(exception); - assertThat(callbackHolder[0]).isEqualTo(exception); - assertThat(settableListenableFuture.isCancelled()).isFalse(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - void setExceptionTriggersCallbackOnlyOnce() { - Throwable exception = new RuntimeException(); - final Throwable[] callbackHolder = new Throwable[1]; - - settableListenableFuture.addCallback(new ListenableFutureCallback<>() { - @Override - public void onSuccess(String result) { - fail("Expected onFailure() to be called"); - } - - @Override - public void onFailure(Throwable ex) { - callbackHolder[0] = ex; - } - }); - - settableListenableFuture.setException(exception); - assertThat(settableListenableFuture.setException(new IllegalArgumentException())).isFalse(); - assertThat(callbackHolder[0]).isEqualTo(exception); - assertThat(settableListenableFuture.isCancelled()).isFalse(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - void nullIsAcceptedAsValueToSet() throws ExecutionException, InterruptedException { - settableListenableFuture.set(null); - assertThat(settableListenableFuture.get()).isNull(); - assertThat(settableListenableFuture.isCancelled()).isFalse(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - void getWaitsForCompletion() throws ExecutionException, InterruptedException { - final String string = "hello"; - - new Thread(() -> { - try { - Thread.sleep(20L); - settableListenableFuture.set(string); - } - catch (InterruptedException ex) { - throw new RuntimeException(ex); - } - }).start(); - - String value = settableListenableFuture.get(); - assertThat(value).isEqualTo(string); - assertThat(settableListenableFuture.isCancelled()).isFalse(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - void getWithTimeoutThrowsTimeoutException() { - assertThatExceptionOfType(TimeoutException.class).isThrownBy(() -> - settableListenableFuture.get(1L, TimeUnit.MILLISECONDS)); - } - - @Test - void getWithTimeoutWaitsForCompletion() throws ExecutionException, InterruptedException, TimeoutException { - final String string = "hello"; - - new Thread(() -> { - try { - Thread.sleep(20L); - settableListenableFuture.set(string); - } - catch (InterruptedException ex) { - throw new RuntimeException(ex); - } - }).start(); - - String value = settableListenableFuture.get(500L, TimeUnit.MILLISECONDS); - assertThat(value).isEqualTo(string); - assertThat(settableListenableFuture.isCancelled()).isFalse(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - void cancelPreventsValueFromBeingSet() { - assertThat(settableListenableFuture.cancel(true)).isTrue(); - assertThat(settableListenableFuture.set("hello")).isFalse(); - assertThat(settableListenableFuture.isCancelled()).isTrue(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - void cancelSetsFutureToDone() { - settableListenableFuture.cancel(true); - assertThat(settableListenableFuture.isCancelled()).isTrue(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - void cancelWithMayInterruptIfRunningTrueCallsOverriddenMethod() { - InterruptibleSettableListenableFuture interruptibleFuture = new InterruptibleSettableListenableFuture(); - assertThat(interruptibleFuture.cancel(true)).isTrue(); - assertThat(interruptibleFuture.calledInterruptTask()).isTrue(); - assertThat(interruptibleFuture.isCancelled()).isTrue(); - assertThat(interruptibleFuture.isDone()).isTrue(); - } - - @Test - void cancelWithMayInterruptIfRunningFalseDoesNotCallOverriddenMethod() { - InterruptibleSettableListenableFuture interruptibleFuture = new InterruptibleSettableListenableFuture(); - assertThat(interruptibleFuture.cancel(false)).isTrue(); - assertThat(interruptibleFuture.calledInterruptTask()).isFalse(); - assertThat(interruptibleFuture.isCancelled()).isTrue(); - assertThat(interruptibleFuture.isDone()).isTrue(); - } - - @Test - void setPreventsCancel() { - assertThat(settableListenableFuture.set("hello")).isTrue(); - assertThat(settableListenableFuture.cancel(true)).isFalse(); - assertThat(settableListenableFuture.isCancelled()).isFalse(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - void cancelPreventsExceptionFromBeingSet() { - assertThat(settableListenableFuture.cancel(true)).isTrue(); - assertThat(settableListenableFuture.setException(new RuntimeException())).isFalse(); - assertThat(settableListenableFuture.isCancelled()).isTrue(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - void setExceptionPreventsCancel() { - assertThat(settableListenableFuture.setException(new RuntimeException())).isTrue(); - assertThat(settableListenableFuture.cancel(true)).isFalse(); - assertThat(settableListenableFuture.isCancelled()).isFalse(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - void cancelStateThrowsExceptionWhenCallingGet() { - settableListenableFuture.cancel(true); - - assertThatExceptionOfType(CancellationException.class).isThrownBy(settableListenableFuture::get); - - assertThat(settableListenableFuture.isCancelled()).isTrue(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - void cancelStateThrowsExceptionWhenCallingGetWithTimeout() { - new Thread(() -> { - try { - Thread.sleep(20L); - settableListenableFuture.cancel(true); - } - catch (InterruptedException ex) { - throw new RuntimeException(ex); - } - }).start(); - - assertThatExceptionOfType(CancellationException.class).isThrownBy(() -> - settableListenableFuture.get(500L, TimeUnit.MILLISECONDS)); - - assertThat(settableListenableFuture.isCancelled()).isTrue(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - @SuppressWarnings({"rawtypes", "unchecked"}) - public void cancelDoesNotNotifyCallbacksOnSet() { - ListenableFutureCallback callback = mock(); - settableListenableFuture.addCallback(callback); - settableListenableFuture.cancel(true); - - verify(callback).onFailure(any(CancellationException.class)); - verifyNoMoreInteractions(callback); - - settableListenableFuture.set("hello"); - verifyNoMoreInteractions(callback); - - assertThat(settableListenableFuture.isCancelled()).isTrue(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - @Test - @SuppressWarnings({"rawtypes", "unchecked"}) - public void cancelDoesNotNotifyCallbacksOnSetException() { - ListenableFutureCallback callback = mock(); - settableListenableFuture.addCallback(callback); - settableListenableFuture.cancel(true); - - verify(callback).onFailure(any(CancellationException.class)); - verifyNoMoreInteractions(callback); - - settableListenableFuture.setException(new RuntimeException()); - verifyNoMoreInteractions(callback); - - assertThat(settableListenableFuture.isCancelled()).isTrue(); - assertThat(settableListenableFuture.isDone()).isTrue(); - } - - - private static class InterruptibleSettableListenableFuture extends SettableListenableFuture { - - private boolean interrupted = false; - - @Override - protected void interruptTask() { - interrupted = true; - } - - boolean calledInterruptTask() { - return interrupted; - } - } - -} diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java b/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java index 32247c95b949..5b3deb8f2c9d 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java @@ -211,69 +211,11 @@ public void enterScope() { initScopeRootObjects().push(getActiveContextObject()); } - /** - * Enter a new scope with a new {@linkplain #getActiveContextObject() root - * context object} and a new local variable scope containing the supplied - * name/value pair. - * @param name the name of the local variable - * @param value the value of the local variable - * @deprecated as of 6.2 with no replacement; to be removed in 7.0 - */ - @Deprecated(since = "6.2", forRemoval = true) - public void enterScope(String name, Object value) { - initVariableScopes().push(new VariableScope(name, value)); - initScopeRootObjects().push(getActiveContextObject()); - } - - /** - * Enter a new scope with a new {@linkplain #getActiveContextObject() root - * context object} and a new local variable scope containing the supplied - * name/value pairs. - * @param variables a map containing name/value pairs for local variables - * @deprecated as of 6.2 with no replacement; to be removed in 7.0 - */ - @Deprecated(since = "6.2", forRemoval = true) - public void enterScope(@Nullable Map variables) { - initVariableScopes().push(new VariableScope(variables)); - initScopeRootObjects().push(getActiveContextObject()); - } - public void exitScope() { initVariableScopes().pop(); initScopeRootObjects().pop(); } - /** - * Set a local variable with the given name to the supplied value within the - * current scope. - *

    If a local variable with the given name already exists, it will be - * overwritten. - * @param name the name of the local variable - * @param value the value of the local variable - * @deprecated as of 6.2 with no replacement; to be removed in 7.0 - */ - @Deprecated(since = "6.2", forRemoval = true) - public void setLocalVariable(String name, Object value) { - initVariableScopes().element().setVariable(name, value); - } - - /** - * Look up the value of the local variable with the given name. - * @param name the name of the local variable - * @return the value of the local variable, or {@code null} if the variable - * does not exist in the current scope - * @deprecated as of 6.2 with no replacement; to be removed in 7.0 - */ - @Deprecated(since = "6.2", forRemoval = true) - @Nullable - public Object lookupLocalVariable(String name) { - for (VariableScope scope : initVariableScopes()) { - if (scope.definesVariable(name)) { - return scope.lookupVariable(name); - } - } - return null; - } private Deque initContextObjects() { if (this.contextObjects == null) { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java index 94cc5d37016d..b6b09c1fb764 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java @@ -123,17 +123,6 @@ private AccessMode(boolean supportsReads, boolean supportsWrites) { private volatile CachedIndexState cachedIndexWriteState; - /** - * Create an {@code Indexer} with the given start position, end position, and - * index expression. - * @see #Indexer(boolean, int, int, SpelNodeImpl) - * @deprecated as of 6.2, in favor of {@link #Indexer(boolean, int, int, SpelNodeImpl)} - */ - @Deprecated(since = "6.2", forRemoval = true) - public Indexer(int startPos, int endPos, SpelNodeImpl indexExpression) { - this(false, startPos, endPos, indexExpression); - } - /** * Create an {@code Indexer} with the given null-safe flag, start position, * end position, and index expression. diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/ExpressionStateTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/ExpressionStateTests.java index f827eab9e9ca..d2957bab9a4c 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/ExpressionStateTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/ExpressionStateTests.java @@ -16,8 +16,6 @@ package org.springframework.expression.spel; -import java.util.Map; - import org.junit.jupiter.api.Test; import org.springframework.core.convert.TypeDescriptor; @@ -55,21 +53,6 @@ void construction() { assertThat(state.getEvaluationContext()).isEqualTo(context); } - @Test - @SuppressWarnings("removal") - void localVariables() { - Object value = state.lookupLocalVariable("foo"); - assertThat(value).isNull(); - - state.setLocalVariable("foo",34); - value = state.lookupLocalVariable("foo"); - assertThat(value).isEqualTo(34); - - state.setLocalVariable("foo", null); - value = state.lookupLocalVariable("foo"); - assertThat(value).isNull(); - } - @Test void globalVariables() { TypedValue typedValue = state.lookupVariable("foo"); @@ -86,41 +69,6 @@ void globalVariables() { assertThat(typedValue.getTypeDescriptor().getType()).isEqualTo(String.class); } - @Test - @SuppressWarnings("removal") - void noVariableInterference() { - TypedValue typedValue = state.lookupVariable("foo"); - assertThat(typedValue).isEqualTo(TypedValue.NULL); - - state.setLocalVariable("foo",34); - typedValue = state.lookupVariable("foo"); - assertThat(typedValue).isEqualTo(TypedValue.NULL); - - state.setVariable("goo", "hello"); - assertThat(state.lookupLocalVariable("goo")).isNull(); - } - - @Test - @SuppressWarnings("removal") - void localVariableNestedScopes() { - assertThat(state.lookupLocalVariable("foo")).isNull(); - - state.setLocalVariable("foo",12); - assertThat(state.lookupLocalVariable("foo")).isEqualTo(12); - - state.enterScope(null); - // found in upper scope - assertThat(state.lookupLocalVariable("foo")).isEqualTo(12); - - state.setLocalVariable("foo","abc"); - // found in nested scope - assertThat(state.lookupLocalVariable("foo")).isEqualTo("abc"); - - state.exitScope(); - // found in nested scope - assertThat(state.lookupLocalVariable("foo")).isEqualTo(12); - } - @Test void rootContextObject() { assertThat(state.getRootContextObject().getValue().getClass()).isEqualTo(Inventor.class); @@ -159,25 +107,6 @@ void activeContextObject() { assertThat(state.getActiveContextObject()).isEqualTo(TypedValue.NULL); } - @Test - @SuppressWarnings("removal") - void populatedNestedScopes() { - assertThat(state.lookupLocalVariable("foo")).isNull(); - - state.enterScope("foo",34); - assertThat(state.lookupLocalVariable("foo")).isEqualTo(34); - - state.enterScope(null); - state.setLocalVariable("foo", 12); - assertThat(state.lookupLocalVariable("foo")).isEqualTo(12); - - state.exitScope(); - assertThat(state.lookupLocalVariable("foo")).isEqualTo(34); - - state.exitScope(); - assertThat(state.lookupLocalVariable("goo")).isNull(); - } - @Test void rootObjectConstructor() { EvaluationContext ctx = TestScenarioCreator.getTestEvaluationContext(); @@ -189,27 +118,6 @@ void rootObjectConstructor() { assertThat(stateRoot.getValue()).isEqualTo("i am a string"); } - @Test - @SuppressWarnings("removal") - void populatedNestedScopesMap() { - assertThat(state.lookupLocalVariable("foo")).isNull(); - assertThat(state.lookupLocalVariable("goo")).isNull(); - - state.enterScope(Map.of("foo", 34, "goo", "abc")); - assertThat(state.lookupLocalVariable("foo")).isEqualTo(34); - assertThat(state.lookupLocalVariable("goo")).isEqualTo("abc"); - - state.enterScope(null); - state.setLocalVariable("foo",12); - assertThat(state.lookupLocalVariable("foo")).isEqualTo(12); - assertThat(state.lookupLocalVariable("goo")).isEqualTo("abc"); - - state.exitScope(); - state.exitScope(); - assertThat(state.lookupLocalVariable("foo")).isNull(); - assertThat(state.lookupLocalVariable("goo")).isNull(); - } - @Test void operators() { assertThatExceptionOfType(SpelEvaluationException.class) diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/AsyncHandlerMethodReturnValueHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/AsyncHandlerMethodReturnValueHandler.java index f6342f6f8475..72c85ae99477 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/AsyncHandlerMethodReturnValueHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/AsyncHandlerMethodReturnValueHandler.java @@ -50,35 +50,6 @@ public interface AsyncHandlerMethodReturnValueHandler extends HandlerMethodRetur */ boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType); - /** - * Adapt the asynchronous return value to a - * {@link org.springframework.util.concurrent.ListenableFuture ListenableFuture}. - *

    Implementations should consider returning an instance of - * {@link org.springframework.util.concurrent.SettableListenableFuture - * SettableListenableFuture}. Return value handling will then continue when - * the ListenableFuture is completed with either success or error. - *

    Note: this method will only be invoked after - * {@link #supportsReturnType(org.springframework.core.MethodParameter)} - * is called and it returns {@code true}. - * @param returnValue the value returned from the handler method - * @param returnType the type of the return value - * @return the resulting ListenableFuture, or {@code null} in which case - * no further handling will be performed - * @deprecated as of 6.0, in favor of - * {@link #toCompletableFuture(Object, MethodParameter)} - */ - @Deprecated(since = "6.0", forRemoval = true) - @SuppressWarnings("removal") - @Nullable - default org.springframework.util.concurrent.ListenableFuture toListenableFuture( - Object returnValue, MethodParameter returnType) { - - CompletableFuture result = toCompletableFuture(returnValue, returnType); - return (result != null ? - new org.springframework.util.concurrent.CompletableToListenableFutureAdapter<>(result) : - null); - } - /** * Adapt the asynchronous return value to a {@link CompletableFuture}. *

    Return value handling will then continue when diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/ListenableFutureReturnValueHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/ListenableFutureReturnValueHandler.java deleted file mode 100644 index 93aeb5952cd8..000000000000 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/ListenableFutureReturnValueHandler.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2002-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.messaging.handler.invocation; - -import java.util.concurrent.CompletableFuture; - -import org.springframework.core.MethodParameter; - -/** - * Support for {@link org.springframework.util.concurrent.ListenableFuture} as a return value type. - * - * @author Sebastien Deleuze - * @since 4.2 - * @deprecated as of 6.0, in favor of {@link CompletableFutureReturnValueHandler} - */ -@Deprecated(since = "6.0", forRemoval = true) -@SuppressWarnings("removal") -public class ListenableFutureReturnValueHandler extends AbstractAsyncReturnValueHandler { - - @Override - public boolean supportsReturnType(MethodParameter returnType) { - return org.springframework.util.concurrent.ListenableFuture.class.isAssignableFrom(returnType.getParameterType()); - } - - @Override - public org.springframework.util.concurrent.ListenableFuture toListenableFuture(Object returnValue, MethodParameter returnType) { - return (org.springframework.util.concurrent.ListenableFuture) returnValue; - } - - @Override - public CompletableFuture toCompletableFuture(Object returnValue, MethodParameter returnType) { - return ((org.springframework.util.concurrent.ListenableFuture) returnValue).completable(); - } - -} diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java index 41ecfb1730e3..caefa70ac49c 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java @@ -344,13 +344,11 @@ protected List initArgumentResolvers() { } @Override - @SuppressWarnings("removal") protected List initReturnValueHandlers() { List handlers = new ArrayList<>(); // Single-purpose return value types - handlers.add(new org.springframework.messaging.handler.invocation.ListenableFutureReturnValueHandler()); handlers.add(new CompletableFutureReturnValueHandler()); if (reactorPresent) { handlers.add(new ReactiveReturnValueHandler()); diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/ConnectionHandlingStompSession.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/ConnectionHandlingStompSession.java index 40faa0bcd59d..77befefeb96f 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/ConnectionHandlingStompSession.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/ConnectionHandlingStompSession.java @@ -31,17 +31,6 @@ */ public interface ConnectionHandlingStompSession extends StompSession, StompTcpConnectionHandler { - /** - * Return a future that will complete when the session is ready for use. - * @deprecated as of 6.0, in favor of {@link #getSession()} - */ - @Deprecated(since = "6.0", forRemoval = true) - @SuppressWarnings("removal") - default org.springframework.util.concurrent.ListenableFuture getSessionFuture() { - return new org.springframework.util.concurrent.CompletableToListenableFutureAdapter<>( - getSession()); - } - /** * Return a future that will complete when the session is ready for use. * @since 6.0 diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/ReactorNettyTcpStompClient.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/ReactorNettyTcpStompClient.java index d16ab82b3282..e321e19f6670 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/ReactorNettyTcpStompClient.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/ReactorNettyTcpStompClient.java @@ -89,22 +89,6 @@ else if (reactorNetty2ClientPresent) { } - /** - * Connect and notify the given {@link StompSessionHandler} when connected - * on the STOMP level. - * @param handler the handler for the STOMP session - * @return a ListenableFuture for access to the session when ready for use - * @deprecated as of 6.0, in favor of {@link #connectAsync(StompSessionHandler)} - */ - @Deprecated(since = "6.0", forRemoval = true) - @SuppressWarnings("removal") - public org.springframework.util.concurrent.ListenableFuture connect( - StompSessionHandler handler) { - - return new org.springframework.util.concurrent.CompletableToListenableFutureAdapter<>( - connectAsync(handler)); - } - /** * Connect and notify the given {@link StompSessionHandler} when connected * on the STOMP level. @@ -116,24 +100,6 @@ public CompletableFuture connectAsync(StompSessionHandler handler) return connectAsync(null, handler); } - /** - * An overloaded version of {@link #connect(StompSessionHandler)} that - * accepts headers to use for the STOMP CONNECT frame. - * @param connectHeaders headers to add to the CONNECT frame - * @param handler the handler for the STOMP session - * @return a ListenableFuture for access to the session when ready for use - * @deprecated as of 6.0, in favor of {@link #connectAsync(StompHeaders, StompSessionHandler)} - */ - @Deprecated(since = "6.0", forRemoval = true) - @SuppressWarnings("removal") - public org.springframework.util.concurrent.ListenableFuture connect( - @Nullable StompHeaders connectHeaders, StompSessionHandler handler) { - - ConnectionHandlingStompSession session = createSession(connectHeaders, handler); - this.tcpClient.connectAsync(session); - return session.getSessionFuture(); - } - /** * An overloaded version of {@link #connectAsync(StompSessionHandler)} that * accepts headers to use for the STOMP CONNECT frame. diff --git a/spring-messaging/src/main/java/org/springframework/messaging/tcp/TcpConnection.java b/spring-messaging/src/main/java/org/springframework/messaging/tcp/TcpConnection.java index 545b1f3d1218..c3f1bdccca4c 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/tcp/TcpConnection.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/tcp/TcpConnection.java @@ -30,20 +30,6 @@ */ public interface TcpConnection

    extends Closeable { - /** - * Send the given message. - * @param message the message - * @return a ListenableFuture that can be used to determine when and if the - * message was successfully sent - * @deprecated as of 6.0, in favor of {@link #sendAsync(Message)} - */ - @Deprecated(since = "6.0", forRemoval = true) - @SuppressWarnings("removal") - default org.springframework.util.concurrent.ListenableFuture send(Message

    message) { - return new org.springframework.util.concurrent.CompletableToListenableFutureAdapter<>( - sendAsync(message)); - } - /** * Send the given message. * @param message the message diff --git a/spring-messaging/src/main/java/org/springframework/messaging/tcp/TcpOperations.java b/spring-messaging/src/main/java/org/springframework/messaging/tcp/TcpOperations.java index 6ee521358da9..d6a7099ab667 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/tcp/TcpOperations.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/tcp/TcpOperations.java @@ -27,22 +27,6 @@ */ public interface TcpOperations

    { - /** - * Open a new connection. - * @param connectionHandler a handler to manage the connection - * @return a ListenableFuture that can be used to determine when and if the - * connection is successfully established - * @deprecated as of 6.0, in favor of {@link #connectAsync(TcpConnectionHandler)} - */ - @Deprecated(since = "6.0", forRemoval = true) - @SuppressWarnings("removal") - default org.springframework.util.concurrent.ListenableFuture connect( - TcpConnectionHandler

    connectionHandler) { - - return new org.springframework.util.concurrent.CompletableToListenableFutureAdapter<>( - connectAsync(connectionHandler)); - } - /** * Open a new connection. * @param connectionHandler a handler to manage the connection @@ -52,23 +36,6 @@ default org.springframework.util.concurrent.ListenableFuture connect( */ CompletableFuture connectAsync(TcpConnectionHandler

    connectionHandler); - /** - * Open a new connection and a strategy for reconnecting if the connection fails. - * @param connectionHandler a handler to manage the connection - * @param reconnectStrategy a strategy for reconnecting - * @return a ListenableFuture that can be used to determine when and if the - * initial connection is successfully established - * @deprecated as of 6.0, in favor of {@link #connectAsync(TcpConnectionHandler, ReconnectStrategy)} - */ - @Deprecated(since = "6.0", forRemoval = true) - @SuppressWarnings("removal") - default org.springframework.util.concurrent.ListenableFuture connect( - TcpConnectionHandler

    connectionHandler, ReconnectStrategy reconnectStrategy) { - - return new org.springframework.util.concurrent.CompletableToListenableFutureAdapter<>( - connectAsync(connectionHandler, reconnectStrategy)); - } - /** * Open a new connection and a strategy for reconnecting if the connection fails. * @param connectionHandler a handler to manage the connection @@ -79,18 +46,6 @@ default org.springframework.util.concurrent.ListenableFuture connect( */ CompletableFuture connectAsync(TcpConnectionHandler

    connectionHandler, ReconnectStrategy reconnectStrategy); - /** - * Shut down and close any open connections. - * @return a ListenableFuture that can be used to determine when and if the - * connection is successfully closed - * @deprecated as of 6.0, in favor of {@link #shutdownAsync()} - */ - @Deprecated(since = "6.0", forRemoval = true) - @SuppressWarnings("removal") - default org.springframework.util.concurrent.ListenableFuture shutdown() { - return new org.springframework.util.concurrent.CompletableToListenableFutureAdapter<>(shutdownAsync()); - } - /** * Shut down and close any open connections. * @return a CompletableFuture that can be used to determine when and if the diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandlerTests.java index 532838457aa5..7421a10d3c65 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandlerTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandlerTests.java @@ -273,39 +273,6 @@ void dotPathSeparator() { assertThat(controller.method).isEqualTo("handleFoo"); } - @Test - @SuppressWarnings({ "unchecked", "rawtypes" }) - public void listenableFutureSuccess() { - Message emptyMessage = MessageBuilder.withPayload(new byte[0]).build(); - given(this.channel.send(any(Message.class))).willReturn(true); - given(this.converter.toMessage(any(), any(MessageHeaders.class))).willReturn(emptyMessage); - - ListenableFutureController controller = new ListenableFutureController(); - this.messageHandler.registerHandler(controller); - this.messageHandler.setDestinationPrefixes(Arrays.asList("/app1", "/app2/")); - - Message message = createMessage("/app1/listenable-future/success"); - this.messageHandler.handleMessage(message); - - assertThat(controller.future).isNotNull(); - controller.future.run(); - verify(this.converter).toMessage(this.payloadCaptor.capture(), any(MessageHeaders.class)); - assertThat(this.payloadCaptor.getValue()).isEqualTo("foo"); - } - - @Test - void listenableFutureFailure() { - ListenableFutureController controller = new ListenableFutureController(); - this.messageHandler.registerHandler(controller); - this.messageHandler.setDestinationPrefixes(Arrays.asList("/app1", "/app2/")); - - Message message = createMessage("/app1/listenable-future/failure"); - this.messageHandler.handleMessage(message); - - controller.future.run(); - assertThat(controller.exceptionCaught).isTrue(); - } - @Test @SuppressWarnings({ "unchecked", "rawtypes" }) public void completableFutureSuccess() { @@ -569,36 +536,6 @@ public void handleFoo() { } - @Controller - @MessageMapping("listenable-future") - @SuppressWarnings({"deprecation", "removal"}) - private static class ListenableFutureController { - - org.springframework.util.concurrent.ListenableFutureTask future; - - boolean exceptionCaught = false; - - @MessageMapping("success") - public org.springframework.util.concurrent.ListenableFutureTask handleListenableFuture() { - this.future = new org.springframework.util.concurrent.ListenableFutureTask<>(() -> "foo"); - return this.future; - } - - @MessageMapping("failure") - public org.springframework.util.concurrent.ListenableFutureTask handleListenableFutureException() { - this.future = new org.springframework.util.concurrent.ListenableFutureTask<>(() -> { - throw new IllegalStateException(); - }); - return this.future; - } - - @MessageExceptionHandler(IllegalStateException.class) - public void handleValidationException() { - this.exceptionCaught = true; - } - } - - @Controller private static class CompletableFutureController { diff --git a/spring-test/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java b/spring-test/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java index 00771118bed5..f3e8841e6509 100644 --- a/spring-test/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java +++ b/spring-test/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java @@ -82,18 +82,6 @@ public JsonPathExpectationsHelper(String expression, @Nullable Configuration con this.configuration = (configuration != null) ? configuration : Configuration.defaultConfiguration(); } - /** - * Construct a new {@code JsonPathExpectationsHelper}. - * @param expression the {@link JsonPath} expression; never {@code null} or empty - * @param args arguments to parameterize the {@code JsonPath} expression with, - * using formatting specifiers defined in {@link String#format(String, Object...)} - * @deprecated in favor of calling {@link String#formatted(Object...)} upfront - */ - @Deprecated(since = "6.2", forRemoval = true) - public JsonPathExpectationsHelper(String expression, Object... args) { - this(expression.formatted(args), (Configuration) null); - } - /** * Evaluate the JSON path expression against the supplied {@code content} diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java index 5b8597a68ec5..ad11d715a4b5 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java @@ -711,13 +711,6 @@ public JsonPathAssertions jsonPath(String expression) { JsonPathConfigurationProvider.getConfiguration(this.jsonEncoderDecoder)); } - @Override - @SuppressWarnings("removal") - public JsonPathAssertions jsonPath(String expression, Object... args) { - Assert.hasText(expression, "expression must not be null or empty"); - return jsonPath(expression.formatted(args)); - } - @Override public XpathAssertions xpath(String expression, @Nullable Map namespaces, Object... args) { return new XpathAssertions(this, expression, namespaces, args); diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeResult.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeResult.java index 881b7c0654a4..554fddfccbb2 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeResult.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/ExchangeResult.java @@ -176,16 +176,6 @@ public HttpStatusCode getStatus() { return this.response.getStatusCode(); } - /** - * Return the HTTP status code as an integer. - * @since 5.1.10 - * @deprecated in favor of {@link #getStatus()}, for removal in 7.0 - */ - @Deprecated(since = "6.0", forRemoval = true) - public int getRawStatusCode() { - return getStatus().value(); - } - /** * Return the response headers received from the server. */ diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/JsonPathAssertions.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/JsonPathAssertions.java index d761575ac839..6881a799bf56 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/JsonPathAssertions.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/JsonPathAssertions.java @@ -161,17 +161,6 @@ public WebTestClient.BodyContentSpec value(Class targetType, Matcher WebTestClient.BodyContentSpec value(Matcher matcher, Class targetType) { - this.pathHelper.assertValue(this.content, matcher, targetType); - return this.bodySpec; - } - /** * Delegates to {@link JsonPathExpectationsHelper#assertValue(String, Matcher, ParameterizedTypeReference)}. * @since 6.2 @@ -202,16 +191,6 @@ public WebTestClient.BodyContentSpec value(Class targetType, Consumer return this.bodySpec; } - /** - * Consume the result of the JSONPath evaluation and provide a target class. - * @since 5.1 - * @deprecated in favor of {@link #value(Class, Consumer)} - */ - @Deprecated(since = "6.2", forRemoval = true) - public WebTestClient.BodyContentSpec value(Consumer consumer, Class targetType) { - return value(targetType, consumer); - } - /** * Consume the result of the JSONPath evaluation and provide a parameterized type. * @since 6.2 diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java index 49999e352235..8d766d001c77 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java @@ -1075,19 +1075,6 @@ default BodyContentSpec json(String expectedJson) { */ JsonPathAssertions jsonPath(String expression); - /** - * Access to response body assertions using a - * JsonPath expression - * to inspect a specific subset of the body. - *

    The JSON path expression can be a parameterized string using - * formatting specifiers as defined in {@link String#format}. - * @param expression the JsonPath expression - * @param args arguments to parameterize the expression - * @deprecated in favor of calling {@link String#formatted(Object...)} upfront - */ - @Deprecated(since = "6.2", forRemoval = true) - JsonPathAssertions jsonPath(String expression, Object... args); - /** * Access to response body assertions using an XPath expression to * inspect a specific subset of the body. diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/AsyncTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/AsyncTests.java index 29c8367c9452..b7ebd65934a7 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/AsyncTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client/standalone/AsyncTests.java @@ -117,16 +117,6 @@ void deferredResultWithDelayedError() { .expectBody(String.class).isEqualTo("Delayed Error"); } - @Test - void listenableFuture() { - this.testClient.get() - .uri("/1?listenableFuture=true") - .exchange() - .expectStatus().isOk() - .expectHeader().contentType(MediaType.APPLICATION_JSON) - .expectBody().json("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"); - } - @Test void completableFutureWithImmediateValue() { this.testClient.get() @@ -193,15 +183,6 @@ DeferredResult getDeferredResultWithDelayedError() { return result; } - @GetMapping(params = "listenableFuture") - @SuppressWarnings({ "deprecation", "removal" }) - org.springframework.util.concurrent.ListenableFuture getListenableFuture() { - org.springframework.util.concurrent.ListenableFutureTask futureTask = - new org.springframework.util.concurrent.ListenableFutureTask<>(() -> new Person("Joe")); - delay(100, futureTask); - return futureTask; - } - @GetMapping(params = "completableFutureWithImmediateValue") CompletableFuture getCompletableFutureWithImmediateValue() { CompletableFuture future = new CompletableFuture<>(); diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java index dd70f74d9b07..ee2841538c57 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java @@ -144,18 +144,6 @@ void deferredResultWithDelayedError() throws Exception { .andExpect(content().string("Delayed Error")); } - @Test - void listenableFuture() throws Exception { - MvcResult mvcResult = this.mockMvc.perform(get("/1").param("listenableFuture", "true")) - .andExpect(request().asyncStarted()) - .andReturn(); - - this.mockMvc.perform(asyncDispatch(mvcResult)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}")); - } - @Test // SPR-12597 void completableFutureWithImmediateValue() throws Exception { MvcResult mvcResult = this.mockMvc.perform(get("/1").param("completableFutureWithImmediateValue", "true")) @@ -245,13 +233,6 @@ void deferredResultWithDelayedError() { .hasStatus5xxServerError().hasBodyTextEqualTo("Delayed Error"); } - @Test - void listenableFuture() { - assertThat(this.mockMvc.get().uri("/1").param("listenableFuture", "true")) - .hasStatusOk().hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON) - .hasBodyTextEqualTo("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"); - } - @Test // SPR-12597 void completableFutureWithImmediateValue() { assertThat(this.mockMvc.get().uri("/1").param("completableFutureWithImmediateValue", "true")) @@ -333,15 +314,6 @@ DeferredResult getDeferredResultWithDelayedError() { return result; } - @RequestMapping(params = "listenableFuture") - @SuppressWarnings({"deprecation", "removal"}) - org.springframework.util.concurrent.ListenableFuture getListenableFuture() { - org.springframework.util.concurrent.ListenableFutureTask futureTask = - new org.springframework.util.concurrent.ListenableFutureTask<>(() -> new Person("Joe")); - delay(100, futureTask); - return futureTask; - } - @RequestMapping(params = "completableFutureWithImmediateValue") CompletableFuture getCompletableFutureWithImmediateValue() { CompletableFuture future = new CompletableFuture<>(); diff --git a/spring-tx/src/main/java/org/springframework/transaction/reactive/GenericReactiveTransaction.java b/spring-tx/src/main/java/org/springframework/transaction/reactive/GenericReactiveTransaction.java index f4c9602a1c62..0cf7a5833d28 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/reactive/GenericReactiveTransaction.java +++ b/spring-tx/src/main/java/org/springframework/transaction/reactive/GenericReactiveTransaction.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -96,13 +96,6 @@ public GenericReactiveTransaction( this.suspendedResources = suspendedResources; } - @Deprecated(since = "6.1", forRemoval = true) - public GenericReactiveTransaction(@Nullable Object transaction, boolean newTransaction, - boolean newSynchronization, boolean readOnly, boolean debug, @Nullable Object suspendedResources) { - - this(null, transaction, newTransaction, newSynchronization, false, readOnly, debug, suspendedResources); - } - @Override public String getTransactionName() { diff --git a/spring-tx/src/main/java/org/springframework/transaction/support/DefaultTransactionStatus.java b/spring-tx/src/main/java/org/springframework/transaction/support/DefaultTransactionStatus.java index 5b92724b5e2f..1d71e544d436 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/support/DefaultTransactionStatus.java +++ b/spring-tx/src/main/java/org/springframework/transaction/support/DefaultTransactionStatus.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -102,13 +102,6 @@ public DefaultTransactionStatus( this.suspendedResources = suspendedResources; } - @Deprecated(since = "6.1", forRemoval = true) - public DefaultTransactionStatus(@Nullable Object transaction, boolean newTransaction, - boolean newSynchronization, boolean readOnly, boolean debug, @Nullable Object suspendedResources) { - - this(null, transaction, newTransaction, newSynchronization, false, readOnly, debug, suspendedResources); - } - @Override public String getTransactionName() { diff --git a/spring-web/spring-web.gradle b/spring-web/spring-web.gradle index 80fb8fe5b5c5..b107f41a0bfc 100644 --- a/spring-web/spring-web.gradle +++ b/spring-web/spring-web.gradle @@ -20,7 +20,6 @@ dependencies { optional("com.google.code.gson:gson") optional("com.google.protobuf:protobuf-java-util") optional("com.rometools:rome") - optional("com.squareup.okhttp3:okhttp") optional("io.micrometer:context-propagation") optional("io.netty:netty-buffer") optional("io.netty:netty-handler") diff --git a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java index 5d895e4a368d..4cd56579cdf7 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java +++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java @@ -1961,21 +1961,6 @@ public static HttpHeaders readOnlyHttpHeaders(HttpHeaders headers) { return (headers instanceof ReadOnlyHttpHeaders ? headers : new ReadOnlyHttpHeaders(headers.headers)); } - /** - * Remove any read-only wrapper that may have been previously applied around - * the given headers via {@link #readOnlyHttpHeaders(HttpHeaders)}. - *

    Once the writable instance is mutated, the read-only instance is likely - * to be out of sync and should be discarded. - * @param headers the headers to expose - * @return a writable variant of the headers, or the original headers as-is - * @since 5.1.1 - * @deprecated as of 6.2 in favor of {@link #HttpHeaders(MultiValueMap)}. - */ - @Deprecated(since = "6.2", forRemoval = true) - public static HttpHeaders writableHttpHeaders(HttpHeaders headers) { - return new HttpHeaders(headers); - } - /** * Helps to format HTTP header values, as HTTP header values themselves can * contain comma-separated values, can become confusing with regular diff --git a/spring-web/src/main/java/org/springframework/http/MediaType.java b/spring-web/src/main/java/org/springframework/http/MediaType.java index ff27cb0ea375..308d7c4346d8 100644 --- a/spring-web/src/main/java/org/springframework/http/MediaType.java +++ b/spring-web/src/main/java/org/springframework/http/MediaType.java @@ -22,7 +22,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -100,23 +99,6 @@ public class MediaType extends MimeType implements Serializable { */ public static final String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded"; - /** - * Public constant media type for {@code application/graphql+json}. - * @since 5.3.19 - * @see GraphQL over HTTP spec change - * @deprecated as of 6.0.3, in favor of {@link MediaType#APPLICATION_GRAPHQL_RESPONSE} - */ - @Deprecated(since = "6.0.3", forRemoval = true) - public static final MediaType APPLICATION_GRAPHQL; - - /** - * A String equivalent of {@link MediaType#APPLICATION_GRAPHQL}. - * @since 5.3.19 - * @deprecated as of 6.0.3, in favor of {@link MediaType#APPLICATION_GRAPHQL_RESPONSE_VALUE} - */ - @Deprecated(since = "6.0.3", forRemoval = true) - public static final String APPLICATION_GRAPHQL_VALUE = "application/graphql+json"; - /** * Public constant media type for {@code application/graphql-response+json}. * @since 6.0.3 @@ -456,7 +438,6 @@ public class MediaType extends MimeType implements Serializable { APPLICATION_ATOM_XML = new MediaType("application", "atom+xml"); APPLICATION_CBOR = new MediaType("application", "cbor"); APPLICATION_FORM_URLENCODED = new MediaType("application", "x-www-form-urlencoded"); - APPLICATION_GRAPHQL = new MediaType("application", "graphql+json"); APPLICATION_GRAPHQL_RESPONSE = new MediaType("application", "graphql-response+json"); APPLICATION_JSON = new MediaType("application", "json"); APPLICATION_JSON_UTF8 = new MediaType("application", "json", StandardCharsets.UTF_8); @@ -847,141 +828,4 @@ public static String toString(Collection mediaTypes) { return MimeTypeUtils.toString(mediaTypes); } - /** - * Sorts the given list of {@code MediaType} objects by specificity. - *

    Given two media types: - *

      - *
    1. if either media type has a {@linkplain #isWildcardType() wildcard type}, then the media type without the - * wildcard is ordered before the other.
    2. - *
    3. if the two media types have different {@linkplain #getType() types}, then they are considered equal and - * remain their current order.
    4. - *
    5. if either media type has a {@linkplain #isWildcardSubtype() wildcard subtype}, then the media type without - * the wildcard is sorted before the other.
    6. - *
    7. if the two media types have different {@linkplain #getSubtype() subtypes}, then they are considered equal - * and remain their current order.
    8. - *
    9. if the two media types have different {@linkplain #getQualityValue() quality value}, then the media type - * with the highest quality value is ordered before the other.
    10. - *
    11. if the two media types have a different amount of {@linkplain #getParameter(String) parameters}, then the - * media type with the most parameters is ordered before the other.
    12. - *
    - *

    For example: - *

    audio/basic < audio/* < */*
    - *
    audio/* < audio/*;q=0.7; audio/*;q=0.3
    - *
    audio/basic;level=1 < audio/basic
    - *
    audio/basic == text/html
    - *
    audio/basic == audio/wave
    - * @param mediaTypes the list of media types to be sorted - * @deprecated As of 6.0, in favor of {@link MimeTypeUtils#sortBySpecificity(List)} - */ - @Deprecated(since = "6.0", forRemoval = true) - public static void sortBySpecificity(List mediaTypes) { - Assert.notNull(mediaTypes, "'mediaTypes' must not be null"); - if (mediaTypes.size() > 1) { - mediaTypes.sort(SPECIFICITY_COMPARATOR); - } - } - - /** - * Sorts the given list of {@code MediaType} objects by quality value. - *

    Given two media types: - *

      - *
    1. if the two media types have different {@linkplain #getQualityValue() quality value}, then the media type - * with the highest quality value is ordered before the other.
    2. - *
    3. if either media type has a {@linkplain #isWildcardType() wildcard type}, then the media type without the - * wildcard is ordered before the other.
    4. - *
    5. if the two media types have different {@linkplain #getType() types}, then they are considered equal and - * remain their current order.
    6. - *
    7. if either media type has a {@linkplain #isWildcardSubtype() wildcard subtype}, then the media type without - * the wildcard is sorted before the other.
    8. - *
    9. if the two media types have different {@linkplain #getSubtype() subtypes}, then they are considered equal - * and remain their current order.
    10. - *
    11. if the two media types have a different amount of {@linkplain #getParameter(String) parameters}, then the - * media type with the most parameters is ordered before the other.
    12. - *
    - * @param mediaTypes the list of media types to be sorted - * @see #getQualityValue() - * @deprecated As of 6.0, with no direct replacement - */ - @Deprecated(since = "6.0", forRemoval = true) - public static void sortByQualityValue(List mediaTypes) { - Assert.notNull(mediaTypes, "'mediaTypes' must not be null"); - if (mediaTypes.size() > 1) { - mediaTypes.sort(QUALITY_VALUE_COMPARATOR); - } - } - - /** - * Sorts the given list of {@code MediaType} objects by specificity as the - * primary criteria and quality value the secondary. - * @deprecated As of 6.0, in favor of {@link MimeTypeUtils#sortBySpecificity(List)} - */ - @Deprecated(since = "6.0") - public static void sortBySpecificityAndQuality(List mediaTypes) { - Assert.notNull(mediaTypes, "'mediaTypes' must not be null"); - if (mediaTypes.size() > 1) { - mediaTypes.sort(MediaType.SPECIFICITY_COMPARATOR.thenComparing(MediaType.QUALITY_VALUE_COMPARATOR)); - } - } - - - /** - * Comparator used by {@link #sortByQualityValue(List)}. - * @deprecated As of 6.0, with no direct replacement - */ - @Deprecated(since = "6.0", forRemoval = true) - public static final Comparator QUALITY_VALUE_COMPARATOR = (mediaType1, mediaType2) -> { - double quality1 = mediaType1.getQualityValue(); - double quality2 = mediaType2.getQualityValue(); - int qualityComparison = Double.compare(quality2, quality1); - if (qualityComparison != 0) { - return qualityComparison; // audio/*;q=0.7 < audio/*;q=0.3 - } - else if (mediaType1.isWildcardType() && !mediaType2.isWildcardType()) { // */* < audio/* - return 1; - } - else if (mediaType2.isWildcardType() && !mediaType1.isWildcardType()) { // audio/* > */* - return -1; - } - else if (!mediaType1.getType().equals(mediaType2.getType())) { // audio/basic == text/html - return 0; - } - else { // mediaType1.getType().equals(mediaType2.getType()) - if (mediaType1.isWildcardSubtype() && !mediaType2.isWildcardSubtype()) { // audio/* < audio/basic - return 1; - } - else if (mediaType2.isWildcardSubtype() && !mediaType1.isWildcardSubtype()) { // audio/basic > audio/* - return -1; - } - else if (!mediaType1.getSubtype().equals(mediaType2.getSubtype())) { // audio/basic == audio/wave - return 0; - } - else { - int paramsSize1 = mediaType1.getParameters().size(); - int paramsSize2 = mediaType2.getParameters().size(); - return Integer.compare(paramsSize2, paramsSize1); // audio/basic;level=1 < audio/basic - } - } - }; - - - /** - * Comparator used by {@link #sortBySpecificity(List)}. - * @deprecated As of 6.0, with no direct replacement - */ - @Deprecated(since = "6.0", forRemoval = true) - @SuppressWarnings("removal") - public static final Comparator SPECIFICITY_COMPARATOR = new SpecificityComparator<>() { - - @Override - protected int compareParameters(MediaType mediaType1, MediaType mediaType2) { - double quality1 = mediaType1.getQualityValue(); - double quality2 = mediaType2.getQualityValue(); - int qualityComparison = Double.compare(quality2, quality1); - if (qualityComparison != 0) { - return qualityComparison; // audio/*;q=0.7 < audio/*;q=0.3 - } - return super.compareParameters(mediaType1, mediaType2); - } - }; - } diff --git a/spring-web/src/main/java/org/springframework/http/client/ClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/ClientHttpResponse.java index b4b90443d4aa..455f2c12f544 100644 --- a/spring-web/src/main/java/org/springframework/http/client/ClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/ClientHttpResponse.java @@ -42,19 +42,6 @@ public interface ClientHttpResponse extends HttpInputMessage, Closeable { */ HttpStatusCode getStatusCode() throws IOException; - /** - * Get the HTTP status code as an integer. - * @return the HTTP status as an integer value - * @throws IOException in case of I/O errors - * @since 3.1.1 - * @see #getStatusCode() - * @deprecated in favor of {@link #getStatusCode()}, for removal in 7.0 - */ - @Deprecated(since = "6.0", forRemoval = true) - default int getRawStatusCode() throws IOException { - return getStatusCode().value(); - } - /** * Get the HTTP status text of the response. * @return the HTTP status text diff --git a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java index 6d62a8ffbb34..3f3825c62f0c 100644 --- a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java +++ b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java @@ -209,18 +209,6 @@ public void setReadTimeout(Duration readTimeout) { this.readTimeout = readTimeout.toMillis(); } - /** - * Indicates whether this request factory should buffer the request body internally. - *

    Default is {@code true}. When sending large amounts of data via POST or PUT, it is - * recommended to change this property to {@code false}, so as not to run out of memory. - * @since 4.0 - * @deprecated since 6.1 requests are never buffered, as if this property is {@code false} - */ - @Deprecated(since = "6.1", forRemoval = true) - public void setBufferRequestBody(boolean bufferRequestBody) { - // no-op - } - /** * Configure a factory to pre-create the {@link HttpContext} for each request. *

    This may be useful for example in mutual TLS authentication where a diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequest.java deleted file mode 100644 index f6448d92448e..000000000000 --- a/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequest.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2002-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.http.client; - -import java.io.IOException; -import java.net.URI; - -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okio.BufferedSink; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.lang.Nullable; -import org.springframework.util.StringUtils; - -/** - * {@link ClientHttpRequest} implementation based on OkHttp 3.x. - * - *

    Created via the {@link OkHttp3ClientHttpRequestFactory}. - * - * @author Luciano Leggieri - * @author Arjen Poutsma - * @author Roy Clarkson - * @since 4.3 - * @deprecated since 6.1, in favor of other HTTP client libraries; - * scheduled for removal in 7.0 - */ -@Deprecated(since = "6.1", forRemoval = true) -class OkHttp3ClientHttpRequest extends AbstractStreamingClientHttpRequest { - - private final OkHttpClient client; - - private final URI uri; - - private final HttpMethod method; - - - public OkHttp3ClientHttpRequest(OkHttpClient client, URI uri, HttpMethod method) { - this.client = client; - this.uri = uri; - this.method = method; - } - - - @Override - public HttpMethod getMethod() { - return this.method; - } - - @Override - public URI getURI() { - return this.uri; - } - - @Override - @SuppressWarnings("removal") - protected ClientHttpResponse executeInternal(HttpHeaders headers, @Nullable Body body) throws IOException { - - RequestBody requestBody; - if (body != null) { - requestBody = new BodyRequestBody(headers, body); - } - else if (okhttp3.internal.http.HttpMethod.requiresRequestBody(getMethod().name())) { - String header = headers.getFirst(HttpHeaders.CONTENT_TYPE); - MediaType contentType = (header != null) ? MediaType.parse(header) : null; - requestBody = RequestBody.create(contentType, new byte[0]); - } - else { - requestBody = null; - } - Request.Builder builder = new Request.Builder() - .url(this.uri.toURL()); - builder.method(this.method.name(), requestBody); - headers.forEach((headerName, headerValues) -> { - for (String headerValue : headerValues) { - builder.addHeader(headerName, headerValue); - } - }); - Request request = builder.build(); - return new OkHttp3ClientHttpResponse(this.client.newCall(request).execute()); - } - - - private static class BodyRequestBody extends RequestBody { - - private final HttpHeaders headers; - - private final Body body; - - - public BodyRequestBody(HttpHeaders headers, Body body) { - this.headers = headers; - this.body = body; - } - - @Override - public long contentLength() { - return this.headers.getContentLength(); - } - - @Nullable - @Override - public MediaType contentType() { - String contentType = this.headers.getFirst(HttpHeaders.CONTENT_TYPE); - if (StringUtils.hasText(contentType)) { - return MediaType.parse(contentType); - } - else { - return null; - } - } - - @Override - public void writeTo(BufferedSink sink) throws IOException { - this.body.writeTo(sink.outputStream()); - } - - @Override - public boolean isOneShot() { - return !this.body.repeatable(); - } - } - - -} diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactory.java deleted file mode 100644 index 6a9a637e339c..000000000000 --- a/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactory.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2002-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.http.client; - -import java.io.IOException; -import java.net.URI; -import java.time.Duration; -import java.util.concurrent.TimeUnit; - -import okhttp3.Cache; -import okhttp3.OkHttpClient; - -import org.springframework.beans.factory.DisposableBean; -import org.springframework.http.HttpMethod; -import org.springframework.util.Assert; - -/** - * {@link ClientHttpRequestFactory} implementation that uses - * OkHttp 3.x to create requests. - * - * @author Luciano Leggieri - * @author Arjen Poutsma - * @author Roy Clarkson - * @since 4.3 - * @deprecated since 6.1, in favor of other {@link ClientHttpRequestFactory} implementations; - * scheduled for removal in 7.0 - */ -@Deprecated(since = "6.1", forRemoval = true) -public class OkHttp3ClientHttpRequestFactory implements ClientHttpRequestFactory, DisposableBean { - - private OkHttpClient client; - - private final boolean defaultClient; - - - /** - * Create a factory with a default {@link OkHttpClient} instance. - */ - public OkHttp3ClientHttpRequestFactory() { - this.client = new OkHttpClient(); - this.defaultClient = true; - } - - /** - * Create a factory with the given {@link OkHttpClient} instance. - * @param client the client to use - */ - public OkHttp3ClientHttpRequestFactory(OkHttpClient client) { - Assert.notNull(client, "OkHttpClient must not be null"); - this.client = client; - this.defaultClient = false; - } - - - /** - * Set the underlying read timeout in milliseconds. - * A value of 0 specifies an infinite timeout. - */ - public void setReadTimeout(int readTimeout) { - this.client = this.client.newBuilder() - .readTimeout(readTimeout, TimeUnit.MILLISECONDS) - .build(); - } - - /** - * Set the underlying read timeout in milliseconds. - * A value of 0 specifies an infinite timeout. - * @since 6.1 - */ - public void setReadTimeout(Duration readTimeout) { - this.client = this.client.newBuilder() - .readTimeout(readTimeout) - .build(); - } - - /** - * Set the underlying write timeout in milliseconds. - * A value of 0 specifies an infinite timeout. - */ - public void setWriteTimeout(int writeTimeout) { - this.client = this.client.newBuilder() - .writeTimeout(writeTimeout, TimeUnit.MILLISECONDS) - .build(); - } - - /** - * Set the underlying write timeout in milliseconds. - * A value of 0 specifies an infinite timeout. - * @since 6.1 - */ - public void setWriteTimeout(Duration writeTimeout) { - this.client = this.client.newBuilder() - .writeTimeout(writeTimeout) - .build(); - } - - /** - * Set the underlying connect timeout in milliseconds. - * A value of 0 specifies an infinite timeout. - */ - public void setConnectTimeout(int connectTimeout) { - this.client = this.client.newBuilder() - .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS) - .build(); - } - - /** - * Set the underlying connect timeout in milliseconds. - * A value of 0 specifies an infinite timeout. - * @since 6.1 - */ - public void setConnectTimeout(Duration connectTimeout) { - this.client = this.client.newBuilder() - .connectTimeout(connectTimeout) - .build(); - } - - - @Override - @SuppressWarnings("removal") - public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) { - return new OkHttp3ClientHttpRequest(this.client, uri, httpMethod); - } - - - @Override - public void destroy() throws IOException { - if (this.defaultClient) { - // Clean up the client if we created it in the constructor - Cache cache = this.client.cache(); - if (cache != null) { - cache.close(); - } - this.client.dispatcher().executorService().shutdown(); - this.client.connectionPool().evictAll(); - } - } - -} diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpResponse.java deleted file mode 100644 index 1e233e82ad52..000000000000 --- a/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpResponse.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2002-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.http.client; - -import java.io.IOException; -import java.io.InputStream; - -import okhttp3.Response; -import okhttp3.ResponseBody; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatusCode; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * {@link ClientHttpResponse} implementation based on OkHttp 3.x. - * - * @author Luciano Leggieri - * @author Arjen Poutsma - * @author Roy Clarkson - * @since 4.3 - * @deprecated since 6.1, in favor of other HTTP client libraries; - * scheduled for removal in 7.0 - */ -@Deprecated(since = "6.1", forRemoval = true) -class OkHttp3ClientHttpResponse implements ClientHttpResponse { - - private final Response response; - - @Nullable - private volatile HttpHeaders headers; - - - public OkHttp3ClientHttpResponse(Response response) { - Assert.notNull(response, "Response must not be null"); - this.response = response; - } - - - @Override - public HttpStatusCode getStatusCode() throws IOException { - return HttpStatusCode.valueOf(this.response.code()); - } - - @Override - public String getStatusText() { - return this.response.message(); - } - - @Override - public InputStream getBody() throws IOException { - ResponseBody body = this.response.body(); - return (body != null ? body.byteStream() : InputStream.nullInputStream()); - } - - @Override - public HttpHeaders getHeaders() { - HttpHeaders headers = this.headers; - if (headers == null) { - headers = new HttpHeaders(); - for (String headerName : this.response.headers().names()) { - for (String headerValue : this.response.headers(headerName)) { - headers.add(headerName, headerValue); - } - } - this.headers = headers; - } - return headers; - } - - @Override - public void close() { - ResponseBody body = this.response.body(); - if (body != null) { - body.close(); - } - } - -} diff --git a/spring-web/src/main/java/org/springframework/http/client/ReactorClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/ReactorClientHttpRequest.java index feddda57a763..b55dfee56014 100644 --- a/spring-web/src/main/java/org/springframework/http/client/ReactorClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/ReactorClientHttpRequest.java @@ -81,24 +81,6 @@ public ReactorClientHttpRequest(HttpClient httpClient, HttpMethod method, URI ur this.exchangeTimeout = exchangeTimeout; } - /** - * Original constructor with timeout values. - * @deprecated without a replacement; readTimeout is now applied to the - * underlying client via {@link HttpClient#responseTimeout(Duration)}, and the - * value passed here is not used; exchangeTimeout is deprecated and superseded - * by Reactor Netty timeout configuration, but applied if set. - */ - @Deprecated(since = "6.2", forRemoval = true) - public ReactorClientHttpRequest( - HttpClient httpClient, URI uri, HttpMethod method, - @Nullable Duration exchangeTimeout, @Nullable Duration readTimeout) { - - this.httpClient = httpClient; - this.method = method; - this.uri = uri; - this.exchangeTimeout = exchangeTimeout; - } - @Override public HttpMethod getMethod() { diff --git a/spring-web/src/main/java/org/springframework/http/client/ReactorClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/ReactorClientHttpRequestFactory.java index f3366497cc3e..63cfc19ffaca 100644 --- a/spring-web/src/main/java/org/springframework/http/client/ReactorClientHttpRequestFactory.java +++ b/spring-web/src/main/java/org/springframework/http/client/ReactorClientHttpRequestFactory.java @@ -180,36 +180,6 @@ public void setReadTimeout(long readTimeout) { setReadTimeout(Duration.ofMillis(readTimeout)); } - /** - * Set the timeout for the HTTP exchange in milliseconds. - *

    By default, as of 6.2 this is no longer set. - * @see #setConnectTimeout(int) - * @see #setReadTimeout(Duration) - * @see Timeout Configuration - * @deprecated as of 6.2 and no longer set by default (previously 5 seconds) - * in favor of using Reactor Netty HttpClient timeout configuration. - */ - @Deprecated(since = "6.2", forRemoval = true) - public void setExchangeTimeout(long exchangeTimeout) { - Assert.isTrue(exchangeTimeout > 0, "Timeout must be a positive value"); - this.exchangeTimeout = Duration.ofMillis(exchangeTimeout); - } - - /** - * Variant of {@link #setExchangeTimeout(long)} with a Duration value. - *

    By default, as of 6.2 this is no longer set. - * @see #setConnectTimeout(int) - * @see #setReadTimeout(Duration) - * @see Timeout Configuration - * @deprecated as of 6.2 and no longer set by default (previously 5 seconds) - * in favor of using Reactor Netty HttpClient timeout configuration. - */ - @Deprecated(since = "6.2", forRemoval = true) - public void setExchangeTimeout(Duration exchangeTimeout) { - Assert.notNull(exchangeTimeout, "ExchangeTimeout must not be null"); - setExchangeTimeout((int) exchangeTimeout.toMillis()); - } - @Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { diff --git a/spring-web/src/main/java/org/springframework/http/client/ReactorClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/ReactorClientHttpResponse.java index b83395a163b1..4f92c0997937 100644 --- a/spring-web/src/main/java/org/springframework/http/client/ReactorClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/ReactorClientHttpResponse.java @@ -18,12 +18,10 @@ import java.io.IOException; import java.io.InputStream; -import java.time.Duration; import io.netty.buffer.ByteBuf; import org.reactivestreams.FlowAdapters; import reactor.netty.Connection; -import reactor.netty.http.client.HttpClient; import reactor.netty.http.client.HttpClientResponse; import org.springframework.http.HttpHeaders; @@ -64,21 +62,6 @@ public ReactorClientHttpResponse(HttpClientResponse response, Connection connect new Netty4HeadersAdapter(response.responseHeaders())); } - /** - * Original constructor. - * @deprecated without a replacement; readTimeout is now applied to the - * underlying client via {@link HttpClient#responseTimeout(Duration)}, and the - * value passed here is not used. - */ - @Deprecated(since = "6.2", forRemoval = true) - public ReactorClientHttpResponse( - HttpClientResponse response, Connection connection, @Nullable Duration readTimeout) { - - this.response = response; - this.connection = connection; - this.headers = HttpHeaders.readOnlyHttpHeaders(new Netty4HeadersAdapter(response.responseHeaders())); - } - @Override public HttpStatusCode getStatusCode() { diff --git a/spring-web/src/main/java/org/springframework/http/client/ReactorNettyClientRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/ReactorNettyClientRequestFactory.java deleted file mode 100644 index a116c071385e..000000000000 --- a/spring-web/src/main/java/org/springframework/http/client/ReactorNettyClientRequestFactory.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2002-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.http.client; - -import java.util.function.Function; - -import reactor.netty.http.client.HttpClient; - -/** - * Reactor-Netty implementation of {@link ClientHttpRequestFactory}. - * - * @author Arjen Poutsma - * @author Juergen Hoeller - * @since 6.1 - * @deprecated in favor of the renamed {@link ReactorClientHttpRequestFactory} - */ -@Deprecated(since = "6.2", forRemoval = true) -public class ReactorNettyClientRequestFactory extends ReactorClientHttpRequestFactory { - - /** - * Superseded by {@link ReactorClientHttpRequestFactory}. - * @see ReactorClientHttpRequestFactory#ReactorClientHttpRequestFactory() - */ - public ReactorNettyClientRequestFactory() { - super(); - } - - /** - * Superseded by {@link ReactorClientHttpRequestFactory}. - * @see ReactorClientHttpRequestFactory#ReactorClientHttpRequestFactory(HttpClient) - */ - public ReactorNettyClientRequestFactory(HttpClient httpClient) { - super(httpClient); - } - - /** - * Superseded by {@link ReactorClientHttpRequestFactory}. - * @see ReactorClientHttpRequestFactory#ReactorClientHttpRequestFactory(ReactorResourceFactory, Function) - */ - public ReactorNettyClientRequestFactory(ReactorResourceFactory resourceFactory, Function mapper) { - super(resourceFactory, mapper); - } - -} diff --git a/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java index ec2b075bd53f..995f73c9e239 100644 --- a/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java +++ b/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -59,24 +59,6 @@ public void setProxy(Proxy proxy) { this.proxy = proxy; } - /** - * Indicate whether this request factory should buffer the - * {@linkplain ClientHttpRequest#getBody() request body} internally. - *

    Default is {@code true}. When sending large amounts of data via POST or PUT, - * it is recommended to change this property to {@code false}, so as not to run - * out of memory. This will result in a {@link ClientHttpRequest} that either - * streams directly to the underlying {@link HttpURLConnection} (if the - * {@link org.springframework.http.HttpHeaders#getContentLength() Content-Length} - * is known in advance), or that will use "Chunked transfer encoding" - * (if the {@code Content-Length} is not known in advance). - * @see #setChunkSize(int) - * @see HttpURLConnection#setFixedLengthStreamingMode(int) - * @deprecated since 6.1 requests are never buffered, as if this property is {@code false} - */ - @Deprecated(since = "6.1", forRemoval = true) - public void setBufferRequestBody(boolean bufferRequestBody) { - } - /** * Set the number of bytes to write in each chunk when not buffering request * bodies locally. @@ -134,20 +116,6 @@ public void setReadTimeout(Duration readTimeout) { this.readTimeout = (int) readTimeout.toMillis(); } - /** - * Set if the underlying URLConnection can be set to 'output streaming' mode. - * Default is {@code true}. - *

    When output streaming is enabled, authentication and redirection cannot be handled automatically. - * If output streaming is disabled, the {@link HttpURLConnection#setFixedLengthStreamingMode} and - * {@link HttpURLConnection#setChunkedStreamingMode} methods of the underlying connection will never - * be called. - * @param outputStreaming if output streaming is enabled - * @deprecated as of 6.1 requests are always streamed, as if this property is {@code true} - */ - @Deprecated(since = "6.1", forRemoval = true) - public void setOutputStreaming(boolean outputStreaming) { - } - @Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/ClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ClientHttpResponse.java index f031558d9ba1..23711637ccd0 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/ClientHttpResponse.java @@ -46,18 +46,6 @@ default String getId() { */ HttpStatusCode getStatusCode(); - /** - * Return the HTTP status code as an integer. - * @return the HTTP status as an integer value - * @since 5.0.6 - * @see #getStatusCode() - * @deprecated in favor of {@link #getStatusCode()}, for removal in 7.0 - */ - @Deprecated(since = "6.0", forRemoval = true) - default int getRawStatusCode() { - return getStatusCode().value(); - } - /** * Return a read-only map of response cookies received from the server. */ diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpResponse.java index 0e8c6c68b1d9..3d89eaad7a44 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorNetty2ServerHttpResponse.java @@ -76,14 +76,6 @@ public HttpStatusCode getStatusCode() { return (status != null ? status : HttpStatusCode.valueOf(this.response.status().code())); } - @Override - @Deprecated - @SuppressWarnings("removal") - public Integer getRawStatusCode() { - Integer status = super.getRawStatusCode(); - return (status != null ? status : this.response.status().code()); - } - @Override protected void applyStatusCode() { HttpStatusCode status = super.getStatusCode(); diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java index 359629ff75d0..d0f8cea29ef9 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java @@ -75,14 +75,6 @@ public HttpStatusCode getStatusCode() { return (status != null ? status : HttpStatusCode.valueOf(this.response.status().code())); } - @Override - @Deprecated - @SuppressWarnings("removal") - public Integer getRawStatusCode() { - Integer status = super.getRawStatusCode(); - return (status != null ? status : this.response.status().code()); - } - @Override protected void applyStatusCode() { HttpStatusCode status = super.getStatusCode(); diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java index f3af2a0ceb45..11194f21541c 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -60,20 +60,6 @@ default boolean setRawStatusCode(@Nullable Integer value) { return setStatusCode(value != null ? HttpStatusCode.valueOf(value) : null); } - /** - * Return the status code that has been set, or otherwise fall back on the - * status of the response from the underlying server. The return value may - * be {@code null} if there is no default value from the underlying server. - * @since 5.2.4 - * @deprecated in favor of {@link #getStatusCode()}, for removal in 7.0 - */ - @Deprecated(since = "6.0", forRemoval = true) - @Nullable - default Integer getRawStatusCode() { - HttpStatusCode httpStatus = getStatusCode(); - return (httpStatus != null ? httpStatus.value() : null); - } - /** * Return a mutable map with the cookies to send to the server. */ diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpResponseDecorator.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpResponseDecorator.java index 4a15989648d9..f9f7fda82839 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpResponseDecorator.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServerHttpResponseDecorator.java @@ -71,14 +71,6 @@ public boolean setRawStatusCode(@Nullable Integer value) { return getDelegate().setRawStatusCode(value); } - @Override - @Nullable - @Deprecated - @SuppressWarnings("removal") - public Integer getRawStatusCode() { - return getDelegate().getRawStatusCode(); - } - @Override public HttpHeaders getHeaders() { return getDelegate().getHeaders(); diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java index ccaf9b5733d4..c2ccdbbcdda6 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java @@ -104,14 +104,6 @@ public HttpStatusCode getStatusCode() { return (status != null ? status : HttpStatusCode.valueOf(this.response.getStatus())); } - @Override - @Deprecated - @SuppressWarnings("removal") - public Integer getRawStatusCode() { - Integer status = super.getRawStatusCode(); - return (status != null ? status : this.response.getStatus()); - } - @Override protected void applyStatusCode() { HttpStatusCode status = super.getStatusCode(); diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java index 9de01ff320ef..65ed91a6042b 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java @@ -87,14 +87,6 @@ public HttpStatusCode getStatusCode() { return (status != null ? status : HttpStatusCode.valueOf(this.exchange.getStatusCode())); } - @Override - @Deprecated - @SuppressWarnings("removal") - public Integer getRawStatusCode() { - Integer status = super.getRawStatusCode(); - return (status != null ? status : this.exchange.getStatusCode()); - } - @Override protected void applyStatusCode() { HttpStatusCode status = super.getStatusCode(); diff --git a/spring-web/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java b/spring-web/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java index b1ce0f525879..93480e371276 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java +++ b/spring-web/src/main/java/org/springframework/web/bind/MissingServletRequestParameterException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -66,25 +66,6 @@ public MissingServletRequestParameterException( getBody().setDetail(initBodyDetail(this.parameterName)); } - /** - * Constructor for use when a value was present but converted to {@code null}. - * @param parameterName the name of the missing parameter - * @param parameterType the expected type of the missing parameter - * @param missingAfterConversion whether the value became null after conversion - * @since 5.3.6 - * @deprecated in favor of {@link #MissingServletRequestParameterException(String, MethodParameter, boolean)} - */ - @Deprecated(since = "6.1", forRemoval = true) - public MissingServletRequestParameterException( - String parameterName, String parameterType, boolean missingAfterConversion) { - - super("", missingAfterConversion, null, new Object[] {parameterName}); - this.parameterName = parameterName; - this.parameterType = parameterType; - this.parameter = null; - getBody().setDetail(initBodyDetail(this.parameterName)); - } - private static String initBodyDetail(String name) { return "Required parameter '" + name + "' is not present."; } diff --git a/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java b/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java index 1388224300b4..b9767b6b8394 100644 --- a/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java +++ b/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java @@ -136,31 +136,9 @@ protected boolean hasError(int statusCode) { */ @Override public void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { - - // For backwards compatibility try handle(response) first - HandleErrorResponseDecorator decorator = new HandleErrorResponseDecorator(response); - handleError(decorator); - if (decorator.isHandled()) { - return; - } - handleError(response, response.getStatusCode(), url, method); } - @SuppressWarnings("removal") - @Override - public void handleError(ClientHttpResponse response) throws IOException { - - // Called via handleError(url, method, response) - if (response instanceof HandleErrorResponseDecorator decorator) { - decorator.setNotHandled(); - return; - } - - // Called directly, so do handle - handleError(response, response.getStatusCode(), null, null); - } - /** * Handle the error based on the resolved status code. *

    The default implementation delegates to @@ -288,22 +266,4 @@ public InputStream getBody() { }; } - - private static class HandleErrorResponseDecorator extends ClientHttpResponseDecorator { - - private boolean handled = true; - - public HandleErrorResponseDecorator(ClientHttpResponse delegate) { - super(delegate); - } - - public void setNotHandled() { - this.handled = false; - } - - public boolean isHandled() { - return this.handled; - } - } - } diff --git a/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java b/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java index be96fb29a11a..088b4138242c 100644 --- a/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java +++ b/spring-web/src/main/java/org/springframework/web/client/ResponseErrorHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-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. @@ -52,19 +52,6 @@ public interface ResponseErrorHandler { * @since 5.0 */ default void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { - handleError(response); - } - - /** - * Handle the error in the given response. - *

    This method is only called when {@link #hasError(ClientHttpResponse)} - * has returned {@code true}. - * @param response the response with the error - * @throws IOException in case of I/O errors - * @deprecated in favor of {@link #handleError(URI, HttpMethod, ClientHttpResponse)} - */ - @Deprecated(since = "6.2.1", forRemoval = true) - default void handleError(ClientHttpResponse response) throws IOException { } } diff --git a/spring-web/src/main/java/org/springframework/web/filter/reactive/ServerHttpObservationFilter.java b/spring-web/src/main/java/org/springframework/web/filter/reactive/ServerHttpObservationFilter.java deleted file mode 100644 index 8782c9d26391..000000000000 --- a/spring-web/src/main/java/org/springframework/web/filter/reactive/ServerHttpObservationFilter.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2002-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.web.filter.reactive; - -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationRegistry; -import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; -import reactor.core.observability.DefaultSignalListener; -import reactor.core.publisher.Mono; -import reactor.util.context.Context; - -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention; -import org.springframework.http.server.reactive.observation.ServerHttpObservationDocumentation; -import org.springframework.http.server.reactive.observation.ServerRequestObservationContext; -import org.springframework.http.server.reactive.observation.ServerRequestObservationConvention; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebFilter; -import org.springframework.web.server.WebFilterChain; -import org.springframework.web.server.adapter.WebHttpHandlerBuilder; - -/** - * {@link org.springframework.web.server.WebFilter} that creates {@link Observation observations} - * for HTTP exchanges. This collects information about the execution time and - * information gathered from the {@link ServerRequestObservationContext}. - *

    Web Frameworks can fetch the current {@link ServerRequestObservationContext context} - * as a {@link #CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE request attribute} and contribute - * additional information to it. - * The configured {@link ServerRequestObservationConvention} will use this context to collect - * {@link io.micrometer.common.KeyValue metadata} and attach it to the observation. - * - * @author Brian Clozel - * @since 6.0 - * @deprecated since 6.1 in favor of {@link WebHttpHandlerBuilder}. - */ -@Deprecated(since = "6.1", forRemoval = true) -public class ServerHttpObservationFilter implements WebFilter { - - /** - * Name of the request attribute holding the {@link ServerRequestObservationContext context} for the current observation. - */ - public static final String CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE = ServerHttpObservationFilter.class.getName() + ".context"; - - private static final ServerRequestObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultServerRequestObservationConvention(); - - private final ObservationRegistry observationRegistry; - - private final ServerRequestObservationConvention observationConvention; - - /** - * Create an {@code HttpRequestsObservationWebFilter} that records observations - * against the given {@link ObservationRegistry}. The default - * {@link DefaultServerRequestObservationConvention convention} will be used. - * @param observationRegistry the registry to use for recording observations - */ - public ServerHttpObservationFilter(ObservationRegistry observationRegistry) { - this(observationRegistry, DEFAULT_OBSERVATION_CONVENTION); - } - - /** - * Create an {@code HttpRequestsObservationWebFilter} that records observations - * against the given {@link ObservationRegistry} with a custom convention. - * @param observationRegistry the registry to use for recording observations - * @param observationConvention the convention to use for all recorded observations - */ - public ServerHttpObservationFilter(ObservationRegistry observationRegistry, ServerRequestObservationConvention observationConvention) { - this.observationRegistry = observationRegistry; - this.observationConvention = observationConvention; - } - - /** - * Get the current {@link ServerRequestObservationContext observation context} from the given request, if available. - * @param exchange the current exchange - * @return the current observation context - */ - public static Optional findObservationContext(ServerWebExchange exchange) { - return Optional.ofNullable(exchange.getAttribute(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE)); - } - - @Override - public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - ServerRequestObservationContext observationContext = new ServerRequestObservationContext(exchange.getRequest(), - exchange.getResponse(), exchange.getAttributes()); - exchange.getAttributes().put(CURRENT_OBSERVATION_CONTEXT_ATTRIBUTE, observationContext); - return chain.filter(exchange).tap(() -> new ObservationSignalListener(observationContext)); - } - - private final class ObservationSignalListener extends DefaultSignalListener { - - private static final Set DISCONNECTED_CLIENT_EXCEPTIONS = Set.of("AbortedException", - "ClientAbortException", "EOFException", "EofException"); - - private final ServerRequestObservationContext observationContext; - - private final Observation observation; - - private final AtomicBoolean observationRecorded = new AtomicBoolean(); - - ObservationSignalListener(ServerRequestObservationContext observationContext) { - this.observationContext = observationContext; - this.observation = ServerHttpObservationDocumentation.HTTP_REACTIVE_SERVER_REQUESTS.observation(observationConvention, - DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, observationRegistry); - } - - - @Override - public Context addToContext(Context originalContext) { - return originalContext.put(ObservationThreadLocalAccessor.KEY, this.observation); - } - - @Override - public void doFirst() throws Throwable { - this.observation.start(); - } - - @Override - public void doOnCancel() throws Throwable { - if (this.observationRecorded.compareAndSet(false, true)) { - this.observationContext.setConnectionAborted(true); - this.observation.stop(); - } - } - - @Override - public void doOnComplete() throws Throwable { - if (this.observationRecorded.compareAndSet(false, true)) { - doOnTerminate(this.observationContext); - } - } - - @Override - public void doOnError(Throwable error) throws Throwable { - if (this.observationRecorded.compareAndSet(false, true)) { - if (DISCONNECTED_CLIENT_EXCEPTIONS.contains(error.getClass().getSimpleName())) { - this.observationContext.setConnectionAborted(true); - } - this.observationContext.setError(error); - doOnTerminate(this.observationContext); - } - } - - private void doOnTerminate(ServerRequestObservationContext context) { - ServerHttpResponse response = context.getResponse(); - if (response != null) { - if (response.isCommitted()) { - this.observation.stop(); - } - else { - response.beforeCommit(() -> { - this.observation.stop(); - return Mono.empty(); - }); - } - } - } - } - -} diff --git a/spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java b/spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java index 54dce91f4059..70b6c3877941 100644 --- a/spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java +++ b/spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-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. @@ -88,11 +88,10 @@ private String formatError(Throwable ex, ServerHttpRequest request) { return "Resolved [" + className + ": " + message + "] for HTTP " + request.getMethod() + " " + path; } - @SuppressWarnings("deprecation") private boolean updateResponse(ServerHttpResponse response, Throwable ex) { boolean result = false; HttpStatusCode statusCode = determineStatus(ex); - int code = (statusCode != null ? statusCode.value() : determineRawStatusCode(ex)); + int code = (statusCode != null ? statusCode.value() : -1); if (code != -1) { if (response.setStatusCode(statusCode)) { if (ex instanceof ResponseStatusException responseStatusException) { @@ -127,19 +126,4 @@ protected HttpStatusCode determineStatus(Throwable ex) { } } - /** - * Determine the raw status code for the given exception. - * @param ex the exception to check - * @return the associated HTTP status code, or -1 if it can't be derived. - * @since 5.3 - * @deprecated in favor of {@link #determineStatus(Throwable)}, for removal in 7.0 - */ - @Deprecated(since = "6.0", forRemoval = true) - protected int determineRawStatusCode(Throwable ex) { - if (ex instanceof ResponseStatusException responseStatusException) { - return responseStatusException.getStatusCode().value(); - } - return -1; - } - } diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/AbstractReactorHttpExchangeAdapter.java b/spring-web/src/main/java/org/springframework/web/service/invoker/AbstractReactorHttpExchangeAdapter.java index 653a2b262f62..a11e6f516731 100644 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/AbstractReactorHttpExchangeAdapter.java +++ b/spring-web/src/main/java/org/springframework/web/service/invoker/AbstractReactorHttpExchangeAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -18,9 +18,6 @@ import java.time.Duration; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.http.HttpHeaders; @@ -35,9 +32,7 @@ * @author Rossen Stoyanchev * @since 6.1 */ -@SuppressWarnings("removal") -public abstract class AbstractReactorHttpExchangeAdapter - implements ReactorHttpExchangeAdapter, org.springframework.web.service.invoker.HttpClientAdapter { +public abstract class AbstractReactorHttpExchangeAdapter implements ReactorHttpExchangeAdapter { private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance(); @@ -126,46 +121,4 @@ public ResponseEntity exchangeForEntity( return entity; } - - // HttpClientAdapter implementation - - @Override - public Mono requestToVoid(HttpRequestValues requestValues) { - return exchangeForMono(requestValues); - } - - @Override - public Mono requestToHeaders(HttpRequestValues requestValues) { - return exchangeForHeadersMono(requestValues); - } - - @Override - public Mono requestToBody(HttpRequestValues requestValues, ParameterizedTypeReference bodyType) { - return exchangeForBodyMono(requestValues, bodyType); - } - - @Override - public Flux requestToBodyFlux(HttpRequestValues requestValues, ParameterizedTypeReference bodyType) { - return exchangeForBodyFlux(requestValues, bodyType); - } - - @Override - public Mono> requestToBodilessEntity(HttpRequestValues requestValues) { - return exchangeForBodilessEntityMono(requestValues); - } - - @Override - public Mono> requestToEntity( - HttpRequestValues requestValues, ParameterizedTypeReference bodyType) { - - return exchangeForEntityMono(requestValues, bodyType); - } - - @Override - public Mono>> requestToEntityFlux( - HttpRequestValues requestValues, ParameterizedTypeReference bodyType) { - - return exchangeForEntityFlux(requestValues, bodyType); - } - } diff --git a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpClientAdapter.java b/spring-web/src/main/java/org/springframework/web/service/invoker/HttpClientAdapter.java deleted file mode 100644 index 75eaeb36fbbf..000000000000 --- a/spring-web/src/main/java/org/springframework/web/service/invoker/HttpClientAdapter.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2002-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.web.service.invoker; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; - -/** - * Contract to abstract the underlying HTTP client and decouple it from the - * {@linkplain HttpServiceProxyFactory#createClient(Class) HTTP service proxy}. - * - * @author Rossen Stoyanchev - * @author Olga Maciaszek-Sharma - * @since 6.0 - * @deprecated in favor of {@link ReactorHttpExchangeAdapter} - */ -@Deprecated(since = "6.1", forRemoval = true) -public interface HttpClientAdapter { - - /** - * Perform the given request, and release the response content, if any. - * @param requestValues the request to perform - * @return {@code Mono} that completes when the request is fully executed - * and the response content is released. - */ - Mono requestToVoid(HttpRequestValues requestValues); - - /** - * Perform the given request, release the response content, and return the - * response headers. - * @param requestValues the request to perform - * @return {@code Mono} that returns the response headers the request is - * fully executed and the response content released. - */ - Mono requestToHeaders(HttpRequestValues requestValues); - - /** - * Perform the given request and decode the response content to the given type. - * @param requestValues the request to perform - * @param bodyType the target type to decode to - * @return {@code Mono} that returns the decoded response. - * @param the type the response is decoded to - */ - Mono requestToBody(HttpRequestValues requestValues, ParameterizedTypeReference bodyType); - - /** - * Perform the given request and decode the response content to a stream with - * elements of the given type. - * @param requestValues the request to perform - * @param bodyType the target stream element type to decode to - * @return {@code Flux} with decoded stream elements. - * @param the type the response is decoded to - */ - Flux requestToBodyFlux(HttpRequestValues requestValues, ParameterizedTypeReference bodyType); - - /** - * Variant of {@link #requestToVoid(HttpRequestValues)} with additional - * access to the response status and headers. - */ - Mono> requestToBodilessEntity(HttpRequestValues requestValues); - - /** - * Variant of {@link #requestToBody(HttpRequestValues, ParameterizedTypeReference)} - * with additional access to the response status and headers. - */ - Mono> requestToEntity(HttpRequestValues requestValues, ParameterizedTypeReference bodyType); - - /** - * Variant of {@link #requestToBodyFlux(HttpRequestValues, ParameterizedTypeReference)} - * with additional access to the response status and headers. - */ - Mono>> requestToEntityFlux(HttpRequestValues requestValues, ParameterizedTypeReference bodyType); - - - /** - * Adapt this instance to {@link ReactorHttpExchangeAdapter}. - * @since 6.1 - */ - default ReactorHttpExchangeAdapter asReactorExchangeAdapter() { - - return new AbstractReactorHttpExchangeAdapter() { - - @Override - public boolean supportsRequestAttributes() { - return true; - } - - @Override - public Mono exchangeForMono(HttpRequestValues values) { - return HttpClientAdapter.this.requestToVoid(values); - } - - @Override - public Mono exchangeForHeadersMono(HttpRequestValues values) { - return HttpClientAdapter.this.requestToHeaders(values); - } - - @Override - public Mono exchangeForBodyMono(HttpRequestValues values, ParameterizedTypeReference bodyType) { - return HttpClientAdapter.this.requestToBody(values, bodyType); - } - - @Override - public Flux exchangeForBodyFlux(HttpRequestValues values, ParameterizedTypeReference bodyType) { - return HttpClientAdapter.this.requestToBodyFlux(values, bodyType); - } - - @Override - public Mono> exchangeForBodilessEntityMono(HttpRequestValues values) { - return HttpClientAdapter.this.requestToBodilessEntity(values); - } - - @Override - public Mono> exchangeForEntityMono( - HttpRequestValues requestValues, ParameterizedTypeReference bodyType) { - - return HttpClientAdapter.this.requestToEntity(requestValues, bodyType); - } - - @Override - public Mono>> exchangeForEntityFlux( - HttpRequestValues requestValues, ParameterizedTypeReference bodyType) { - - return HttpClientAdapter.this.requestToEntityFlux(requestValues, bodyType); - } - }; - } - -} diff --git a/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java b/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java index 184ef4a51a17..fe4a465c08cb 100644 --- a/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java +++ b/spring-web/src/main/java/org/springframework/web/util/ContentCachingRequestWrapper.java @@ -86,21 +86,6 @@ public ContentCachingRequestWrapper(HttpServletRequest request, int cacheLimit) this.contentCacheLimit = (cacheLimit > 0 ? cacheLimit : null); } - /** - * Create a new ContentCachingRequestWrapper for the given servlet request. - * @param request the original servlet request - * @deprecated in favor of {@link #ContentCachingRequestWrapper(HttpServletRequest, int)} - * in order to explicitly choose the cache limit - */ - @Deprecated(since = "6.2.1", forRemoval = true) - public ContentCachingRequestWrapper(HttpServletRequest request) { - super(request); - int contentLength = request.getContentLength(); - this.cachedContent = (contentLength > 0 ? - new FastByteArrayOutputStream(contentLength) : new FastByteArrayOutputStream()); - this.contentCacheLimit = null; - } - @Override public ServletInputStream getInputStream() throws IOException { diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java index f436bc2e96e3..422142082a1b 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java @@ -30,7 +30,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.springframework.http.HttpRequest; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -243,34 +242,6 @@ public static UriComponentsBuilder fromHttpUrl(String httpUrl) throws InvalidUrl return fromUriString(httpUrl); } - /** - * Create a new {@code UriComponents} object from the URI associated with - * the given HttpRequest while also overlaying with values from the headers - * "Forwarded" (RFC 7239), - * or "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" if - * "Forwarded" is not found. - * @param request the source request - * @return the URI components of the URI - * @since 4.1.5 - * @deprecated in favor of {@link ForwardedHeaderUtils#adaptFromForwardedHeaders}; - * to be removed in 7.0 - */ - @Deprecated(since = "6.1", forRemoval = true) - public static UriComponentsBuilder fromHttpRequest(HttpRequest request) { - return ForwardedHeaderUtils.adaptFromForwardedHeaders(request.getURI(), request.getHeaders()); - } - - /** - * Create an instance by parsing the "Origin" header of an HTTP request. - * @see RFC 6454 - * @deprecated in favor of {@link UriComponentsBuilder#fromUriString(String)}; - * to be removed in 7.0 - */ - @Deprecated(since = "6.2", forRemoval = true) - public static UriComponentsBuilder fromOriginHeader(String origin) { - return fromUriString(origin); - } - // Encode methods diff --git a/spring-web/src/test/java/org/springframework/http/MediaTypeTests.java b/spring-web/src/test/java/org/springframework/http/MediaTypeTests.java index ff9a2055511c..3ede45acee07 100644 --- a/spring-web/src/test/java/org/springframework/http/MediaTypeTests.java +++ b/spring-web/src/test/java/org/springframework/http/MediaTypeTests.java @@ -19,7 +19,6 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Random; @@ -279,221 +278,6 @@ void isLessSpecific() { assertThat(audioBasic.isLessSpecific(MediaType.TEXT_HTML)).isFalse(); } - @Test - void specificityComparator() { - MediaType audioBasic = new MediaType("audio", "basic"); - MediaType audioWave = new MediaType("audio", "wave"); - MediaType audio = new MediaType("audio"); - MediaType audio03 = new MediaType("audio", "*", 0.3); - MediaType audio07 = new MediaType("audio", "*", 0.7); - MediaType audioBasicLevel = new MediaType("audio", "basic", Collections.singletonMap("level", "1")); - MediaType textHtml = new MediaType("text", "html"); - MediaType allXml = new MediaType("application", "*+xml"); - MediaType all = MediaType.ALL; - - @SuppressWarnings("removal") - Comparator comp = MediaType.SPECIFICITY_COMPARATOR; - - // equal - assertThat(comp.compare(audioBasic, audioBasic)).as("Invalid comparison result").isEqualTo(0); - assertThat(comp.compare(audio, audio)).as("Invalid comparison result").isEqualTo(0); - assertThat(comp.compare(audio07, audio07)).as("Invalid comparison result").isEqualTo(0); - assertThat(comp.compare(audio03, audio03)).as("Invalid comparison result").isEqualTo(0); - assertThat(comp.compare(audioBasicLevel, audioBasicLevel)).as("Invalid comparison result").isEqualTo(0); - - // specific to unspecific - assertThat(comp.compare(audioBasic, audio)).as("Invalid comparison result").isLessThan(0); - assertThat(comp.compare(audioBasic, all)).as("Invalid comparison result").isLessThan(0); - assertThat(comp.compare(audio, all)).as("Invalid comparison result").isLessThan(0); - assertThat(comp.compare(MediaType.APPLICATION_XHTML_XML, allXml)).as("Invalid comparison result").isLessThan(0); - - // unspecific to specific - assertThat(comp.compare(audio, audioBasic)).as("Invalid comparison result").isGreaterThan(0); - assertThat(comp.compare(allXml, MediaType.APPLICATION_XHTML_XML)).as("Invalid comparison result") - .isGreaterThan(0); - assertThat(comp.compare(all, audioBasic)).as("Invalid comparison result").isGreaterThan(0); - assertThat(comp.compare(all, audio)).as("Invalid comparison result").isGreaterThan(0); - - // qualifiers - assertThat(comp.compare(audio, audio07)).as("Invalid comparison result").isLessThan(0); - assertThat(comp.compare(audio07, audio)).as("Invalid comparison result").isGreaterThan(0); - assertThat(comp.compare(audio07, audio03)).as("Invalid comparison result").isLessThan(0); - assertThat(comp.compare(audio03, audio07)).as("Invalid comparison result").isGreaterThan(0); - assertThat(comp.compare(audio03, all)).as("Invalid comparison result").isLessThan(0); - assertThat(comp.compare(all, audio03)).as("Invalid comparison result").isGreaterThan(0); - - // other parameters - assertThat(comp.compare(audioBasic, audioBasicLevel)).as("Invalid comparison result").isGreaterThan(0); - assertThat(comp.compare(audioBasicLevel, audioBasic)).as("Invalid comparison result").isLessThan(0); - - // different types - assertThat(comp.compare(audioBasic, textHtml)).as("Invalid comparison result").isEqualTo(0); - assertThat(comp.compare(textHtml, audioBasic)).as("Invalid comparison result").isEqualTo(0); - - // different subtypes - assertThat(comp.compare(audioBasic, audioWave)).as("Invalid comparison result").isEqualTo(0); - assertThat(comp.compare(audioWave, audioBasic)).as("Invalid comparison result").isEqualTo(0); - } - - @Test - @SuppressWarnings("removal") - public void sortBySpecificityRelated() { - MediaType audioBasic = new MediaType("audio", "basic"); - MediaType audio = new MediaType("audio"); - MediaType audio03 = new MediaType("audio", "*", 0.3); - MediaType audio07 = new MediaType("audio", "*", 0.7); - MediaType audioBasicLevel = new MediaType("audio", "basic", Collections.singletonMap("level", "1")); - MediaType all = MediaType.ALL; - - List expected = new ArrayList<>(); - expected.add(audioBasicLevel); - expected.add(audioBasic); - expected.add(audio); - expected.add(audio07); - expected.add(audio03); - expected.add(all); - - List result = new ArrayList<>(expected); - Random rnd = new Random(); - // shuffle & sort 10 times - for (int i = 0; i < 10; i++) { - Collections.shuffle(result, rnd); - MediaType.sortBySpecificity(result); - - for (int j = 0; j < result.size(); j++) { - assertThat(result.get(j)).as("Invalid media type at " + j).isSameAs(expected.get(j)); - } - } - } - - @Test - @SuppressWarnings("removal") - public void sortBySpecificityUnrelated() { - MediaType audioBasic = new MediaType("audio", "basic"); - MediaType audioWave = new MediaType("audio", "wave"); - MediaType textHtml = new MediaType("text", "html"); - - List expected = new ArrayList<>(); - expected.add(textHtml); - expected.add(audioBasic); - expected.add(audioWave); - - List result = new ArrayList<>(expected); - MediaType.sortBySpecificity(result); - - for (int i = 0; i < result.size(); i++) { - assertThat(result.get(i)).as("Invalid media type at " + i).isSameAs(expected.get(i)); - } - - } - - @Test - void qualityComparator() { - MediaType audioBasic = new MediaType("audio", "basic"); - MediaType audioWave = new MediaType("audio", "wave"); - MediaType audio = new MediaType("audio"); - MediaType audio03 = new MediaType("audio", "*", 0.3); - MediaType audio07 = new MediaType("audio", "*", 0.7); - MediaType audioBasicLevel = new MediaType("audio", "basic", Collections.singletonMap("level", "1")); - MediaType textHtml = new MediaType("text", "html"); - MediaType allXml = new MediaType("application", "*+xml"); - MediaType all = MediaType.ALL; - - @SuppressWarnings("removal") - Comparator comp = MediaType.QUALITY_VALUE_COMPARATOR; - - // equal - assertThat(comp.compare(audioBasic, audioBasic)).as("Invalid comparison result").isEqualTo(0); - assertThat(comp.compare(audio, audio)).as("Invalid comparison result").isEqualTo(0); - assertThat(comp.compare(audio07, audio07)).as("Invalid comparison result").isEqualTo(0); - assertThat(comp.compare(audio03, audio03)).as("Invalid comparison result").isEqualTo(0); - assertThat(comp.compare(audioBasicLevel, audioBasicLevel)).as("Invalid comparison result").isEqualTo(0); - - // specific to unspecific - assertThat(comp.compare(audioBasic, audio)).as("Invalid comparison result").isLessThan(0); - assertThat(comp.compare(audioBasic, all)).as("Invalid comparison result").isLessThan(0); - assertThat(comp.compare(audio, all)).as("Invalid comparison result").isLessThan(0); - assertThat(comp.compare(MediaType.APPLICATION_XHTML_XML, allXml)).as("Invalid comparison result").isLessThan(0); - - // unspecific to specific - assertThat(comp.compare(audio, audioBasic)).as("Invalid comparison result").isGreaterThan(0); - assertThat(comp.compare(all, audioBasic)).as("Invalid comparison result").isGreaterThan(0); - assertThat(comp.compare(all, audio)).as("Invalid comparison result").isGreaterThan(0); - assertThat(comp.compare(allXml, MediaType.APPLICATION_XHTML_XML)).as("Invalid comparison result") - .isGreaterThan(0); - - // qualifiers - assertThat(comp.compare(audio, audio07)).as("Invalid comparison result").isLessThan(0); - assertThat(comp.compare(audio07, audio)).as("Invalid comparison result").isGreaterThan(0); - assertThat(comp.compare(audio07, audio03)).as("Invalid comparison result").isLessThan(0); - assertThat(comp.compare(audio03, audio07)).as("Invalid comparison result").isGreaterThan(0); - assertThat(comp.compare(audio03, all)).as("Invalid comparison result").isGreaterThan(0); - assertThat(comp.compare(all, audio03)).as("Invalid comparison result").isLessThan(0); - - // other parameters - assertThat(comp.compare(audioBasic, audioBasicLevel)).as("Invalid comparison result").isGreaterThan(0); - assertThat(comp.compare(audioBasicLevel, audioBasic)).as("Invalid comparison result").isLessThan(0); - - // different types - assertThat(comp.compare(audioBasic, textHtml)).as("Invalid comparison result").isEqualTo(0); - assertThat(comp.compare(textHtml, audioBasic)).as("Invalid comparison result").isEqualTo(0); - - // different subtypes - assertThat(comp.compare(audioBasic, audioWave)).as("Invalid comparison result").isEqualTo(0); - assertThat(comp.compare(audioWave, audioBasic)).as("Invalid comparison result").isEqualTo(0); - } - - @Test - @SuppressWarnings("removal") - public void sortByQualityRelated() { - MediaType audioBasic = new MediaType("audio", "basic"); - MediaType audio = new MediaType("audio"); - MediaType audio03 = new MediaType("audio", "*", 0.3); - MediaType audio07 = new MediaType("audio", "*", 0.7); - MediaType audioBasicLevel = new MediaType("audio", "basic", Collections.singletonMap("level", "1")); - MediaType all = MediaType.ALL; - - List expected = new ArrayList<>(); - expected.add(audioBasicLevel); - expected.add(audioBasic); - expected.add(audio); - expected.add(all); - expected.add(audio07); - expected.add(audio03); - - List result = new ArrayList<>(expected); - Random rnd = new Random(); - // shuffle & sort 10 times - for (int i = 0; i < 10; i++) { - Collections.shuffle(result, rnd); - MediaType.sortByQualityValue(result); - - for (int j = 0; j < result.size(); j++) { - assertThat(result.get(j)).as("Invalid media type at " + j).isSameAs(expected.get(j)); - } - } - } - - @Test - @SuppressWarnings("removal") - public void sortByQualityUnrelated() { - MediaType audioBasic = new MediaType("audio", "basic"); - MediaType audioWave = new MediaType("audio", "wave"); - MediaType textHtml = new MediaType("text", "html"); - - List expected = new ArrayList<>(); - expected.add(textHtml); - expected.add(audioBasic); - expected.add(audioWave); - - List result = new ArrayList<>(expected); - MediaType.sortBySpecificity(result); - - for (int i = 0; i < result.size(); i++) { - assertThat(result.get(i)).as("Invalid media type at " + i).isSameAs(expected.get(i)); - } - } - @Test void testWithConversionService() { ConversionService conversionService = new DefaultConversionService(); diff --git a/spring-web/src/test/java/org/springframework/http/client/BufferingClientHttpRequestFactoryWithOkHttpTests.java b/spring-web/src/test/java/org/springframework/http/client/BufferingClientHttpRequestFactoryWithOkHttpTests.java deleted file mode 100644 index 0d2436996ba7..000000000000 --- a/spring-web/src/test/java/org/springframework/http/client/BufferingClientHttpRequestFactoryWithOkHttpTests.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2002-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.http.client; - -/** - * Tests for {@link BufferingClientHttpRequestWrapper} for clients - * not supporting non-null, empty request bodies for GET requests. - */ -class BufferingClientHttpRequestFactoryWithOkHttpTests extends AbstractHttpRequestFactoryTests { - - @Override - @SuppressWarnings("removal") - protected ClientHttpRequestFactory createRequestFactory() { - return new BufferingClientHttpRequestFactory(new OkHttp3ClientHttpRequestFactory()); - } - -} diff --git a/spring-web/src/test/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactoryTests.java deleted file mode 100644 index 8ebc5397fad9..000000000000 --- a/spring-web/src/test/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactoryTests.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2002-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.http.client; - -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpMethod; - -/** - * @author Roy Clarkson - */ -class OkHttp3ClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTests { - - @SuppressWarnings("removal") - @Override - protected ClientHttpRequestFactory createRequestFactory() { - return new OkHttp3ClientHttpRequestFactory(); - } - - @Override - @Test - void httpMethods() throws Exception { - super.httpMethods(); - assertHttpMethod("patch", HttpMethod.PATCH); - } - -} diff --git a/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java index 916116fa45d1..074fa8f36a0c 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java @@ -76,12 +76,10 @@ class RestClientIntegrationTests { @interface ParameterizedRestClientTest { } - @SuppressWarnings("removal") static Stream clientHttpRequestFactories() { return Stream.of( argumentSet("JDK HttpURLConnection", new SimpleClientHttpRequestFactory()), argumentSet("HttpComponents", new HttpComponentsClientHttpRequestFactory()), - argumentSet("OkHttp", new org.springframework.http.client.OkHttp3ClientHttpRequestFactory()), argumentSet("Jetty", new JettyClientHttpRequestFactory()), argumentSet("JDK HttpClient", new JdkClientHttpRequestFactory()), argumentSet("Reactor Netty", new ReactorClientHttpRequestFactory()) diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java index a189a518d14b..4a595825229b 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateIntegrationTests.java @@ -90,12 +90,10 @@ class RestTemplateIntegrationTests extends AbstractMockWebServerTests { @interface ParameterizedRestTemplateTest { } - @SuppressWarnings("removal") static Stream clientHttpRequestFactories() { return Stream.of( argumentSet("JDK HttpURLConnection", new SimpleClientHttpRequestFactory()), argumentSet("HttpComponents", new HttpComponentsClientHttpRequestFactory()), - argumentSet("OkHttp", new org.springframework.http.client.OkHttp3ClientHttpRequestFactory()), argumentSet("Jetty", new JettyClientHttpRequestFactory()), argumentSet("JDK HttpClient", new JdkClientHttpRequestFactory()), argumentSet("Reactor Netty", new ReactorClientHttpRequestFactory()) diff --git a/spring-web/src/test/java/org/springframework/web/filter/reactive/ServerHttpObservationFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/reactive/ServerHttpObservationFilterTests.java deleted file mode 100644 index 89e15eef5e9a..000000000000 --- a/spring-web/src/test/java/org/springframework/web/filter/reactive/ServerHttpObservationFilterTests.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2002-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.web.filter.reactive; - - -import java.util.Optional; - -import io.micrometer.observation.Observation; -import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; -import io.micrometer.observation.tck.TestObservationRegistry; -import io.micrometer.observation.tck.TestObservationRegistryAssert; -import org.assertj.core.api.ThrowingConsumer; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.http.server.reactive.observation.ServerRequestObservationContext; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebFilterChain; -import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest; -import org.springframework.web.testfixture.server.MockServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ServerHttpObservationFilter}. - * - * @author Brian Clozel - */ -@SuppressWarnings("removal") -class ServerHttpObservationFilterTests { - - private final TestObservationRegistry observationRegistry = TestObservationRegistry.create(); - - private final ServerHttpObservationFilter filter = new ServerHttpObservationFilter(this.observationRegistry); - - @Test - void filterShouldFillObservationContext() { - ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/test/resource")); - exchange.getResponse().setRawStatusCode(200); - WebFilterChain filterChain = createFilterChain(filterExchange -> { - Optional observationContext = ServerHttpObservationFilter.findObservationContext(filterExchange); - assertThat(observationContext).isPresent(); - assertThat(observationContext.get().getCarrier()).isEqualTo(exchange.getRequest()); - assertThat(observationContext.get().getResponse()).isEqualTo(exchange.getResponse()); - }); - this.filter.filter(exchange, filterChain).block(); - assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS"); - } - - @Test - void filterShouldAddNewObservationToReactorContext() { - ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/test/resource")); - exchange.getResponse().setRawStatusCode(200); - WebFilterChain filterChain = webExchange -> Mono.deferContextual(contextView -> { - Observation observation = contextView.get(ObservationThreadLocalAccessor.KEY); - assertThat(observation).isNotNull(); - // check that the observation was started - assertThat(observation.getContext().getLowCardinalityKeyValue("outcome")).isNotNull(); - return Mono.empty(); - }); - this.filter.filter(exchange, filterChain).block(); - assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SUCCESS"); - } - - @Test - void filterShouldUseThrownException() { - ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/test/resource")); - exchange.getResponse().setRawStatusCode(500); - WebFilterChain filterChain = createFilterChain(filterExchange -> { - throw new IllegalArgumentException("server error"); - }); - StepVerifier.create(this.filter.filter(exchange, filterChain)) - .expectError(IllegalArgumentException.class) - .verify(); - Optional observationContext = ServerHttpObservationFilter.findObservationContext(exchange); - assertThat(observationContext.get().getError()).isInstanceOf(IllegalArgumentException.class); - assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SERVER_ERROR"); - } - - @Test - void filterShouldRecordObservationWhenCancelled() { - ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/test/resource")); - exchange.getResponse().setRawStatusCode(200); - WebFilterChain filterChain = createFilterChain(filterExchange -> { - }); - StepVerifier.create(this.filter.filter(exchange, filterChain)) - .thenCancel() - .verify(); - assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "UNKNOWN"); - } - - @Test - void filterShouldStopObservationOnResponseCommit() { - ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/test/resource")); - WebFilterChain filterChain = createFilterChain(filterExchange -> { - throw new IllegalArgumentException("server error"); - }); - StepVerifier.create(this.filter.filter(exchange, filterChain).doOnError(throwable -> { - ServerHttpResponse response = exchange.getResponse(); - response.setRawStatusCode(500); - response.setComplete().block(); - })) - .expectError(IllegalArgumentException.class) - .verify(); - Optional observationContext = ServerHttpObservationFilter.findObservationContext(exchange); - assertThat(observationContext.get().getError()).isInstanceOf(IllegalArgumentException.class); - assertThatHttpObservation().hasLowCardinalityKeyValue("outcome", "SERVER_ERROR"); - } - - - private WebFilterChain createFilterChain(ThrowingConsumer exchangeConsumer) { - return filterExchange -> { - try { - exchangeConsumer.accept(filterExchange); - } - catch (Throwable ex) { - return Mono.error(ex); - } - return Mono.empty(); - }; - } - - private TestObservationRegistryAssert.TestObservationRegistryAssertReturningObservationContextAssert assertThatHttpObservation() { - return assertThat(this.observationRegistry).hasObservationWithNameEqualTo("http.server.requests").that(); - } -} diff --git a/spring-webflux/spring-webflux.gradle b/spring-webflux/spring-webflux.gradle index e05cd2b9461f..1ea20efabc81 100644 --- a/spring-webflux/spring-webflux.gradle +++ b/spring-webflux/spring-webflux.gradle @@ -33,7 +33,6 @@ dependencies { optional("org.jetbrains.kotlin:kotlin-reflect") optional("org.jetbrains.kotlin:kotlin-stdlib") optional("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") - optional("org.webjars:webjars-locator-core") optional("org.webjars:webjars-locator-lite") testImplementation(testFixtures(project(":spring-beans"))) testImplementation(testFixtures(project(":spring-core"))) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/ResourceChainRegistration.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/ResourceChainRegistration.java index 376daf987aa7..fe7318feb023 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/ResourceChainRegistration.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/ResourceChainRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-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. @@ -32,7 +32,6 @@ import org.springframework.web.reactive.resource.ResourceResolver; import org.springframework.web.reactive.resource.ResourceTransformer; import org.springframework.web.reactive.resource.VersionResourceResolver; -import org.springframework.web.reactive.resource.WebJarsResourceResolver; /** * Assists with the registration of resource resolvers and transformers. @@ -44,10 +43,7 @@ public class ResourceChainRegistration { private static final String DEFAULT_CACHE_NAME = "spring-resource-chain-cache"; - private static final boolean isWebJarAssetLocatorPresent = ClassUtils.isPresent( - "org.webjars.WebJarAssetLocator", ResourceChainRegistration.class.getClassLoader()); - - private static final boolean isWebJarVersionLocatorPresent = ClassUtils.isPresent( + private static final boolean webJarsPresent = ClassUtils.isPresent( "org.webjars.WebJarVersionLocator", ResourceChainRegistration.class.getClassLoader()); @@ -93,7 +89,7 @@ public ResourceChainRegistration addResolver(ResourceResolver resolver) { else if (resolver instanceof PathResourceResolver) { this.hasPathResolver = true; } - else if (resolver instanceof WebJarsResourceResolver || resolver instanceof LiteWebJarsResourceResolver) { + else if (resolver instanceof LiteWebJarsResourceResolver) { this.hasWebjarsResolver = true; } return this; @@ -113,16 +109,12 @@ public ResourceChainRegistration addTransformer(ResourceTransformer transformer) return this; } - @SuppressWarnings("removal") protected List getResourceResolvers() { if (!this.hasPathResolver) { List result = new ArrayList<>(this.resolvers); - if (isWebJarVersionLocatorPresent && !this.hasWebjarsResolver) { + if (webJarsPresent && !this.hasWebjarsResolver) { result.add(new LiteWebJarsResourceResolver()); } - else if (isWebJarAssetLocatorPresent && !this.hasWebjarsResolver) { - result.add(new WebJarsResourceResolver()); - } result.add(new PathResourceResolver()); return result; } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientRequestObservationContext.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientRequestObservationContext.java index deba7061c092..434ca9f140bc 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientRequestObservationContext.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ClientRequestObservationContext.java @@ -42,15 +42,6 @@ public class ClientRequestObservationContext extends RequestReplySenderContext getHandlerInternal(ServerWebExchange exchange) { } } - @SuppressWarnings({"unchecked", "removal"}) + @SuppressWarnings("unchecked") private void setAttributes( Map attributes, ServerRequest serverRequest, HandlerFunction handlerFunction) { @@ -171,9 +171,6 @@ private void setAttributes( PathPattern matchingPattern = (PathPattern) attributes.get(RouterFunctions.MATCHING_PATTERN_ATTRIBUTE); if (matchingPattern != null) { attributes.put(BEST_MATCHING_PATTERN_ATTRIBUTE, matchingPattern); - org.springframework.web.filter.reactive.ServerHttpObservationFilter - .findObservationContext(serverRequest.exchange()) - .ifPresent(context -> context.setPathPattern(matchingPattern.toString())); ServerRequestObservationContext.findCurrent(serverRequest.exchange().getAttributes()) .ifPresent(context -> context.setPathPattern(matchingPattern.toString())); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java index 610ad2853177..01263e6050e6 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractUrlHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-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. @@ -167,9 +167,6 @@ protected Object lookupHandler(PathContainer lookupPath, ServerWebExchange excha exchange.getAttributes().put(BEST_MATCHING_HANDLER_ATTRIBUTE, handler); exchange.getAttributes().put(BEST_MATCHING_PATTERN_ATTRIBUTE, pattern); - org.springframework.web.filter.reactive.ServerHttpObservationFilter - .findObservationContext(exchange) - .ifPresent(context -> context.setPathPattern(pattern.toString())); ServerRequestObservationContext.findCurrent(exchange.getAttributes()) .ifPresent(context -> context.setPathPattern(pattern.toString())); exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/WebJarsResourceResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/WebJarsResourceResolver.java deleted file mode 100644 index c851a379f7e1..000000000000 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/WebJarsResourceResolver.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2002-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.web.reactive.resource; - -import java.util.List; - -import org.webjars.WebJarAssetLocator; -import reactor.core.publisher.Mono; - -import org.springframework.core.io.Resource; -import org.springframework.lang.Nullable; -import org.springframework.web.server.ServerWebExchange; - -/** - * A {@code ResourceResolver} that delegates to the chain to locate a resource and then - * attempts to find a matching versioned resource contained in a WebJar JAR file. - * - *

    This allows WebJars.org users to write version agnostic paths in their templates, - * like {@code