From 764eedef78f59901d2878e185e439e88ea1823ce Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Tue, 16 Jul 2024 14:46:48 -0400 Subject: [PATCH 01/33] Start work on moving to Spring 6+ and Spring Boot 3+ --- .editorconfig | 4 + .../buildlogic/java-conventions.gradle.kts | 21 +-- docs/build.gradle.kts | 2 +- .../WebMvcDocsJavaConfigIntegrationTest.java | 3 +- .../WebMvcDocsXmlConfigIntegrationTest.java | 3 +- integration-tests/build.gradle.kts | 10 +- .../test/web/bind/CookieParameterBean.java | 3 +- .../web/bind/CookieParameterController.java | 3 +- .../test/web/bind/FormParameterBean.java | 4 +- .../web/bind/FormParameterController.java | 4 +- .../test/web/bind/HeaderParameterBean.java | 2 +- .../web/bind/HeaderParameterController.java | 2 +- .../test/web/bind/PathParameterBean.java | 2 +- .../web/bind/PathParameterController.java | 2 +- .../spring/test/web/bind/RequestBodyBean.java | 3 +- .../test/web/bind/RequestBodyController.java | 2 +- .../test/web/bind/RequestContextBean.java | 2 +- .../test/web/bind/RequestParameterBean.java | 4 +- .../web/bind/RequestParameterController.java | 4 +- .../test/web/bind/SessionParameterBean.java | 3 +- .../web/bind/SessionParameterController.java | 3 +- .../test/CookieParameterIntegrationTest.java | 3 +- .../DirectFieldAccessIntegrationTest.java | 3 +- libs.versions.toml | 16 ++- .../build.gradle.kts | 2 +- .../web/bind/annotation/CookieParameter.java | 2 +- .../web/bind/annotation/RequestContext.java | 4 +- .../web/bind/support/MapValueResolver.java | 20 +++ .../spring/web/bind/support/package-info.java | 4 + .../build.gradle.kts | 2 +- .../BeanParameterMethodArgumentResolver.java | 40 ++---- ...anParameterMethodArgumentResolverTest.java | 2 +- .../build.gradle.kts | 4 +- .../BeanParameterMethodArgumentResolver.java | 56 +++----- ...ookieParameterRequestPropertyResolver.java | 7 +- ...RequestContextRequestPropertyResolver.java | 8 +- ...stParameterMapRequestPropertyResolver.java | 6 +- ...questParameterRequestPropertyResolver.java | 3 +- ...anParameterMethodArgumentResolverTest.java | 124 ++---------------- .../servlet/mvc/bind/MockWebDataBinder.java | 75 +++++++++++ .../mvc/bind/MockWebDataBinderFactory.java | 55 ++++++++ ...eParameterRequestPropertyResolverTest.java | 4 +- ...rowingMockMultipartHttpServletRequest.java | 4 +- ...rameterMapRequestPropertyResolverTest.java | 4 +- ...mParameterRequestPropertyResolverTest.java | 2 +- ...estContextRequestPropertyResolverTest.java | 6 +- ...rameterMapRequestPropertyResolverTest.java | 2 +- ...tParameterRequestPropertyResolverTest.java | 2 +- 48 files changed, 272 insertions(+), 274 deletions(-) create mode 100644 spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/MapValueResolver.java create mode 100644 spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/package-info.java create mode 100644 spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinder.java create mode 100644 spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinderFactory.java diff --git a/.editorconfig b/.editorconfig index 31325f8..83142a7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,6 +9,10 @@ indent_size = 4 indent_style = space insert_final_newline = true +[*.java] +ij_java_class_count_to_use_import_on_demand = 999 +ij_java_names_count_to_use_import_on_demand = 999 + [*.{bat,cmd}] end_of_line = crlf diff --git a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts index fbc4ecd..c928c35 100644 --- a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts @@ -13,10 +13,6 @@ java { } } -tasks.named("compileJava").configure { - options.release.set(8) -} - testing { suites { named("test").configure { @@ -34,25 +30,20 @@ tasks.named("jar").configure { } } -val springVersion: String = versionCatalog.findVersion("spring").orElseThrow().toString() -val springBootVersion: String = versionCatalog.findVersion("springBoot").orElseThrow().toString() - val javadocLinks = arrayOf( - "https://docs.oracle.com/javase/8/docs/api/", - "https://docs.oracle.com/javaee/7/api/", - "https://docs.spring.io/spring-framework/docs/$springVersion/javadoc-api/", - "https://docs.spring.io/spring-boot/docs/$springBootVersion/api/" + "https://docs.oracle.com/en/java/javase/17/docs/api", + "https://jakarta.ee/specifications/platform/11/apidocs/", + "https://docs.spring.io/spring-framework/docs/current/javadoc-api/", + "https://docs.spring.io/spring-boot/api/java/" ) tasks.named("javadoc").configure { options { this as StandardJavadocDocletOptions - source = "8" + source = "17" links(*javadocLinks) addStringOption("Xdoclint:none", "-quiet") - if (java.toolchain.languageVersion.get().asInt() >= 9) { - addBooleanOption("html5", true) - } + addBooleanOption("html5", true) } } diff --git a/docs/build.gradle.kts b/docs/build.gradle.kts index 82b5cb0..15451ce 100644 --- a/docs/build.gradle.kts +++ b/docs/build.gradle.kts @@ -12,7 +12,7 @@ configurations { dependencies { implementation(project(":spring-webmvc-annotated-data-binder")) implementation(project(":spring-webflux-annotated-data-binder")) - implementation(libs.javaxServletApi) // Version defined in Spring BOM file + implementation(libs.jakartaServletApi) // Version defined in Spring BOM file add("asciidoctorExt", libs.springAsciidoctorExtBlockSwitch) diff --git a/docs/src/test/java/com/mattbertolini/spring/web/bind/docs/webmvc/WebMvcDocsJavaConfigIntegrationTest.java b/docs/src/test/java/com/mattbertolini/spring/web/bind/docs/webmvc/WebMvcDocsJavaConfigIntegrationTest.java index 3db99b9..df6f140 100644 --- a/docs/src/test/java/com/mattbertolini/spring/web/bind/docs/webmvc/WebMvcDocsJavaConfigIntegrationTest.java +++ b/docs/src/test/java/com/mattbertolini/spring/web/bind/docs/webmvc/WebMvcDocsJavaConfigIntegrationTest.java @@ -16,6 +16,7 @@ package com.mattbertolini.spring.web.bind.docs.webmvc; +import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; @@ -24,8 +25,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; -import javax.servlet.http.Cookie; - import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; diff --git a/docs/src/test/java/com/mattbertolini/spring/web/bind/docs/webmvc/WebMvcDocsXmlConfigIntegrationTest.java b/docs/src/test/java/com/mattbertolini/spring/web/bind/docs/webmvc/WebMvcDocsXmlConfigIntegrationTest.java index 8b85535..e1c91ca 100644 --- a/docs/src/test/java/com/mattbertolini/spring/web/bind/docs/webmvc/WebMvcDocsXmlConfigIntegrationTest.java +++ b/docs/src/test/java/com/mattbertolini/spring/web/bind/docs/webmvc/WebMvcDocsXmlConfigIntegrationTest.java @@ -16,6 +16,7 @@ package com.mattbertolini.spring.web.bind.docs.webmvc; +import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; @@ -24,8 +25,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; -import javax.servlet.http.Cookie; - import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; diff --git a/integration-tests/build.gradle.kts b/integration-tests/build.gradle.kts index 5ee51e9..faeea26 100644 --- a/integration-tests/build.gradle.kts +++ b/integration-tests/build.gradle.kts @@ -5,10 +5,12 @@ plugins { dependencies { implementation(project(":spring-webmvc-annotated-data-binder")) implementation(project(":spring-webflux-annotated-data-binder")) - implementation(libs.javaxServletApi) + implementation(libs.jakartaServletApi) implementation(libs.hibernateValidator) - implementation(libs.glassfishJavaxEl) // Needed by Hibernate Validator + implementation(libs.glassfishJakartaEl) // Needed by Hibernate Validator implementation(libs.jacksonDatabind) + implementation(libs.jakartaWebsocketApi) + implementation(libs.jakartaWebsocketClientApi) testImplementation(libs.junitJupiterApi) testImplementation(libs.assertJCore) @@ -16,10 +18,6 @@ dependencies { testCompileOnly(libs.hamcrest) // Needed for Spring mock MVC matchers } -tasks.named("compileJava").configure { - options.release.set(17) -} - tasks.named("jacocoTestReport").configure { reports { html.required.set(false) diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/CookieParameterBean.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/CookieParameterBean.java index 34c9f4f..16fb669 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/CookieParameterBean.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/CookieParameterBean.java @@ -18,8 +18,7 @@ import com.mattbertolini.spring.web.bind.annotation.BeanParameter; import com.mattbertolini.spring.web.bind.annotation.CookieParameter; - -import javax.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotEmpty; public class CookieParameterBean { @CookieParameter("annotated_field") diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/CookieParameterController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/CookieParameterController.java index 572997f..bc68286 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/CookieParameterController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/CookieParameterController.java @@ -18,13 +18,12 @@ import com.mattbertolini.spring.test.web.bind.records.CookieParameterRecord; import com.mattbertolini.spring.web.bind.annotation.BeanParameter; +import jakarta.validation.Valid; import org.springframework.http.MediaType; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -import javax.validation.Valid; - @RestController public class CookieParameterController { @GetMapping(value = "/annotatedField", produces = MediaType.TEXT_PLAIN_VALUE) diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterBean.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterBean.java index 686f479..9989e41 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterBean.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterBean.java @@ -18,12 +18,12 @@ import com.mattbertolini.spring.web.bind.annotation.BeanParameter; import com.mattbertolini.spring.web.bind.annotation.FormParameter; +import jakarta.servlet.http.Part; +import jakarta.validation.constraints.NotEmpty; import org.springframework.http.codec.multipart.FilePart; import org.springframework.util.MultiValueMap; import org.springframework.web.multipart.MultipartFile; -import javax.servlet.http.Part; -import javax.validation.constraints.NotEmpty; import java.util.Map; public class FormParameterBean { diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterController.java index da5d072..c4141fc 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterController.java @@ -18,6 +18,8 @@ import com.mattbertolini.spring.test.web.bind.records.FormParameterRecord; import com.mattbertolini.spring.web.bind.annotation.BeanParameter; +import jakarta.servlet.http.Part; +import jakarta.validation.Valid; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.MediaType; @@ -30,8 +32,6 @@ import org.springframework.web.multipart.MultipartFile; import reactor.core.publisher.Flux; -import javax.servlet.http.Part; -import javax.validation.Valid; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/HeaderParameterBean.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/HeaderParameterBean.java index 30aa8e0..e2fea6c 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/HeaderParameterBean.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/HeaderParameterBean.java @@ -18,10 +18,10 @@ import com.mattbertolini.spring.web.bind.annotation.BeanParameter; import com.mattbertolini.spring.web.bind.annotation.HeaderParameter; +import jakarta.validation.constraints.NotEmpty; import org.springframework.http.HttpHeaders; import org.springframework.util.MultiValueMap; -import javax.validation.constraints.NotEmpty; import java.util.Map; public class HeaderParameterBean { diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/HeaderParameterController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/HeaderParameterController.java index 4ce66a5..09f708b 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/HeaderParameterController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/HeaderParameterController.java @@ -18,6 +18,7 @@ import com.mattbertolini.spring.test.web.bind.records.HeaderParameterRecord; import com.mattbertolini.spring.web.bind.annotation.BeanParameter; +import jakarta.validation.Valid; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.util.MultiValueMap; @@ -25,7 +26,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -import javax.validation.Valid; import java.util.Map; @RestController diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterBean.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterBean.java index 8c33df5..d035c99 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterBean.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterBean.java @@ -18,8 +18,8 @@ import com.mattbertolini.spring.web.bind.annotation.BeanParameter; import com.mattbertolini.spring.web.bind.annotation.PathParameter; +import jakarta.validation.constraints.NotEmpty; -import javax.validation.constraints.NotEmpty; import java.util.Map; public class PathParameterBean { diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterController.java index 473bde8..0d4c3cb 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterController.java @@ -18,12 +18,12 @@ import com.mattbertolini.spring.test.web.bind.records.PathParameterRecord; import com.mattbertolini.spring.web.bind.annotation.BeanParameter; +import jakarta.validation.Valid; import org.springframework.http.MediaType; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -import javax.validation.Valid; import java.util.Map; @SuppressWarnings("MVCPathVariableInspection") diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestBodyBean.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestBodyBean.java index 9d59242..1cf0f01 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestBodyBean.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestBodyBean.java @@ -18,12 +18,11 @@ import com.mattbertolini.spring.web.bind.annotation.BeanParameter; import com.mattbertolini.spring.web.bind.annotation.RequestBody; +import jakarta.validation.constraints.NotNull; import org.springframework.http.codec.multipart.Part; import org.springframework.util.MultiValueMap; import reactor.core.publisher.Flux; -import javax.validation.constraints.NotNull; - public class RequestBodyBean { @SuppressWarnings("unused") diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestBodyController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestBodyController.java index 5b53c11..8c3dffc 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestBodyController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestBodyController.java @@ -18,6 +18,7 @@ import com.mattbertolini.spring.test.web.bind.records.RequestBodyRecord; import com.mattbertolini.spring.web.bind.annotation.BeanParameter; +import jakarta.validation.Valid; import org.springframework.http.MediaType; import org.springframework.http.codec.multipart.Part; import org.springframework.util.Assert; @@ -27,7 +28,6 @@ import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; -import javax.validation.Valid; import java.util.concurrent.atomic.AtomicInteger; @RestController diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestContextBean.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestContextBean.java index d23a638..b2c6fec 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestContextBean.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestContextBean.java @@ -17,12 +17,12 @@ package com.mattbertolini.spring.test.web.bind; import com.mattbertolini.spring.web.bind.annotation.RequestContext; +import jakarta.servlet.http.HttpSession; import org.springframework.http.HttpMethod; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebSession; -import javax.servlet.http.HttpSession; import java.time.ZoneId; import java.util.Locale; import java.util.TimeZone; diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterBean.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterBean.java index 73f239e..066908c 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterBean.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterBean.java @@ -18,11 +18,11 @@ import com.mattbertolini.spring.web.bind.annotation.BeanParameter; import com.mattbertolini.spring.web.bind.annotation.RequestParameter; +import jakarta.servlet.http.Part; +import jakarta.validation.constraints.NotEmpty; import org.springframework.util.MultiValueMap; import org.springframework.web.multipart.MultipartFile; -import javax.servlet.http.Part; -import javax.validation.constraints.NotEmpty; import java.util.Map; public class RequestParameterBean { diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterController.java index 7edb153..dc206be 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterController.java @@ -18,6 +18,8 @@ import com.mattbertolini.spring.test.web.bind.records.RequestParameterRecord; import com.mattbertolini.spring.web.bind.annotation.BeanParameter; +import jakarta.servlet.http.Part; +import jakarta.validation.Valid; import org.springframework.http.MediaType; import org.springframework.util.MultiValueMap; import org.springframework.util.StreamUtils; @@ -27,8 +29,6 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import javax.servlet.http.Part; -import javax.validation.Valid; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.List; diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/SessionParameterBean.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/SessionParameterBean.java index 01b7f1b..18c0ef6 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/SessionParameterBean.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/SessionParameterBean.java @@ -18,8 +18,7 @@ import com.mattbertolini.spring.web.bind.annotation.BeanParameter; import com.mattbertolini.spring.web.bind.annotation.SessionParameter; - -import javax.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotEmpty; public class SessionParameterBean { @SessionParameter("annotated_field") diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/SessionParameterController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/SessionParameterController.java index 14d57ff..858bddf 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/SessionParameterController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/SessionParameterController.java @@ -18,13 +18,12 @@ import com.mattbertolini.spring.test.web.bind.records.SessionParameterRecord; import com.mattbertolini.spring.web.bind.annotation.BeanParameter; +import jakarta.validation.Valid; import org.springframework.http.MediaType; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -import javax.validation.Valid; - @RestController public class SessionParameterController { @GetMapping(value = "/annotatedField", produces = MediaType.TEXT_PLAIN_VALUE) diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/web/servlet/mvc/test/CookieParameterIntegrationTest.java b/integration-tests/src/test/java/com/mattbertolini/spring/web/servlet/mvc/test/CookieParameterIntegrationTest.java index 17aac83..0ae09de 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/web/servlet/mvc/test/CookieParameterIntegrationTest.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/web/servlet/mvc/test/CookieParameterIntegrationTest.java @@ -18,6 +18,7 @@ import com.mattbertolini.spring.test.web.bind.CookieParameterController; import com.mattbertolini.spring.web.servlet.mvc.bind.config.BinderConfiguration; +import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Bean; @@ -30,8 +31,6 @@ import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import javax.servlet.http.Cookie; - import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/web/servlet/mvc/test/DirectFieldAccessIntegrationTest.java b/integration-tests/src/test/java/com/mattbertolini/spring/web/servlet/mvc/test/DirectFieldAccessIntegrationTest.java index 2af7b3c..7460fb6 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/web/servlet/mvc/test/DirectFieldAccessIntegrationTest.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/web/servlet/mvc/test/DirectFieldAccessIntegrationTest.java @@ -18,6 +18,7 @@ import com.mattbertolini.spring.test.web.bind.DirectFieldAccessController; import com.mattbertolini.spring.web.servlet.mvc.bind.config.BinderConfiguration; +import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Bean; @@ -34,8 +35,6 @@ import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; -import javax.servlet.http.Cookie; - import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; diff --git a/libs.versions.toml b/libs.versions.toml index e91f1b7..00540e2 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -1,12 +1,14 @@ [versions] -spring = "5.3.28" # Used in java-conventions.gradle.kts -springBoot = "2.7.13" # Used in java-conventions.gradle.kts +spring = "6.1.10" # Used in java-conventions.gradle.kts +springBoot = "3.3.1" # Used in java-conventions.gradle.kts junit = "5.9.3" # Used in java-conventions.gradle.kts jacoco = "0.8.10" # Used in java-conventions.gradle.kts [libraries] -javaxServletApi = { module = "javax.servlet:javax.servlet-api", version = "4.0.1" } -javaxValidationApi = { module = "javax.validation:validation-api", version = "2.0.1.Final" } +jakartaServletApi = { module = "jakarta.servlet:jakarta.servlet-api", version = "6.0.0" } +jakartaValidationApi = { module = "jakarta.validation:jakarta.validation-api", version = "3.1.0" } +jakartaWebsocketApi = { module = "jakarta.websocket:jakarta.websocket-api", version = "2.2.0" } +jakartaWebsocketClientApi = { module = "jakarta.websocket:jakarta.websocket-client-api", version = "2.2.0" } findbugsJsr305 = { module = "com.google.code.findbugs:jsr305", version = "3.0.2" } @@ -22,9 +24,9 @@ springBootTest = { module = "org.springframework.boot:spring-boot-test", version springAsciidoctorExtBlockSwitch = { module = "io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch", version = "0.6.1" } -glassfishJavaxEl = { module = "org.glassfish:javax.el", version = "3.0.1-b12" } # Needed by Hibernate Validator -hibernateValidator = { module = "org.hibernate.validator:hibernate-validator", version = "6.0.21.Final" } -jacksonDatabind = { module = "com.fasterxml.jackson.core:jackson-databind", version = "2.12.7.1" } +glassfishJakartaEl = { module = "org.glassfish:jakarta.el", version = "4.0.2" } # Needed by Hibernate Validator +hibernateValidator = { module = "org.hibernate.validator:hibernate-validator", version = "8.0.1.Final" } +jacksonDatabind = { module = "com.fasterxml.jackson.core:jackson-databind", version = "2.17.2" } junitJupiterApi = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } assertJCore = { module = "org.assertj:assertj-core", version = "3.24.2" } diff --git a/spring-annotated-data-binder-core/build.gradle.kts b/spring-annotated-data-binder-core/build.gradle.kts index 8a644b6..9c88fce 100644 --- a/spring-annotated-data-binder-core/build.gradle.kts +++ b/spring-annotated-data-binder-core/build.gradle.kts @@ -8,7 +8,7 @@ dependencies { api(libs.springBeans) api(libs.springWeb) compileOnly(libs.findbugsJsr305) // To Prevent warnings on missing enum constants - compileOnly(libs.javaxServletApi) // So Javadoc doesn't give warnings about missing links + compileOnly(libs.jakartaServletApi) // So Javadoc doesn't give warnings about missing links testImplementation(libs.junitJupiterApi) testImplementation(libs.assertJCore) diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/annotation/CookieParameter.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/annotation/CookieParameter.java index f140ac1..dc954c2 100644 --- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/annotation/CookieParameter.java +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/annotation/CookieParameter.java @@ -40,7 +40,7 @@ *

* *

If you need access to all attributes of a cookie (e.g. expiration date, domain, etc) you can bind to cookie - * objects. In Spring MVC you and bind directly to a {@link javax.servlet.http.Cookie}: + * objects. In Spring MVC you and bind directly to a {@link jakarta.servlet.http.Cookie}: *

{@code
  *     @CookieParameter("cookie_name")
  *     private Cookie cookie;
diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/annotation/RequestContext.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/annotation/RequestContext.java
index c77a55e..d1f350b 100644
--- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/annotation/RequestContext.java
+++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/annotation/RequestContext.java
@@ -33,8 +33,8 @@
  * In Spring MVC the following additional objects are available:
  * 
    *
  • {@link org.springframework.web.context.request.WebRequest} - The Spring WebRequest.
  • - *
  • {@link javax.servlet.http.HttpServletRequest} - The underlying Servlet request.
  • - *
  • {@link javax.servlet.http.HttpSession} - The Servlet session object.
  • + *
  • {@link jakarta.servlet.http.HttpServletRequest} - The underlying Servlet request.
  • + *
  • {@link jakarta.servlet.http.HttpSession} - The Servlet session object.
  • *
* In Spring WebFlux the following additional objects are available: *
    diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/MapValueResolver.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/MapValueResolver.java new file mode 100644 index 0000000..30fb14b --- /dev/null +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/MapValueResolver.java @@ -0,0 +1,20 @@ +package com.mattbertolini.spring.web.bind.support; + +import org.springframework.lang.Nullable; +import org.springframework.validation.DataBinder; + +import java.util.Map; +import java.util.Set; + +public record MapValueResolver(Map values) implements DataBinder.ValueResolver { + @Override + @Nullable + public Object resolveValue(String name, Class type) { + return values.get(name); + } + + @Override + public Set getNames() { + return Set.copyOf(values.keySet()); + } +} diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/package-info.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/package-info.java new file mode 100644 index 0000000..7debe91 --- /dev/null +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/package-info.java @@ -0,0 +1,4 @@ +@NonNullApi +package com.mattbertolini.spring.web.bind.support; + +import org.springframework.lang.NonNullApi; diff --git a/spring-webflux-annotated-data-binder/build.gradle.kts b/spring-webflux-annotated-data-binder/build.gradle.kts index d8a8c28..8848608 100644 --- a/spring-webflux-annotated-data-binder/build.gradle.kts +++ b/spring-webflux-annotated-data-binder/build.gradle.kts @@ -12,7 +12,7 @@ dependencies { testImplementation(libs.assertJCore) testImplementation(libs.mockitoCore) testImplementation(libs.springTest) - testImplementation(libs.javaxValidationApi) // Used to test validation annotations + testImplementation(libs.jakartaValidationApi) // Used to test validation annotations testCompileOnly(libs.findbugsJsr305) // To Prevent warnings on missing enum constants } diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java index 9a7cbea..73597b5 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 the original author or authors. + * Copyright 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. @@ -21,17 +21,15 @@ import com.mattbertolini.spring.web.bind.introspect.AnnotatedRequestBeanIntrospector; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import com.mattbertolini.spring.web.bind.introspect.ResolvedPropertyData; +import com.mattbertolini.spring.web.bind.support.MapValueResolver; import com.mattbertolini.spring.web.reactive.bind.resolver.RequestPropertyResolver; import org.springframework.beans.BeanUtils; import org.springframework.beans.MutablePropertyValues; import org.springframework.core.MethodParameter; -import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; -import org.springframework.core.ResolvableType; import org.springframework.lang.NonNull; import org.springframework.util.Assert; import org.springframework.web.bind.support.WebExchangeDataBinder; -import org.springframework.web.reactive.BindingContext; import org.springframework.web.reactive.result.method.annotation.ModelAttributeMethodArgumentResolver; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; @@ -44,9 +42,6 @@ import java.util.Objects; public class BeanParameterMethodArgumentResolver extends ModelAttributeMethodArgumentResolver { - private static final String INTROSPECTOR_RESOLVABLE_TYPE = BeanParameterMethodArgumentResolver.class.getName() - + ".INTROSPECTOR_RESOLVABLE_TYPE"; - private final AnnotatedRequestBeanIntrospector introspector; public BeanParameterMethodArgumentResolver( @@ -63,17 +58,13 @@ public boolean supportsParameter(@NonNull MethodParameter parameter) { @Override @NonNull - public Mono resolveArgument(@NonNull MethodParameter parameter, @NonNull BindingContext context, ServerWebExchange exchange) { - try { - ResolvableType type = ResolvableType.forMethodParameter(parameter); - Class resolvedType = type.resolve(); - ReactiveAdapter adapter = (resolvedType != null ? getAdapterRegistry().getAdapter(resolvedType) : null); - ResolvableType valueType = (adapter != null ? type.getGeneric() : type); - exchange.getAttributes().put(INTROSPECTOR_RESOLVABLE_TYPE, valueType); - return super.resolveArgument(parameter, context, exchange); - } finally { - exchange.getAttributes().remove(INTROSPECTOR_RESOLVABLE_TYPE); - } + protected Mono constructAttribute(WebExchangeDataBinder binder, @NonNull ServerWebExchange exchange) { + Assert.state(binder.getTargetType() != null, "WebExchangeDataBinder must have a target type"); + Collection propertyData = introspector.getResolversFor(Objects.requireNonNull(binder.getTargetType().getRawClass())); + return getValuesToBind(propertyData, exchange) + .map(MapValueResolver::new) + .doOnNext(binder::construct) + .then(); } @Override @@ -87,19 +78,6 @@ protected Mono bindRequestParameters(@NonNull WebExchangeDataBinder binder .then(); } - @Override - @NonNull - public Mono> getValuesToBind(@NonNull WebExchangeDataBinder binder, ServerWebExchange exchange) { - ResolvableType resolvableType = exchange.getAttribute(INTROSPECTOR_RESOLVABLE_TYPE); - if (resolvableType == null) { - return super.getValuesToBind(binder, exchange); - } - Class type = resolvableType.resolve(); - Assert.notNull(type, "The resolved type must not be null"); - Collection propertyData = introspector.getResolversFor(type); - return getValuesToBind(propertyData, exchange); - } - @NonNull private Mono> getValuesToBind(@NonNull Collection propertyData, @NonNull ServerWebExchange exchange) { return Flux.fromIterable(propertyData).flatMap(data -> { diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java index 6753a09..60bd2c8 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java @@ -22,6 +22,7 @@ import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import com.mattbertolini.spring.web.bind.introspect.ResolvedPropertyData; import com.mattbertolini.spring.web.reactive.bind.resolver.RequestPropertyResolver; +import jakarta.validation.Valid; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.PropertyValue; @@ -42,7 +43,6 @@ import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -import javax.validation.Valid; import java.beans.PropertyDescriptor; import java.util.Arrays; import java.util.List; diff --git a/spring-webmvc-annotated-data-binder/build.gradle.kts b/spring-webmvc-annotated-data-binder/build.gradle.kts index b837cd8..b565033 100644 --- a/spring-webmvc-annotated-data-binder/build.gradle.kts +++ b/spring-webmvc-annotated-data-binder/build.gradle.kts @@ -6,14 +6,14 @@ plugins { dependencies { api(project(":spring-annotated-data-binder-core")) api(libs.springWebmvc) - implementation(libs.javaxServletApi) + implementation(libs.jakartaServletApi) compileOnly(libs.findbugsJsr305) // To Prevent warnings on missing enum constants testImplementation(libs.junitJupiterApi) testImplementation(libs.assertJCore) testImplementation(libs.mockitoCore) testImplementation(libs.springTest) - testImplementation(libs.javaxValidationApi) // Used to test validation annotations + testImplementation(libs.jakartaValidationApi) // Used to test validation annotations testCompileOnly(libs.findbugsJsr305) // To Prevent warnings on missing enum constants } diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java index 337c95d..071208f 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 the original author or authors. + * Copyright 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,26 +20,23 @@ import com.mattbertolini.spring.web.bind.annotation.BeanParameter; import com.mattbertolini.spring.web.bind.introspect.AnnotatedRequestBeanIntrospector; import com.mattbertolini.spring.web.bind.introspect.ResolvedPropertyData; +import com.mattbertolini.spring.web.bind.support.MapValueResolver; import com.mattbertolini.spring.web.servlet.mvc.bind.resolver.RequestPropertyResolver; import org.springframework.beans.BeanUtils; import org.springframework.beans.MutablePropertyValues; -import org.springframework.beans.PropertyValues; import org.springframework.core.MethodParameter; import org.springframework.lang.NonNull; import org.springframework.util.Assert; import org.springframework.web.bind.WebDataBinder; -import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.method.annotation.ModelAttributeMethodProcessor; import java.util.Collection; +import java.util.HashMap; import java.util.Map; +import java.util.Objects; public class BeanParameterMethodArgumentResolver extends ModelAttributeMethodProcessor { - private static final String INTROSPECTOR_TARGET_CLASS = BeanParameterMethodArgumentResolver.class + - ".INTROSPECTOR_TARGET_CLASS"; - private final AnnotatedRequestBeanIntrospector introspector; public BeanParameterMethodArgumentResolver(@NonNull AnnotatedRequestBeanIntrospector introspector) { @@ -57,16 +54,23 @@ public boolean supportsReturnType(@NonNull MethodParameter returnType) { return false; } + @Override + protected void constructAttribute(WebDataBinder binder, @NonNull NativeWebRequest request) { + Assert.state(binder.getTargetType() != null, "WebDataBinder must have a target type"); + Map valuesToBind = getValuesToBind(Objects.requireNonNull(binder.getTargetType().getRawClass()), request); + binder.construct(new MapValueResolver(valuesToBind)); + } + @Override protected void bindRequestParameters(@NonNull WebDataBinder binder, @NonNull NativeWebRequest request) { Assert.state(binder.getTarget() != null, "WebDataBinder must have a target object"); - PropertyValues propertyValues = makePropertyValues(binder.getTarget().getClass(), request); - binder.bind(propertyValues); + Map valuesToBind = getValuesToBind(binder.getTarget().getClass(), request); + binder.bind(new MutablePropertyValues(valuesToBind)); } @NonNull - private PropertyValues makePropertyValues(@NonNull Class targetType, @NonNull NativeWebRequest request) { - MutablePropertyValues propertyValues = new MutablePropertyValues(); + private Map getValuesToBind(@NonNull Class targetType, @NonNull NativeWebRequest request) { + Map values = new HashMap<>(); Collection propertyData = introspector.getResolversFor(targetType); for (ResolvedPropertyData data : propertyData) { RequestPropertyResolver resolver = (RequestPropertyResolver) data.getResolver(); @@ -74,38 +78,12 @@ private PropertyValues makePropertyValues(@NonNull Class targetType, @NonNull Object value = resolver.resolve(data.getBindingProperty(), request); if (value != null) { String propertyName = data.getPropertyName(); - propertyValues.add(propertyName, value); + values.put(propertyName, value); } } catch (Exception e) { throw new RequestPropertyBindingException("Unable to resolve property. " + e.getMessage(), e); } } - return propertyValues; - } - - @Override - @NonNull - protected Object createAttribute(@NonNull String attributeName, MethodParameter parameter, @NonNull WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception { - try { - MethodParameter nestedParameter = parameter.nestedIfOptional(); - Class clazz = nestedParameter.getNestedParameterType(); - webRequest.setAttribute(INTROSPECTOR_TARGET_CLASS, clazz, RequestAttributes.SCOPE_REQUEST); - return super.createAttribute(attributeName, parameter, binderFactory, webRequest); - } finally { - webRequest.removeAttribute(INTROSPECTOR_TARGET_CLASS, RequestAttributes.SCOPE_REQUEST); - } - } - - @Override - public Object resolveConstructorArgument(@NonNull String paramName,@NonNull Class paramType, NativeWebRequest request) throws Exception { - Class clazz = (Class) request.getAttribute(INTROSPECTOR_TARGET_CLASS, RequestAttributes.SCOPE_REQUEST); - if (clazz == null) { - return super.resolveConstructorArgument(paramName, paramType, request); - } - - final Map resolvers = introspector.getResolverMapFor(clazz); - final ResolvedPropertyData resolvedPropertyData = resolvers.get(paramName); - final RequestPropertyResolver resolver = (RequestPropertyResolver) resolvedPropertyData.getResolver(); - return resolver.resolve(resolvedPropertyData.getBindingProperty(), request); + return values; } } diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolver.java index cab8d4d..4e10e36 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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,14 +18,13 @@ import com.mattbertolini.spring.web.bind.annotation.CookieParameter; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.lang.NonNull; import org.springframework.util.Assert; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.util.WebUtils; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; - public class CookieParameterRequestPropertyResolver implements RequestPropertyResolver { @Override public boolean supports(@NonNull BindingProperty bindingProperty) { diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestContextRequestPropertyResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestContextRequestPropertyResolver.java index dab9a90..a45064a 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestContextRequestPropertyResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestContextRequestPropertyResolver.java @@ -18,15 +18,15 @@ import com.mattbertolini.spring.web.bind.annotation.RequestContext; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; import org.springframework.http.HttpMethod; import org.springframework.lang.NonNull; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.support.RequestContextUtils; -import javax.servlet.ServletRequest; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; import java.time.ZoneId; import java.util.Locale; import java.util.TimeZone; @@ -64,7 +64,7 @@ public Object resolve(@NonNull BindingProperty bindingProperty, @NonNull NativeW // Not creating a session here. return servletRequest.getSession(false); } else if (HttpMethod.class.isAssignableFrom(type)) { - return HttpMethod.resolve(servletRequest.getMethod()); + return HttpMethod.valueOf(servletRequest.getMethod()); } else if (Locale.class.isAssignableFrom(type)) { return RequestContextUtils.getLocale(servletRequest); } else if(TimeZone.class.isAssignableFrom(type)) { diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolver.java index 345cf29..d30a651 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolver.java @@ -19,6 +19,9 @@ import com.mattbertolini.spring.web.bind.PropertyResolutionException; import com.mattbertolini.spring.web.bind.annotation.RequestParameter; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.Part; import org.springframework.core.ResolvableType; import org.springframework.lang.NonNull; import org.springframework.util.LinkedMultiValueMap; @@ -29,9 +32,6 @@ import org.springframework.web.multipart.MultipartRequest; import org.springframework.web.multipart.support.MultipartResolutionDelegate; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.Part; import java.io.IOException; import java.util.Arrays; import java.util.Collection; diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolver.java index 868efb3..b6de384 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolver.java @@ -20,14 +20,13 @@ import com.mattbertolini.spring.web.bind.annotation.RequestParameter; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import com.mattbertolini.spring.web.bind.resolver.AbstractNamedRequestPropertyResolver; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.lang.NonNull; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.multipart.support.MultipartResolutionDelegate; -import javax.servlet.http.HttpServletRequest; - public class RequestParameterRequestPropertyResolver extends AbstractNamedRequestPropertyResolver implements RequestPropertyResolver { diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolverTest.java index 19c7e53..d3b8d13 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolverTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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,25 +22,22 @@ import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import com.mattbertolini.spring.web.bind.introspect.ResolvedPropertyData; import com.mattbertolini.spring.web.servlet.mvc.bind.resolver.RequestPropertyResolver; +import jakarta.validation.Valid; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.PropertyValue; import org.springframework.beans.PropertyValues; import org.springframework.core.MethodParameter; -import org.springframework.format.support.FormattingConversionServiceFactoryBean; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.WebDataBinder; -import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.method.support.ModelAndViewContainer; -import javax.validation.Valid; import java.beans.PropertyDescriptor; import java.util.Arrays; import java.util.List; @@ -49,9 +46,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.catchThrowableOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -62,7 +56,7 @@ class BeanParameterMethodArgumentResolverTest { private ServletWebRequest request; private ModelAndViewContainer mavContainer; private AnnotatedRequestBeanIntrospector introspector; - private WebDataBinderFactory webDataBinderFactory; + private MockWebDataBinderFactory webDataBinderFactory; @BeforeEach void setUp() { @@ -70,7 +64,7 @@ void setUp() { request = new ServletWebRequest(servletRequest); mavContainer = new ModelAndViewContainer(); introspector = mock(AnnotatedRequestBeanIntrospector.class); - webDataBinderFactory = mock(WebDataBinderFactory.class); + webDataBinderFactory = new MockWebDataBinderFactory(); resolver = new BeanParameterMethodArgumentResolver(introspector); } @@ -120,8 +114,6 @@ void supportsParameterReturnsFalseWhenAnnotationIsMissing() throws Exception { @Test void resolveArgumentReturnsTheTargetObject() throws Exception { MethodParameter methodParameter = createMethodParameter("anAnnotatedMethod", ABeanClass.class); - ABeanClass target = new ABeanClass(); - when(webDataBinderFactory.createBinder(eq(request), any(ABeanClass.class), anyString())).thenReturn(new StubWebDataBinder(target)); Object actual = resolver.resolveArgument(methodParameter, mavContainer, request, webDataBinderFactory); assertThat(actual).isNotNull(); assertThat(actual.getClass()).isEqualTo(ABeanClass.class); @@ -130,8 +122,6 @@ void resolveArgumentReturnsTheTargetObject() throws Exception { @Test void resolveArgumentUnwrapTheTargetObjectFromOptional() throws Exception { MethodParameter methodParameter = createMethodParameter("optionalTypeAnnotated", Optional.class); - Object target = new ABeanClass(); - when(webDataBinderFactory.createBinder(eq(request), any(Optional.class), anyString())).thenReturn(new StubWebDataBinder(target)); Object actual = resolver.resolveArgument(methodParameter, mavContainer, request, webDataBinderFactory); assertThat(actual).isNotNull(); assertThat(actual.getClass()).isEqualTo(Optional.class); @@ -143,8 +133,8 @@ void resolveArgumentUnwrapTheTargetObjectFromOptional() throws Exception { @Test void resolveArgumentCallsBinderBind() throws Exception { MethodParameter methodParameter = createMethodParameter("anAnnotatedMethod", ABeanClass.class); - StubWebDataBinder dataBinder = new StubWebDataBinder(new ABeanClass()); - resolver.resolveArgument(methodParameter, mavContainer, request, (webRequest, target, objectName) -> dataBinder); + resolver.resolveArgument(methodParameter, mavContainer, request, webDataBinderFactory); + MockWebDataBinder dataBinder = webDataBinderFactory.getBinder(); assertThat(dataBinder.isBindInvoked()).isTrue(); } @@ -157,13 +147,10 @@ void resolvesPropertyValues() throws Exception { MethodParameter methodParameter = createMethodParameter("anAnnotatedMethod", ABeanClass.class); - StubWebDataBinder dataBinder = new StubWebDataBinder(new ABeanClass()); - when(webDataBinderFactory.createBinder(eq(request), any(ABeanClass.class), anyString())).thenReturn(dataBinder); - when(introspector.getResolversFor(ABeanClass.class)).thenReturn(propertyData); resolver.resolveArgument(methodParameter, mavContainer, request, webDataBinderFactory); - PropertyValues propertyValues = dataBinder.getPropertyValues(); + PropertyValues propertyValues = webDataBinderFactory.getBinder().getPropertyValues(); assertThat(propertyValues.contains("propertyOne")).isTrue(); assertThat(propertyValues.contains("propertyTwo")).isTrue(); @@ -184,13 +171,10 @@ void resolvesOnlyFoundPropertyValues() throws Exception { MethodParameter methodParameter = createMethodParameter("anAnnotatedMethod", ABeanClass.class); - StubWebDataBinder dataBinder = new StubWebDataBinder(new ABeanClass()); - when(webDataBinderFactory.createBinder(eq(request), any(ABeanClass.class), anyString())).thenReturn(dataBinder); - when(introspector.getResolversFor(ABeanClass.class)).thenReturn(propertyData); resolver.resolveArgument(methodParameter, mavContainer, request, webDataBinderFactory); - PropertyValues propertyValues = dataBinder.getPropertyValues(); + PropertyValues propertyValues = webDataBinderFactory.getBinder().getPropertyValues(); assertThat(propertyValues.contains("propertyOne")).isFalse(); assertThat(propertyValues.contains("propertyTwo")).isTrue(); @@ -208,7 +192,6 @@ void throwsExceptionWhenResolverErrors() throws Exception { MethodParameter methodParameter = createMethodParameter("anAnnotatedMethod", ABeanClass.class); - when(webDataBinderFactory.createBinder(eq(request), any(ABeanClass.class), anyString())).thenReturn(new StubWebDataBinder(new ABeanClass())); when(introspector.getResolversFor(ABeanClass.class)).thenReturn(propertyData); assertThatThrownBy(() -> resolver.resolveArgument(methodParameter, mavContainer, request, webDataBinderFactory)) .isInstanceOf(RequestPropertyBindingException.class); @@ -217,36 +200,23 @@ void throwsExceptionWhenResolverErrors() throws Exception { @Test void validatesWhenValidAnnotationPresent() throws Exception { MethodParameter methodParameter = createMethodParameter("aValidMethod", ABeanClass.class); - - StubWebDataBinder dataBinder = new StubWebDataBinder(new ABeanClass()); - when(webDataBinderFactory.createBinder(eq(request), any(ABeanClass.class), anyString())).thenReturn(dataBinder); - resolver.resolveArgument(methodParameter, mavContainer, request, webDataBinderFactory); - - assertThat(dataBinder.isValidateInvoked()).isTrue(); + assertThat(webDataBinderFactory.getBinder().isValidateInvoked()).isTrue(); } @Test void validatesWhenValidatedAnnotationPresent() throws Exception { MethodParameter methodParameter = createMethodParameter("aValidatedMethod", ABeanClass.class); - - StubWebDataBinder dataBinder = new StubWebDataBinder(new ABeanClass()); - when(webDataBinderFactory.createBinder(eq(request), any(ABeanClass.class), anyString())).thenReturn(dataBinder); - resolver.resolveArgument(methodParameter, mavContainer, request, webDataBinderFactory); - - assertThat(dataBinder.isValidateInvoked()).isTrue(); + assertThat(webDataBinderFactory.getBinder().isValidateInvoked()).isTrue(); } @Test void validatesWithGroups() throws Exception { MethodParameter methodParameter = createMethodParameter("validationGroups", ABeanClass.class); - StubWebDataBinder dataBinder = new StubWebDataBinder(new ABeanClass()); - when(webDataBinderFactory.createBinder(eq(request), any(ABeanClass.class), anyString())).thenReturn(dataBinder); - resolver.resolveArgument(methodParameter, mavContainer, request, webDataBinderFactory); - + MockWebDataBinder dataBinder = webDataBinderFactory.getBinder(); assertThat(dataBinder.isValidateInvoked()).isTrue(); assertThat(dataBinder.getValidationHints()).contains(ValidationGroupOne.class, ValidationGroupTwo.class); } @@ -258,9 +228,7 @@ void validationThrowsBindException() throws Exception { BindingResult bindingResult = mock(BindingResult.class); when(bindingResult.hasErrors()).thenReturn(true); - StubWebDataBinder dataBinder = new StubWebDataBinder(new ABeanClass()); - dataBinder.setBindingResult(bindingResult); - when(webDataBinderFactory.createBinder(eq(request), any(ABeanClass.class), anyString())).thenReturn(dataBinder); + webDataBinderFactory.setBindingResult(bindingResult); BindException exception = catchThrowableOfType(() -> resolver.resolveArgument(methodParameter, mavContainer, request, webDataBinderFactory), BindException.class); assertThat(exception.getBindingResult()).isEqualTo(bindingResult); @@ -273,79 +241,15 @@ void validationHasErrorsWithBindingResultMethodParameter() throws Exception { BindingResult bindingResult = mock(BindingResult.class); when(bindingResult.hasErrors()).thenReturn(true); - StubWebDataBinder dataBinder = new StubWebDataBinder(new ABeanClass()); - dataBinder.setBindingResult(bindingResult); - when(webDataBinderFactory.createBinder(eq(request), any(ABeanClass.class), anyString())).thenReturn(dataBinder); - + webDataBinderFactory.setBindingResult(bindingResult); resolver.resolveArgument(methodParameter, mavContainer, request, webDataBinderFactory); - - assertThat(dataBinder.getBindingResult()).isEqualTo(bindingResult); + assertThat(webDataBinderFactory.getBinder().getBindingResult()).isEqualTo(bindingResult); } private MethodParameter createMethodParameter(String anAnnotatedMethod, Class... parameterTypes) throws NoSuchMethodException { return new MethodParameter(FakeHandlerMethod.class.getMethod(anAnnotatedMethod, parameterTypes), 0); } - private static class StubWebDataBinder extends WebDataBinder { - private boolean bindInvoked = false; - private boolean validateInvoked = true; - private PropertyValues pvs; - private List validationHints; - private BindingResult bindingResult; - - public StubWebDataBinder(Object target) { - super(target); - FormattingConversionServiceFactoryBean conversionServiceFactoryBean = new FormattingConversionServiceFactoryBean(); - conversionServiceFactoryBean.afterPropertiesSet(); - setConversionService(conversionServiceFactoryBean.getObject()); - } - - @Override - public void bind(PropertyValues pvs) { - this.pvs = pvs; - bindInvoked = true; - } - - @Override - public void validate() { - validateInvoked = true; - } - - @Override - public void validate(Object... validationHints) { - this.validationHints = Arrays.asList(validationHints); - validateInvoked = true; - } - - @Override - public BindingResult getBindingResult() { - if (bindingResult == null) { - return super.getBindingResult(); - } - return bindingResult; - } - - public void setBindingResult(BindingResult bindingResult) { - this.bindingResult = bindingResult; - } - - public boolean isBindInvoked() { - return bindInvoked; - } - - public boolean isValidateInvoked() { - return validateInvoked; - } - - public List getValidationHints() { - return validationHints; - } - - public PropertyValues getPropertyValues() { - return pvs; - } - } - private static class MockRequestPropertyResolver implements RequestPropertyResolver { private final Object value; private final RuntimeException exception; diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinder.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinder.java new file mode 100644 index 0000000..3217523 --- /dev/null +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinder.java @@ -0,0 +1,75 @@ +package com.mattbertolini.spring.web.servlet.mvc.bind; + +import org.springframework.beans.PropertyValues; +import org.springframework.lang.Nullable; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.WebDataBinder; + +import java.util.Arrays; +import java.util.List; + +public class MockWebDataBinder extends WebDataBinder { + private boolean bindInvoked = false; + private boolean validateInvoked = true; + private PropertyValues pvs; + private List validationHints; + private BindingResult bindingResult; + + public MockWebDataBinder(@Nullable Object target) { + super(target); + } + + public MockWebDataBinder(@Nullable Object target, String objectName) { + super(target, objectName); + } + + @Override + public void construct(ValueResolver valueResolver) { + super.construct(valueResolver); // TODO + } + + @Override + public void bind(PropertyValues pvs) { + this.pvs = pvs; + bindInvoked = true; + } + + @Override + public void validate() { + validateInvoked = true; + } + + @Override + public void validate(Object... validationHints) { + this.validationHints = Arrays.asList(validationHints); + validateInvoked = true; + } + + @Override + public BindingResult getBindingResult() { + if (bindingResult == null) { + return super.getBindingResult(); + } + return bindingResult; + } + + public void setBindingResult(BindingResult bindingResult) { + this.bindingResult = bindingResult; + } + + public boolean isBindInvoked() { + return bindInvoked; + } + + public boolean isValidateInvoked() { + return validateInvoked; + } + + public List getValidationHints() { + return validationHints; + } + + public PropertyValues getPropertyValues() { + return pvs; + } +} diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinderFactory.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinderFactory.java new file mode 100644 index 0000000..eee8741 --- /dev/null +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinderFactory.java @@ -0,0 +1,55 @@ +package com.mattbertolini.spring.web.servlet.mvc.bind; + +import org.springframework.core.ResolvableType; +import org.springframework.format.support.FormattingConversionServiceFactoryBean; +import org.springframework.lang.Nullable; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; + +public class MockWebDataBinderFactory implements WebDataBinderFactory { + private MockWebDataBinder binder; + private BindingResult bindingResult; + + @Override + public WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName, ResolvableType targetType) throws Exception { + binder = new MockWebDataBinder(target, objectName); + if (target == null) { + binder.setTargetType(targetType); + } + + if (bindingResult != null) { + binder.setBindingResult(bindingResult); + } + + FormattingConversionServiceFactoryBean conversionServiceFactoryBean = new FormattingConversionServiceFactoryBean(); + conversionServiceFactoryBean.afterPropertiesSet(); + binder.setConversionService(conversionServiceFactoryBean.getObject()); + + return binder; + } + + @Override + public WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception { + binder = new MockWebDataBinder(target, objectName); + + if (bindingResult != null) { + binder.setBindingResult(bindingResult); + } + + FormattingConversionServiceFactoryBean conversionServiceFactoryBean = new FormattingConversionServiceFactoryBean(); + conversionServiceFactoryBean.afterPropertiesSet(); + binder.setConversionService(conversionServiceFactoryBean.getObject()); + + return binder; + } + + public void setBindingResult(BindingResult bindingResult) { + this.bindingResult = bindingResult; + } + + public MockWebDataBinder getBinder() { + return binder; + } +} diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolverTest.java index d58b62b..80abe2a 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolverTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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,13 @@ import com.mattbertolini.spring.web.bind.annotation.CookieParameter; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; +import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; -import javax.servlet.http.Cookie; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/ExceptionThrowingMockMultipartHttpServletRequest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/ExceptionThrowingMockMultipartHttpServletRequest.java index 41a07d1..6bc0a83 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/ExceptionThrowingMockMultipartHttpServletRequest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/ExceptionThrowingMockMultipartHttpServletRequest.java @@ -1,10 +1,10 @@ package com.mattbertolini.spring.web.servlet.mvc.bind.resolver; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.Part; import org.springframework.mock.web.MockMultipartHttpServletRequest; import org.springframework.web.multipart.MultipartFile; -import javax.servlet.ServletException; -import javax.servlet.http.Part; import java.io.IOException; import java.util.Collection; diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterMapRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterMapRequestPropertyResolverTest.java index 3c84ca6..b87facf 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterMapRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterMapRequestPropertyResolverTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 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,6 +19,7 @@ import com.mattbertolini.spring.web.bind.PropertyResolutionException; import com.mattbertolini.spring.web.bind.annotation.FormParameter; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; +import jakarta.servlet.http.Part; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; @@ -30,7 +31,6 @@ import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.multipart.MultipartFile; -import javax.servlet.http.Part; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.nio.charset.StandardCharsets; diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterRequestPropertyResolverTest.java index b2da2ae..aa0845a 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterRequestPropertyResolverTest.java @@ -19,6 +19,7 @@ import com.mattbertolini.spring.web.bind.PropertyResolutionException; import com.mattbertolini.spring.web.bind.annotation.FormParameter; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; +import jakarta.servlet.http.Part; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; @@ -29,7 +30,6 @@ import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.multipart.MultipartFile; -import javax.servlet.http.Part; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.nio.charset.StandardCharsets; diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestContextRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestContextRequestPropertyResolverTest.java index 16ffbb3..7b6f168 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestContextRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestContextRequestPropertyResolverTest.java @@ -18,6 +18,9 @@ import com.mattbertolini.spring.web.bind.annotation.RequestContext; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.HttpMethod; @@ -29,9 +32,6 @@ import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.i18n.FixedLocaleResolver; -import javax.servlet.ServletRequest; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.time.ZoneId; diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolverTest.java index 09029a1..0c28dc7 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolverTest.java @@ -19,6 +19,7 @@ import com.mattbertolini.spring.web.bind.PropertyResolutionException; import com.mattbertolini.spring.web.bind.annotation.RequestParameter; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; +import jakarta.servlet.http.Part; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; @@ -30,7 +31,6 @@ import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.multipart.MultipartFile; -import javax.servlet.http.Part; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.nio.charset.StandardCharsets; diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolverTest.java index 37c195e..869e469 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolverTest.java @@ -19,6 +19,7 @@ import com.mattbertolini.spring.web.bind.PropertyResolutionException; import com.mattbertolini.spring.web.bind.annotation.RequestParameter; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; +import jakarta.servlet.http.Part; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; @@ -29,7 +30,6 @@ import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.multipart.MultipartFile; -import javax.servlet.http.Part; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.nio.charset.StandardCharsets; From 2359ab09404d72ab1f7804deda1c699dbeb1a37b Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Tue, 16 Jul 2024 16:47:42 -0400 Subject: [PATCH 02/33] More updates for JDK 17 and Spring Boot 3.x --- .../bind/config/BinderConfiguration.java | 9 +- ...anParameterMethodArgumentResolverTest.java | 113 +++--------------- .../web/reactive/bind/MockBindingContext.java | 64 ++++++++++ .../bind/MockWebExchangeDataBinder.java | 73 +++++++++++ .../mvc/bind/config/BinderConfiguration.java | 6 +- .../build.gradle.kts | 2 + .../WebFluxBinderAutoConfiguration.java | 6 +- .../main/resources/META-INF/spring.factories | 2 - ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../WebMvcBinderAutoConfiguration.java | 6 +- .../main/resources/META-INF/spring.factories | 2 - ...ot.autoconfigure.AutoConfiguration.imports | 1 + 12 files changed, 167 insertions(+), 118 deletions(-) create mode 100644 spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockBindingContext.java create mode 100644 spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java delete mode 100644 webflux-annotated-data-binder-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 webflux-annotated-data-binder-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 webmvc-annotated-data-binder-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 webmvc-annotated-data-binder-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/config/BinderConfiguration.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/config/BinderConfiguration.java index 95df482..9345bab 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/config/BinderConfiguration.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/config/BinderConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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. @@ -42,7 +42,7 @@ import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter; import java.util.Collections; -import java.util.LinkedHashSet; +import java.util.HashSet; import java.util.Set; /** @@ -67,7 +67,7 @@ public BinderConfiguration() { * @param propertyResolverRegistry The resolver registry to use. */ public BinderConfiguration(@NonNull PropertyResolverRegistry propertyResolverRegistry) { - packagesToScan = new LinkedHashSet<>(); + packagesToScan = new HashSet<>(); this.propertyResolverRegistry = propertyResolverRegistry; } @@ -137,11 +137,10 @@ public Set getPackagesToScan() { @Override public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName) { - if (!(bean instanceof RequestMappingHandlerAdapter)) { + if (!(bean instanceof RequestMappingHandlerAdapter adapter)) { return bean; } - RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter) bean; ArgumentResolverConfigurer resolverConfigurer = adapter.getArgumentResolverConfigurer(); if (resolverConfigurer != null) { ReactiveAdapterRegistry reactiveAdapterRegistry = adapter.getReactiveAdapterRegistry(); diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java index 60bd2c8..6e051b6 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java @@ -24,22 +24,19 @@ import com.mattbertolini.spring.web.reactive.bind.resolver.RequestPropertyResolver; import jakarta.validation.Valid; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.PropertyValue; import org.springframework.beans.PropertyValues; import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; -import org.springframework.format.support.FormattingConversionServiceFactoryBean; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; -import org.springframework.validation.support.BindingAwareConcurrentModel; import org.springframework.web.bind.support.WebExchangeBindException; -import org.springframework.web.bind.support.WebExchangeDataBinder; -import org.springframework.web.reactive.BindingContext; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @@ -51,9 +48,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.catchThrowableOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -62,15 +56,14 @@ class BeanParameterMethodArgumentResolverTest { private ServerWebExchange exchange; private AnnotatedRequestBeanIntrospector introspector; - private BindingContext bindingContext; + private MockBindingContext bindingContext; @BeforeEach void setUp() { MockServerHttpRequest request = MockServerHttpRequest.get("/irrelevant").build(); exchange = MockServerWebExchange.from(request); introspector = mock(AnnotatedRequestBeanIntrospector.class); - bindingContext = mock(BindingContext.class); - when(bindingContext.getModel()).thenReturn(new BindingAwareConcurrentModel()); + bindingContext = new MockBindingContext(); ReactiveAdapterRegistry registry = new ReactiveAdapterRegistry(); resolver = new BeanParameterMethodArgumentResolver(registry, introspector); } @@ -105,14 +98,11 @@ void resolvesPropertyValues() throws Exception { MethodParameter methodParameter = createMethodParameter("anAnnotatedMethod", ABeanClass.class); - StubWebExchangeDataBinder dataBinder = new StubWebExchangeDataBinder(new ABeanClass()); - when(bindingContext.createDataBinder(eq(exchange), any(ABeanClass.class), anyString())).thenReturn(dataBinder); - when(introspector.getResolversFor(ABeanClass.class)).thenReturn(propertyData); Mono objectMono = resolver.resolveArgument(methodParameter, bindingContext, exchange); objectMono.block(); - PropertyValues propertyValues = dataBinder.getPropertyValues(); + PropertyValues propertyValues = bindingContext.getDataBinder().getPropertyValues(); assertThat(propertyValues.contains("propertyOne")).isTrue(); assertThat(propertyValues.contains("propertyTwo")).isTrue(); @@ -133,14 +123,11 @@ void resolvesOnlyFoundPropertyValues() throws Exception { MethodParameter methodParameter = createMethodParameter("anAnnotatedMethod", ABeanClass.class); - StubWebExchangeDataBinder dataBinder = new StubWebExchangeDataBinder(new ABeanClass()); - when(bindingContext.createDataBinder(eq(exchange), any(ABeanClass.class), anyString())).thenReturn(dataBinder); - when(introspector.getResolversFor(ABeanClass.class)).thenReturn(propertyData); Mono objectMono = resolver.resolveArgument(methodParameter, bindingContext, exchange); objectMono.block(); - PropertyValues propertyValues = dataBinder.getPropertyValues(); + PropertyValues propertyValues = bindingContext.getDataBinder().getPropertyValues(); assertThat(propertyValues.contains("propertyOne")).isFalse(); assertThat(propertyValues.contains("propertyTwo")).isTrue(); @@ -173,7 +160,6 @@ void throwsExceptionWhenResolverErrors() throws Exception { MethodParameter methodParameter = createMethodParameter("anAnnotatedMethod", ABeanClass.class); - when(bindingContext.createDataBinder(eq(exchange), any(ABeanClass.class), anyString())).thenReturn(new StubWebExchangeDataBinder(new ABeanClass())); when(introspector.getResolversFor(ABeanClass.class)).thenReturn(propertyData); Mono objectMono = resolver.resolveArgument(methodParameter, bindingContext, exchange); assertThatThrownBy(objectMono::block).isInstanceOf(RequestPropertyBindingException.class); @@ -183,38 +169,30 @@ void throwsExceptionWhenResolverErrors() throws Exception { void validatesWhenValidAnnotationPresent() throws Exception { MethodParameter methodParameter = createMethodParameter("aValidMethod", ABeanClass.class); - StubWebExchangeDataBinder dataBinder = new StubWebExchangeDataBinder(new ABeanClass()); - when(bindingContext.createDataBinder(eq(exchange), any(ABeanClass.class), anyString())).thenReturn(dataBinder); - Mono objectMono = resolver.resolveArgument(methodParameter, bindingContext, exchange); objectMono.block(); - assertThat(dataBinder.isValidateInvoked()).isTrue(); + assertThat(bindingContext.getDataBinder().isValidateInvoked()).isTrue(); } @Test void validatesWhenValidatedAnnotationPresent() throws Exception { MethodParameter methodParameter = createMethodParameter("aValidatedMethod", ABeanClass.class); - StubWebExchangeDataBinder dataBinder = new StubWebExchangeDataBinder(new ABeanClass()); - when(bindingContext.createDataBinder(eq(exchange), any(ABeanClass.class), anyString())).thenReturn(dataBinder); - Mono objectMono = resolver.resolveArgument(methodParameter, bindingContext, exchange); objectMono.block(); - assertThat(dataBinder.isValidateInvoked()).isTrue(); + assertThat(bindingContext.getDataBinder().isValidateInvoked()).isTrue(); } @Test void validatesWithGroups() throws Exception { MethodParameter methodParameter = createMethodParameter("validationGroups", ABeanClass.class); - StubWebExchangeDataBinder dataBinder = new StubWebExchangeDataBinder(new ABeanClass()); - when(bindingContext.createDataBinder(eq(exchange), any(ABeanClass.class), anyString())).thenReturn(dataBinder); - Mono objectMono = resolver.resolveArgument(methodParameter, bindingContext, exchange); objectMono.block(); + MockWebExchangeDataBinder dataBinder = bindingContext.getDataBinder(); assertThat(dataBinder.isValidateInvoked()).isTrue(); assertThat(dataBinder.getValidationHints()).contains(ValidationGroupOne.class, ValidationGroupTwo.class); } @@ -226,9 +204,7 @@ void validationThrowsBindException() throws Exception { BindingResult bindingResult = mock(BindingResult.class); when(bindingResult.hasErrors()).thenReturn(true); - StubWebExchangeDataBinder dataBinder = new StubWebExchangeDataBinder(new ABeanClass()); - dataBinder.setBindingResult(bindingResult); - when(bindingContext.createDataBinder(eq(exchange), any(ABeanClass.class), anyString())).thenReturn(dataBinder); + bindingContext.setBindingResult(bindingResult); Mono objectMono = resolver.resolveArgument(methodParameter, bindingContext, exchange); WebExchangeBindException exception = catchThrowableOfType(objectMono::block, WebExchangeBindException.class); @@ -236,86 +212,25 @@ void validationThrowsBindException() throws Exception { } @Test + @Disabled // TODO: Fix this void validationHasErrorsWithBindingResultMethodParameter() throws Exception { MethodParameter methodParameter = createMethodParameter("withBindingResult", ABeanClass.class, BindingResult.class); - BindingResult bindingResult = mock(BindingResult.class); - when(bindingResult.hasErrors()).thenReturn(true); +// BindingResult bindingResult = mock(BindingResult.class); +// when(bindingResult.hasErrors()).thenReturn(true); - StubWebExchangeDataBinder dataBinder = new StubWebExchangeDataBinder(new ABeanClass()); - dataBinder.setBindingResult(bindingResult); - when(bindingContext.createDataBinder(eq(exchange), any(ABeanClass.class), anyString())).thenReturn(dataBinder); +// bindingContext.setBindingResult(bindingResult); Mono objectMono = resolver.resolveArgument(methodParameter, bindingContext, exchange); objectMono.block(); - assertThat(dataBinder.getBindingResult()).isEqualTo(bindingResult); + assertThat(bindingContext.getDataBinder().getBindingResult().hasErrors()).isTrue(); } private MethodParameter createMethodParameter(String anAnnotatedMethod, Class... parameterTypes) throws NoSuchMethodException { return new MethodParameter(FakeHandlerMethod.class.getMethod(anAnnotatedMethod, parameterTypes), 0); } - private static class StubWebExchangeDataBinder extends WebExchangeDataBinder { - private boolean bindInvoked = false; - private boolean validateInvoked = true; - private PropertyValues pvs; - private List validationHints; - private BindingResult bindingResult; - - public StubWebExchangeDataBinder(Object target) { - super(target); - FormattingConversionServiceFactoryBean conversionServiceFactoryBean = new FormattingConversionServiceFactoryBean(); - conversionServiceFactoryBean.afterPropertiesSet(); - setConversionService(conversionServiceFactoryBean.getObject()); - } - - @Override - public void bind(PropertyValues pvs) { - this.pvs = pvs; - bindInvoked = true; - } - - @Override - public void validate() { - validateInvoked = true; - } - - @Override - public void validate(Object... validationHints) { - this.validationHints = Arrays.asList(validationHints); - validateInvoked = true; - } - - @Override - public BindingResult getBindingResult() { - if (bindingResult == null) { - return super.getBindingResult(); - } - return bindingResult; - } - - public void setBindingResult(BindingResult bindingResult) { - this.bindingResult = bindingResult; - } - - public boolean isBindInvoked() { - return bindInvoked; - } - - public boolean isValidateInvoked() { - return validateInvoked; - } - - public List getValidationHints() { - return validationHints; - } - - public PropertyValues getPropertyValues() { - return pvs; - } - } - private static class MockRequestPropertyResolver implements RequestPropertyResolver { private final Object value; private final RuntimeException exception; diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockBindingContext.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockBindingContext.java new file mode 100644 index 0000000..d55ed65 --- /dev/null +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockBindingContext.java @@ -0,0 +1,64 @@ +package com.mattbertolini.spring.web.reactive.bind; + +import org.springframework.core.ResolvableType; +import org.springframework.format.support.FormattingConversionServiceFactoryBean; +import org.springframework.lang.Nullable; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.validation.support.BindingAwareConcurrentModel; +import org.springframework.web.bind.support.WebExchangeDataBinder; +import org.springframework.web.reactive.BindingContext; +import org.springframework.web.server.ServerWebExchange; + +public class MockBindingContext extends BindingContext { + private final BindingAwareConcurrentModel model = new BindingAwareConcurrentModel(); + private MockWebExchangeDataBinder dataBinder; + private BindingResult bindingResult; + + @Override + public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, Object target, String name) { + dataBinder = new MockWebExchangeDataBinder(target); + + if (bindingResult != null) { + dataBinder.setBindingResult(bindingResult); + } + + FormattingConversionServiceFactoryBean conversionServiceFactoryBean = new FormattingConversionServiceFactoryBean(); + conversionServiceFactoryBean.afterPropertiesSet(); + dataBinder.setConversionService(conversionServiceFactoryBean.getObject()); + + return dataBinder; + } + + @Override + public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, @Nullable Object target, String name, ResolvableType targetType) { + dataBinder = new MockWebExchangeDataBinder(target); + + if (target == null) { + dataBinder.setTargetType(targetType); + } + + if (bindingResult != null) { + dataBinder.setBindingResult(bindingResult); + } + + FormattingConversionServiceFactoryBean conversionServiceFactoryBean = new FormattingConversionServiceFactoryBean(); + conversionServiceFactoryBean.afterPropertiesSet(); + dataBinder.setConversionService(conversionServiceFactoryBean.getObject()); + + return dataBinder; + } + + @Override + public Model getModel() { + return model; + } + + public MockWebExchangeDataBinder getDataBinder() { + return dataBinder; + } + + public void setBindingResult(BindingResult bindingResult) { + this.bindingResult = bindingResult; + } +} diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java new file mode 100644 index 0000000..293a137 --- /dev/null +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java @@ -0,0 +1,73 @@ +package com.mattbertolini.spring.web.reactive.bind; + +import org.springframework.beans.PropertyValues; +import org.springframework.lang.Nullable; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.support.WebExchangeDataBinder; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.util.Arrays; +import java.util.List; + +public class MockWebExchangeDataBinder extends WebExchangeDataBinder { + private boolean bindInvoked = false; + private boolean validateInvoked = true; + private PropertyValues pvs; + private List validationHints; + private BindingResult bindingResult; + + public MockWebExchangeDataBinder(@Nullable Object target) { + super(target); + } + + @Override + public Mono construct(ServerWebExchange exchange) { + return super.construct(exchange); // TODO + } + + @Override + public void bind(PropertyValues pvs) { + this.pvs = pvs; + bindInvoked = true; + } + + @Override + public void validate() { + validateInvoked = true; + } + + @Override + public void validate(Object... validationHints) { + this.validationHints = Arrays.asList(validationHints); + validateInvoked = true; + } + + @Override + public BindingResult getBindingResult() { + if (bindingResult == null) { + return super.getBindingResult(); + } + return bindingResult; + } + + public void setBindingResult(BindingResult bindingResult) { + this.bindingResult = bindingResult; + } + + public boolean isBindInvoked() { + return bindInvoked; + } + + public boolean isValidateInvoked() { + return validateInvoked; + } + + public List getValidationHints() { + return validationHints; + } + + public PropertyValues getPropertyValues() { + return pvs; + } +} diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/config/BinderConfiguration.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/config/BinderConfiguration.java index d66604c..e331223 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/config/BinderConfiguration.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/config/BinderConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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. @@ -91,12 +91,10 @@ public Set getPackagesToScan() { @Override public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName) { - if (!(bean instanceof RequestMappingHandlerAdapter)) { + if (!(bean instanceof RequestMappingHandlerAdapter adapter)) { return bean; } - RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter) bean; - PropertyResolverRegistry resolverRegistry = createPropertyResolverRegistry(adapter); AnnotatedRequestBeanIntrospector introspector = createIntrospector(resolverRegistry); BeanParameterMethodArgumentResolver resolver = createResolver(introspector); diff --git a/webflux-annotated-data-binder-spring-boot-starter/build.gradle.kts b/webflux-annotated-data-binder-spring-boot-starter/build.gradle.kts index 7e112b4..42110ac 100644 --- a/webflux-annotated-data-binder-spring-boot-starter/build.gradle.kts +++ b/webflux-annotated-data-binder-spring-boot-starter/build.gradle.kts @@ -10,6 +10,8 @@ dependencies { testImplementation(libs.junitJupiterApi) testImplementation(libs.assertJCore) testImplementation(libs.springBootTest) + testImplementation(libs.jakartaWebsocketClientApi) + testImplementation(libs.jakartaWebsocketApi) } tasks.named("jar").configure { diff --git a/webflux-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/reactive/bind/autoconfigure/WebFluxBinderAutoConfiguration.java b/webflux-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/reactive/bind/autoconfigure/WebFluxBinderAutoConfiguration.java index 5c6799f..ca58901 100644 --- a/webflux-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/reactive/bind/autoconfigure/WebFluxBinderAutoConfiguration.java +++ b/webflux-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/reactive/bind/autoconfigure/WebFluxBinderAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 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,11 +22,11 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurationPackages; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Role; import java.util.LinkedHashSet; @@ -34,7 +34,7 @@ import java.util.List; import java.util.Set; -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @Role(BeanDefinition.ROLE_INFRASTRUCTURE) @ConditionalOnMissingBean(BinderConfiguration.class) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) diff --git a/webflux-annotated-data-binder-spring-boot-starter/src/main/resources/META-INF/spring.factories b/webflux-annotated-data-binder-spring-boot-starter/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 25e6444..0000000 --- a/webflux-annotated-data-binder-spring-boot-starter/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - com.mattbertolini.spring.web.reactive.bind.autoconfigure.WebFluxBinderAutoConfiguration \ No newline at end of file diff --git a/webflux-annotated-data-binder-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/webflux-annotated-data-binder-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..fe8d94e --- /dev/null +++ b/webflux-annotated-data-binder-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.mattbertolini.spring.web.reactive.bind.autoconfigure.WebFluxBinderAutoConfiguration diff --git a/webmvc-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/autoconfigure/WebMvcBinderAutoConfiguration.java b/webmvc-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/autoconfigure/WebMvcBinderAutoConfiguration.java index 1fed7ac..a4be734 100644 --- a/webmvc-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/autoconfigure/WebMvcBinderAutoConfiguration.java +++ b/webmvc-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/autoconfigure/WebMvcBinderAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 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,11 +22,11 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurationPackages; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Role; import java.util.LinkedHashSet; @@ -34,7 +34,7 @@ import java.util.List; import java.util.Set; -@Configuration(proxyBeanMethods = false) +@AutoConfiguration @Role(BeanDefinition.ROLE_INFRASTRUCTURE) @ConditionalOnMissingBean(BinderConfiguration.class) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) diff --git a/webmvc-annotated-data-binder-spring-boot-starter/src/main/resources/META-INF/spring.factories b/webmvc-annotated-data-binder-spring-boot-starter/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 4246bcc..0000000 --- a/webmvc-annotated-data-binder-spring-boot-starter/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - com.mattbertolini.spring.web.servlet.mvc.bind.autoconfigure.WebMvcBinderAutoConfiguration \ No newline at end of file diff --git a/webmvc-annotated-data-binder-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/webmvc-annotated-data-binder-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..975fc27 --- /dev/null +++ b/webmvc-annotated-data-binder-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.mattbertolini.spring.web.servlet.mvc.bind.autoconfigure.WebMvcBinderAutoConfiguration From 6464da4ab2644807033c57c8668fc68e81548616 Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Tue, 16 Jul 2024 18:10:19 -0400 Subject: [PATCH 03/33] Green build --- docs/build.gradle.kts | 2 ++ .../resolver/RequestPropertyResolverBase.java | 4 +-- .../bind/config/BinderConfiguration.java | 2 +- .../RequestBodyRequestPropertyResolver.java | 3 +- ...anParameterMethodArgumentResolverTest.java | 13 ++++---- .../BeanParameterMethodArgumentResolver.java | 32 +++++++++++++------ .../web/servlet/mvc/bind/package-info.java | 4 +++ ...ookieParameterRequestPropertyResolver.java | 7 ++-- ...stParameterMapRequestPropertyResolver.java | 4 ++- ...questParameterRequestPropertyResolver.java | 7 ++-- .../resolver/RequestPropertyResolver.java | 2 +- ...ssionParameterRequestPropertyResolver.java | 8 +++-- .../mvc/bind/resolver/package-info.java | 4 +++ 13 files changed, 60 insertions(+), 32 deletions(-) create mode 100644 spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/package-info.java create mode 100644 spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/package-info.java diff --git a/docs/build.gradle.kts b/docs/build.gradle.kts index 15451ce..b318e2b 100644 --- a/docs/build.gradle.kts +++ b/docs/build.gradle.kts @@ -21,6 +21,8 @@ dependencies { testImplementation(libs.mockitoCore) testImplementation(libs.springTest) testCompileOnly(libs.hamcrest) // Needed for Spring mock MVC matchers + testImplementation(libs.jakartaWebsocketApi) + testImplementation(libs.jakartaWebsocketClientApi) } tasks.named("asciidoctor").configure { diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/RequestPropertyResolverBase.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/RequestPropertyResolverBase.java index 2dc977b..7543f4b 100644 --- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/RequestPropertyResolverBase.java +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/RequestPropertyResolverBase.java @@ -28,8 +28,8 @@ * @param The response type to use. */ public interface RequestPropertyResolverBase { - boolean supports(@NonNull BindingProperty bindingProperty); + boolean supports(BindingProperty bindingProperty); @Nullable - R resolve(@NonNull BindingProperty bindingProperty, @NonNull T request); + R resolve(BindingProperty bindingProperty, T request); } diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/config/BinderConfiguration.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/config/BinderConfiguration.java index 9345bab..ff58e52 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/config/BinderConfiguration.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/config/BinderConfiguration.java @@ -116,7 +116,7 @@ public BinderConfiguration addResolvers(Set resolvers) } /** - * Add all of the resolvers from the given registry into this registry. + * Add all the resolvers from the given registry into this registry. * * @param propertyResolverRegistry The registry to add resolvers from. Required. * @return This instance of the configuration. diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestBodyRequestPropertyResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestBodyRequestPropertyResolver.java index dbf7617..b71c32d 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestBodyRequestPropertyResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestBodyRequestPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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. @@ -45,7 +45,6 @@ public boolean supports(@NonNull BindingProperty bindingProperty) { return bindingProperty.hasAnnotation(RequestBody.class); } - @NonNull @Override public Mono resolve(@NonNull BindingProperty bindingProperty, @NonNull ServerWebExchange request) { diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java index 6e051b6..3578a75 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java @@ -24,7 +24,6 @@ import com.mattbertolini.spring.web.reactive.bind.resolver.RequestPropertyResolver; import jakarta.validation.Valid; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.PropertyValue; import org.springframework.beans.PropertyValues; @@ -212,19 +211,21 @@ void validationThrowsBindException() throws Exception { } @Test - @Disabled // TODO: Fix this void validationHasErrorsWithBindingResultMethodParameter() throws Exception { MethodParameter methodParameter = createMethodParameter("withBindingResult", ABeanClass.class, BindingResult.class); -// BindingResult bindingResult = mock(BindingResult.class); -// when(bindingResult.hasErrors()).thenReturn(true); + BindingResult bindingResult = mock(BindingResult.class); + when(bindingResult.hasErrors()) + .thenReturn(false) + .thenReturn(false) + .thenReturn(true); -// bindingContext.setBindingResult(bindingResult); + bindingContext.setBindingResult(bindingResult); Mono objectMono = resolver.resolveArgument(methodParameter, bindingContext, exchange); objectMono.block(); - assertThat(bindingContext.getDataBinder().getBindingResult().hasErrors()).isTrue(); + assertThat(bindingContext.getDataBinder().getBindingResult()).isEqualTo(bindingResult); } private MethodParameter createMethodParameter(String anAnnotatedMethod, Class... parameterTypes) throws NoSuchMethodException { diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java index 071208f..01c3557 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java @@ -25,10 +25,10 @@ import org.springframework.beans.BeanUtils; import org.springframework.beans.MutablePropertyValues; import org.springframework.core.MethodParameter; -import org.springframework.lang.NonNull; import org.springframework.util.Assert; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.method.annotation.ModelAttributeMethodProcessor; import java.util.Collection; @@ -37,39 +37,51 @@ import java.util.Objects; public class BeanParameterMethodArgumentResolver extends ModelAttributeMethodProcessor { + private static final String BIND_VALUES_ATTRIBUTE_KEY = BeanParameterMethodArgumentResolver.class.getName() + ".bindValues"; private final AnnotatedRequestBeanIntrospector introspector; - public BeanParameterMethodArgumentResolver(@NonNull AnnotatedRequestBeanIntrospector introspector) { + public BeanParameterMethodArgumentResolver(AnnotatedRequestBeanIntrospector introspector) { super(false); this.introspector = introspector; } @Override - public boolean supportsParameter(@NonNull MethodParameter parameter) { + public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(BeanParameter.class) && !BeanUtils.isSimpleProperty(parameter.getParameterType()); } @Override - public boolean supportsReturnType(@NonNull MethodParameter returnType) { + public boolean supportsReturnType(MethodParameter returnType) { return false; } @Override - protected void constructAttribute(WebDataBinder binder, @NonNull NativeWebRequest request) { + protected void constructAttribute(WebDataBinder binder, NativeWebRequest request) { Assert.state(binder.getTargetType() != null, "WebDataBinder must have a target type"); - Map valuesToBind = getValuesToBind(Objects.requireNonNull(binder.getTargetType().getRawClass()), request); + Map valuesToBind = memoizedGetValuesToBind(Objects.requireNonNull(binder.getTargetType().getRawClass()), request); binder.construct(new MapValueResolver(valuesToBind)); } @Override - protected void bindRequestParameters(@NonNull WebDataBinder binder, @NonNull NativeWebRequest request) { + protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) { Assert.state(binder.getTarget() != null, "WebDataBinder must have a target object"); - Map valuesToBind = getValuesToBind(binder.getTarget().getClass(), request); + Map valuesToBind = memoizedGetValuesToBind(binder.getTarget().getClass(), request); binder.bind(new MutablePropertyValues(valuesToBind)); + request.removeAttribute(BIND_VALUES_ATTRIBUTE_KEY, RequestAttributes.SCOPE_REQUEST); } - @NonNull - private Map getValuesToBind(@NonNull Class targetType, @NonNull NativeWebRequest request) { + @SuppressWarnings("unchecked") + private Map memoizedGetValuesToBind(Class targetType, NativeWebRequest request) { + Map memoizedValues = (Map) request.getAttribute(BIND_VALUES_ATTRIBUTE_KEY, NativeWebRequest.SCOPE_REQUEST); + if (memoizedValues != null) { + return memoizedValues; + } + Map valuesToBind = getValuesToBind(targetType, request); + request.setAttribute(BIND_VALUES_ATTRIBUTE_KEY, valuesToBind, NativeWebRequest.SCOPE_REQUEST); + return valuesToBind; + } + + private Map getValuesToBind(Class targetType, NativeWebRequest request) { Map values = new HashMap<>(); Collection propertyData = introspector.getResolversFor(targetType); for (ResolvedPropertyData data : propertyData) { diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/package-info.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/package-info.java new file mode 100644 index 0000000..a382391 --- /dev/null +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/package-info.java @@ -0,0 +1,4 @@ +@NonNullApi +package com.mattbertolini.spring.web.servlet.mvc.bind; + +import org.springframework.lang.NonNullApi; diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolver.java index 4e10e36..12fa0fe 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolver.java @@ -20,19 +20,20 @@ import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; -import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.util.WebUtils; public class CookieParameterRequestPropertyResolver implements RequestPropertyResolver { @Override - public boolean supports(@NonNull BindingProperty bindingProperty) { + public boolean supports(BindingProperty bindingProperty) { return bindingProperty.hasAnnotation(CookieParameter.class); } @Override - public Object resolve(@NonNull BindingProperty bindingProperty, @NonNull NativeWebRequest request) { + @Nullable + public Object resolve(BindingProperty bindingProperty, NativeWebRequest request) { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); Assert.state(servletRequest != null, "A HttpServletRequest is required for this resolver and none found."); CookieParameter annotation = bindingProperty.getAnnotation(CookieParameter.class); diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolver.java index d30a651..240201d 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 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,6 +24,7 @@ import jakarta.servlet.http.Part; import org.springframework.core.ResolvableType; import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -48,6 +49,7 @@ public boolean supports(@NonNull BindingProperty bindingProperty) { } @Override + @Nullable public Object resolve(@NonNull BindingProperty bindingProperty, @NonNull NativeWebRequest request) { ResolvableType resolvableType = ResolvableType.forMethodParameter(bindingProperty.getMethodParameter()); diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolver.java index b6de384..bb096cc 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolver.java @@ -22,6 +22,7 @@ import com.mattbertolini.spring.web.bind.resolver.AbstractNamedRequestPropertyResolver; import jakarta.servlet.http.HttpServletRequest; import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.context.request.NativeWebRequest; @@ -31,12 +32,13 @@ public class RequestParameterRequestPropertyResolver extends AbstractNamedReques implements RequestPropertyResolver { @Override - public boolean supports(@NonNull BindingProperty bindingProperty) { + public boolean supports(BindingProperty bindingProperty) { RequestParameter annotation = bindingProperty.getAnnotation(RequestParameter.class); return annotation != null && StringUtils.hasText(annotation.value()); } @Override + @Nullable protected Object resolveWithName(@NonNull BindingProperty bindingProperty, String name, @NonNull NativeWebRequest request) { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); if (servletRequest != null) { @@ -54,8 +56,7 @@ protected Object resolveWithName(@NonNull BindingProperty bindingProperty, Strin } @Override - @NonNull - protected String getName(@NonNull BindingProperty bindingProperty) { + protected String getName(BindingProperty bindingProperty) { RequestParameter annotation = bindingProperty.getAnnotation(RequestParameter.class); Assert.state(annotation != null, "No RequestParameter annotation found on type"); return annotation.value(); diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestPropertyResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestPropertyResolver.java index 7aedadd..66efb13 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestPropertyResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/SessionParameterRequestPropertyResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/SessionParameterRequestPropertyResolver.java index c7aeb15..7f3c7f4 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/SessionParameterRequestPropertyResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/SessionParameterRequestPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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,18 +19,20 @@ import com.mattbertolini.spring.web.bind.annotation.SessionParameter; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.RequestAttributes; public class SessionParameterRequestPropertyResolver implements RequestPropertyResolver { @Override - public boolean supports(@NonNull BindingProperty bindingProperty) { + public boolean supports(BindingProperty bindingProperty) { return bindingProperty.hasAnnotation(SessionParameter.class); } @Override - public Object resolve(@NonNull BindingProperty bindingProperty, @NonNull NativeWebRequest request) { + @Nullable + public Object resolve(BindingProperty bindingProperty, NativeWebRequest request) { SessionParameter annotation = bindingProperty.getAnnotation(SessionParameter.class); Assert.state(annotation != null, "No SessionParameter annotation found on type"); return request.getAttribute(annotation.value(), RequestAttributes.SCOPE_SESSION); diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/package-info.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/package-info.java new file mode 100644 index 0000000..3319042 --- /dev/null +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/package-info.java @@ -0,0 +1,4 @@ +@NonNullApi +package com.mattbertolini.spring.web.servlet.mvc.bind.resolver; + +import org.springframework.lang.NonNullApi; From 5d7ab27ded1997e470de714c9fd27cb7d0e8245f Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Wed, 17 Jul 2024 08:39:48 -0400 Subject: [PATCH 04/33] Start enforcing nullable annotations via NullAway --- buildSrc/build.gradle.kts | 5 ++++ .../buildlogic/error-prone.gradle.kts | 30 +++++++++++++++++++ .../buildlogic/java-conventions.gradle.kts | 1 + libs.versions.toml | 7 +++++ .../AbstractPropertyResolverRegistry.java | 6 ++-- .../web/bind/PropertyResolutionException.java | 2 +- .../bind/RequestPropertyBindingException.java | 2 +- .../web/bind/annotation/package-info.java | 6 ++++ .../AnnotatedRequestBeanIntrospector.java | 10 ++----- .../web/bind/introspect/BindingProperty.java | 16 ++++------ .../web/bind/introspect/package-info.java | 6 ++++ .../spring/web/bind/package-info.java | 6 ++++ .../AbstractNamedRequestPropertyResolver.java | 13 ++++---- .../resolver/RequestPropertyResolverBase.java | 2 +- .../web/bind/resolver/package-info.java | 6 ++++ .../web/bind/support/MapValueResolver.java | 22 ++++++++++++++ .../spring/web/bind/support/package-info.java | 2 ++ .../introspect/ResolvedPropertyDataTest.java | 13 +++++--- .../web/bind/introspect/scan/IgnoredBean.java | 3 ++ .../web/bind/introspect/scan/ScannedBean.java | 3 ++ .../scan/subbackage/SubpackageBean.java | 3 ++ .../BeanParameterMethodArgumentResolver.java | 14 ++++----- .../bind/config/BinderConfiguration.java | 9 +++--- .../reactive/bind/config/package-info.java | 6 ++++ .../web/reactive/bind/package-info.java | 6 ++++ ...ookieParameterRequestPropertyResolver.java | 11 +++---- ...rmParameterMapRequestPropertyResolver.java | 6 ++-- .../FormParameterRequestPropertyResolver.java | 10 +++---- ...erParameterMapRequestPropertyResolver.java | 4 +-- ...eaderParameterRequestPropertyResolver.java | 8 +++-- .../PathParameterRequestPropertyResolver.java | 6 ++-- ...questParameterRequestPropertyResolver.java | 7 +++-- .../resolver/RequestPropertyResolver.java | 4 +-- ...ssionParameterRequestPropertyResolver.java | 14 ++++----- .../reactive/bind/resolver/package-info.java | 6 ++++ .../mvc/bind/PropertyResolverRegistry.java | 2 +- .../mvc/bind/config/BinderConfiguration.java | 5 ++-- .../servlet/mvc/bind/config/package-info.java | 6 ++++ .../web/servlet/mvc/bind/package-info.java | 2 ++ ...ookieParameterRequestPropertyResolver.java | 4 ++- .../FormParameterRequestPropertyResolver.java | 13 ++++---- ...eaderParameterRequestPropertyResolver.java | 16 ++++++---- .../PathParameterRequestPropertyResolver.java | 13 ++++---- ...RequestContextRequestPropertyResolver.java | 9 +++--- ...stParameterMapRequestPropertyResolver.java | 6 ++-- ...questParameterRequestPropertyResolver.java | 8 +++-- ...ssionParameterRequestPropertyResolver.java | 6 ++-- .../mvc/bind/resolver/package-info.java | 2 ++ 48 files changed, 252 insertions(+), 115 deletions(-) create mode 100644 buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/error-prone.gradle.kts create mode 100644 spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/annotation/package-info.java create mode 100644 spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/package-info.java create mode 100644 spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/package-info.java create mode 100644 spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/package-info.java create mode 100644 spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/config/package-info.java create mode 100644 spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/package-info.java create mode 100644 spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/package-info.java create mode 100644 spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/config/package-info.java diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 876c922..1316170 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -3,5 +3,10 @@ plugins { } repositories { + gradlePluginPortal() mavenCentral() } + +dependencies { + implementation("net.ltgt.gradle:gradle-errorprone-plugin:4.0.1") +} diff --git a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/error-prone.gradle.kts b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/error-prone.gradle.kts new file mode 100644 index 0000000..6fa68c7 --- /dev/null +++ b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/error-prone.gradle.kts @@ -0,0 +1,30 @@ +package com.mattbertolini.buildlogic + +import net.ltgt.gradle.errorprone.CheckSeverity +import net.ltgt.gradle.errorprone.errorprone + +plugins { + id("net.ltgt.errorprone") +} + +val libsCatalog = versionCatalogs.named("libs") +val errorProneCore = libsCatalog.findLibrary("errorProneCore").orElseThrow() +val errorProneAnnotations = libsCatalog.findLibrary("errorProneAnnotations").orElseThrow(); +val nullAway = libsCatalog.findLibrary("nullAway").orElseThrow() +val nullAwayAnnotations = libsCatalog.findLibrary("nullAwayAnnotations").orElseThrow() + +dependencies { + errorprone(errorProneCore) + errorprone(nullAway) + add("compileOnly", errorProneAnnotations) + add("compileOnly", nullAwayAnnotations) +} + +tasks.withType().configureEach { + options.isFork = true + options.errorprone { + disableWarningsInGeneratedCode.set(true) + check("NullAway", CheckSeverity.ERROR) + option("NullAway:AnnotatedPackages", "com.mattbertolini") + } +} diff --git a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts index c928c35..5e8ee91 100644 --- a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts @@ -3,6 +3,7 @@ package com.mattbertolini.buildlogic plugins { java jacoco + id("com.mattbertolini.buildlogic.error-prone") } val versionCatalog = versionCatalogs.named("libs") diff --git a/libs.versions.toml b/libs.versions.toml index 00540e2..f8047b9 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -3,6 +3,8 @@ spring = "6.1.10" # Used in java-conventions.gradle.kts springBoot = "3.3.1" # Used in java-conventions.gradle.kts junit = "5.9.3" # Used in java-conventions.gradle.kts jacoco = "0.8.10" # Used in java-conventions.gradle.kts +errorProne = "2.29.0" +nullAway = "0.11.0" [libraries] jakartaServletApi = { module = "jakarta.servlet:jakarta.servlet-api", version = "6.0.0" } @@ -34,6 +36,11 @@ mockitoCore = { module = "org.mockito:mockito-core", version = "5.3.1" } equalsVerifier = { module = "nl.jqno.equalsverifier:equalsverifier", version = "3.14.2" } hamcrest = { module = "org.hamcrest:hamcrest", version = "2.2" } +errorProneCore = { module = "com.google.errorprone:error_prone_core", version.ref = "errorProne" } +errorProneAnnotations = { module = "com.google.errorprone:error_prone_annotations", version.ref = "errorProne"} +nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" } +nullAwayAnnotations = { module = "com.uber.nullaway:nullaway-annotations", version.ref = "nullAway" } + [plugins] asciidoctorConvert = { id = "org.asciidoctor.jvm.convert", version = "3.3.2" } sonarqube = { id = "org.sonarqube", version = "5.0.0.4638" } \ No newline at end of file diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/AbstractPropertyResolverRegistry.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/AbstractPropertyResolverRegistry.java index c466838..46e1f64 100644 --- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/AbstractPropertyResolverRegistry.java +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/AbstractPropertyResolverRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 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,7 +18,6 @@ import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import com.mattbertolini.spring.web.bind.resolver.RequestPropertyResolverBase; -import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import java.util.Collections; @@ -38,7 +37,7 @@ protected AbstractPropertyResolverRegistry() { } @Nullable - public T findResolverFor(@NonNull BindingProperty bindingProperty) { + public T findResolverFor(BindingProperty bindingProperty) { for (T resolver : propertyResolvers) { if (resolver.supports(bindingProperty)) { return resolver; @@ -77,7 +76,6 @@ public void addResolvers(AbstractPropertyResolverRegistry registry) { /** * Returns an unmodifiable collection of the resolvers. */ - @NonNull public Set getPropertyResolvers() { return Collections.unmodifiableSet(propertyResolvers); } diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/PropertyResolutionException.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/PropertyResolutionException.java index 614bfae..74880d9 100644 --- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/PropertyResolutionException.java +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/PropertyResolutionException.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/RequestPropertyBindingException.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/RequestPropertyBindingException.java index a5c553e..95a8af2 100644 --- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/RequestPropertyBindingException.java +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/RequestPropertyBindingException.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/annotation/package-info.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/annotation/package-info.java new file mode 100644 index 0000000..084b600 --- /dev/null +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/annotation/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package com.mattbertolini.spring.web.bind.annotation; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/AnnotatedRequestBeanIntrospector.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/AnnotatedRequestBeanIntrospector.java index 501ef87..2230333 100644 --- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/AnnotatedRequestBeanIntrospector.java +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/AnnotatedRequestBeanIntrospector.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 the original author or authors. + * Copyright 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,8 +16,6 @@ package com.mattbertolini.spring.web.bind.introspect; -import org.springframework.lang.NonNull; - import java.util.Collection; import java.util.Collections; import java.util.Map; @@ -32,8 +30,7 @@ public interface AnnotatedRequestBeanIntrospector { * @return A map of resolved property data. This map is never null but may be empty. * @throws CircularReferenceException If a circular reference is found while traversing the object graph. */ - @NonNull - Map getResolverMapFor(@NonNull Class targetType); + Map getResolverMapFor(Class targetType); /** * Creates a list of resolved property data for the given target class. This method traverses the object graph for @@ -43,8 +40,7 @@ public interface AnnotatedRequestBeanIntrospector { * @return A list of resolved property data. This list is never null but may be empty. * @throws CircularReferenceException If a circular reference is found while traversing the object graph. */ - @NonNull - default Collection getResolversFor(@NonNull Class targetType) { + default Collection getResolversFor(Class targetType) { Map propertyData = getResolverMapFor(targetType); return Collections.unmodifiableCollection(propertyData.values()); } diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/BindingProperty.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/BindingProperty.java index 357e637..6c132d1 100644 --- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/BindingProperty.java +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/BindingProperty.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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,7 +19,6 @@ import org.springframework.core.MethodParameter; import org.springframework.core.convert.Property; import org.springframework.core.convert.TypeDescriptor; -import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import java.beans.PropertyDescriptor; @@ -35,7 +34,7 @@ public final class BindingProperty { private final TypeDescriptor typeDescriptor; private final MethodParameter methodParameter; - private BindingProperty(@NonNull TypeDescriptor typeDescriptor, @NonNull MethodParameter methodParameter) { + private BindingProperty(TypeDescriptor typeDescriptor, MethodParameter methodParameter) { this.typeDescriptor = typeDescriptor; this.methodParameter = methodParameter; } @@ -45,7 +44,6 @@ public T getAnnotation(Class annotationType) { return typeDescriptor.getAnnotation(annotationType); } - @NonNull public MethodParameter getMethodParameter() { return methodParameter; } @@ -69,8 +67,7 @@ public boolean hasAnnotation(Class annotationType) { * @param propertyDescriptor The Java Beans PropertyDescriptor to create a BindingProperty for. * @return A new BindingProperty object. */ - @NonNull - public static BindingProperty forPropertyDescriptor(@NonNull PropertyDescriptor propertyDescriptor) { + public static BindingProperty forPropertyDescriptor(PropertyDescriptor propertyDescriptor) { Property property = new Property( propertyDescriptor.getPropertyType(), propertyDescriptor.getReadMethod(), @@ -86,8 +83,7 @@ public static BindingProperty forPropertyDescriptor(@NonNull PropertyDescriptor * This method is more or less the same as found in {@link Property} but those are not exposed publicly so I * needed to replicate it. */ - @NonNull - private static MethodParameter resolveMethodParameter(@NonNull Property property) { + private static MethodParameter resolveMethodParameter(Property property) { MethodParameter readMethodParameter = resolveReadMethodParameter(property); MethodParameter writeMethodParameter = resolveWriteMethodParameter(property); if (writeMethodParameter == null) { @@ -107,7 +103,7 @@ private static MethodParameter resolveMethodParameter(@NonNull Property property } @Nullable - private static MethodParameter resolveReadMethodParameter(@NonNull Property property) { + private static MethodParameter resolveReadMethodParameter(Property property) { if (property.getReadMethod() == null) { return null; } @@ -115,7 +111,7 @@ private static MethodParameter resolveReadMethodParameter(@NonNull Property prop } @Nullable - private static MethodParameter resolveWriteMethodParameter(@NonNull Property property) { + private static MethodParameter resolveWriteMethodParameter(Property property) { if (property.getWriteMethod() == null) { return null; } diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/package-info.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/package-info.java new file mode 100644 index 0000000..4555f99 --- /dev/null +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package com.mattbertolini.spring.web.bind.introspect; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/package-info.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/package-info.java new file mode 100644 index 0000000..adbe5e8 --- /dev/null +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package com.mattbertolini.spring.web.bind; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/AbstractNamedRequestPropertyResolver.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/AbstractNamedRequestPropertyResolver.java index f66d9cd..a5f1c9a 100644 --- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/AbstractNamedRequestPropertyResolver.java +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/AbstractNamedRequestPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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. @@ -17,17 +17,18 @@ package com.mattbertolini.spring.web.bind.resolver; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; -import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; public abstract class AbstractNamedRequestPropertyResolver implements RequestPropertyResolverBase { - @NonNull - protected abstract String getName(@NonNull BindingProperty bindingProperty); + protected abstract String getName(BindingProperty bindingProperty); @Override - public final R resolve(@NonNull BindingProperty bindingProperty, @NonNull T request) { + @Nullable + public final R resolve(BindingProperty bindingProperty, T request) { String name = getName(bindingProperty); return resolveWithName(bindingProperty, name, request); } - protected abstract R resolveWithName(@NonNull BindingProperty bindingProperty, String name, @NonNull T request); + @Nullable + protected abstract R resolveWithName(BindingProperty bindingProperty, String name, T request); } diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/RequestPropertyResolverBase.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/RequestPropertyResolverBase.java index 7543f4b..1f6b301 100644 --- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/RequestPropertyResolverBase.java +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/RequestPropertyResolverBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/package-info.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/package-info.java new file mode 100644 index 0000000..9668813 --- /dev/null +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package com.mattbertolini.spring.web.bind.resolver; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/MapValueResolver.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/MapValueResolver.java index 30fb14b..51e649e 100644 --- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/MapValueResolver.java +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/MapValueResolver.java @@ -1,8 +1,25 @@ +/* + * Copyright 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 com.mattbertolini.spring.web.bind.support; import org.springframework.lang.Nullable; import org.springframework.validation.DataBinder; +import java.util.Collections; import java.util.Map; import java.util.Set; @@ -17,4 +34,9 @@ public Object resolveValue(String name, Class type) { public Set getNames() { return Set.copyOf(values.keySet()); } + + @Override + public Map values() { + return Collections.unmodifiableMap(values); + } } diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/package-info.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/package-info.java index 7debe91..bf23899 100644 --- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/package-info.java +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/package-info.java @@ -1,4 +1,6 @@ @NonNullApi +@NonNullFields package com.mattbertolini.spring.web.bind.support; import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/ResolvedPropertyDataTest.java b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/ResolvedPropertyDataTest.java index 78a7f32..04f3c5f 100644 --- a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/ResolvedPropertyDataTest.java +++ b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/ResolvedPropertyDataTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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. @@ -21,7 +21,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.core.convert.TypeDescriptor; -import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import java.beans.PropertyDescriptor; @@ -69,22 +69,26 @@ void equalsContract() throws Exception { private static class StubResolver implements RequestPropertyResolverBase { @Override - public boolean supports(@NonNull BindingProperty bindingProperty) { + public boolean supports(BindingProperty bindingProperty) { return false; } @Override - public Object resolve(@NonNull BindingProperty bindingProperty, @NonNull Object request) { + @Nullable + public Object resolve(BindingProperty bindingProperty, Object request) { return null; } } @SuppressWarnings("unused") private static class TestingClass { + @Nullable private String property; + @Nullable private String another; + @Nullable public String getProperty() { return property; } @@ -93,6 +97,7 @@ public void setProperty(String property) { this.property = property; } + @Nullable public String getAnother() { return another; } diff --git a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/scan/IgnoredBean.java b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/scan/IgnoredBean.java index 13fc9d6..45928cb 100644 --- a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/scan/IgnoredBean.java +++ b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/scan/IgnoredBean.java @@ -17,11 +17,14 @@ package com.mattbertolini.spring.web.bind.introspect.scan; import com.mattbertolini.spring.web.bind.annotation.RequestParameter; +import org.springframework.lang.Nullable; public class IgnoredBean { @RequestParameter("property") + @Nullable private String property; + @Nullable public String getProperty() { return property; } diff --git a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/scan/ScannedBean.java b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/scan/ScannedBean.java index e7092f2..b816ba3 100644 --- a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/scan/ScannedBean.java +++ b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/scan/ScannedBean.java @@ -18,12 +18,15 @@ import com.mattbertolini.spring.web.bind.annotation.RequestBean; import com.mattbertolini.spring.web.bind.annotation.RequestParameter; +import org.springframework.lang.Nullable; @RequestBean public class ScannedBean { @RequestParameter("property") + @Nullable private String property; + @Nullable public String getProperty() { return property; } diff --git a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/scan/subbackage/SubpackageBean.java b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/scan/subbackage/SubpackageBean.java index 5b958a7..213030c 100644 --- a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/scan/subbackage/SubpackageBean.java +++ b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/scan/subbackage/SubpackageBean.java @@ -18,12 +18,15 @@ import com.mattbertolini.spring.web.bind.annotation.FormParameter; import com.mattbertolini.spring.web.bind.annotation.RequestBean; +import org.springframework.lang.Nullable; @RequestBean public class SubpackageBean { @FormParameter("subpackage_property") + @Nullable private String subpackageProperty; + @Nullable public String getSubpackageProperty() { return subpackageProperty; } diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java index 73597b5..7c35fae 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java @@ -45,20 +45,19 @@ public class BeanParameterMethodArgumentResolver extends ModelAttributeMethodArg private final AnnotatedRequestBeanIntrospector introspector; public BeanParameterMethodArgumentResolver( - @NonNull ReactiveAdapterRegistry adapterRegistry, - @NonNull AnnotatedRequestBeanIntrospector introspector) { + ReactiveAdapterRegistry adapterRegistry, + AnnotatedRequestBeanIntrospector introspector) { super(adapterRegistry, false); this.introspector = introspector; } @Override - public boolean supportsParameter(@NonNull MethodParameter parameter) { + public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(BeanParameter.class) && !BeanUtils.isSimpleProperty(parameter.getParameterType()); } @Override - @NonNull - protected Mono constructAttribute(WebExchangeDataBinder binder, @NonNull ServerWebExchange exchange) { + protected Mono constructAttribute(WebExchangeDataBinder binder, ServerWebExchange exchange) { Assert.state(binder.getTargetType() != null, "WebExchangeDataBinder must have a target type"); Collection propertyData = introspector.getResolversFor(Objects.requireNonNull(binder.getTargetType().getRawClass())); return getValuesToBind(propertyData, exchange) @@ -69,7 +68,7 @@ protected Mono constructAttribute(WebExchangeDataBinder binder, @NonNull S @Override @NonNull - protected Mono bindRequestParameters(@NonNull WebExchangeDataBinder binder, @NonNull ServerWebExchange exchange) { + protected Mono bindRequestParameters(WebExchangeDataBinder binder, ServerWebExchange exchange) { Assert.state(binder.getTarget() != null, "WebExchangeDataBinder must have a target object"); Collection propertyData = introspector.getResolversFor(binder.getTarget().getClass()); return getValuesToBind(propertyData, exchange) @@ -78,8 +77,7 @@ protected Mono bindRequestParameters(@NonNull WebExchangeDataBinder binder .then(); } - @NonNull - private Mono> getValuesToBind(@NonNull Collection propertyData, @NonNull ServerWebExchange exchange) { + private Mono> getValuesToBind(Collection propertyData, ServerWebExchange exchange) { return Flux.fromIterable(propertyData).flatMap(data -> { BindingProperty bindingProperty = data.getBindingProperty(); RequestPropertyResolver resolver = (RequestPropertyResolver) data.getResolver(); diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/config/BinderConfiguration.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/config/BinderConfiguration.java index ff58e52..2e10d34 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/config/BinderConfiguration.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/config/BinderConfiguration.java @@ -37,7 +37,7 @@ import org.springframework.beans.factory.BeanInitializationException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.core.ReactiveAdapterRegistry; -import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer; import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter; @@ -66,7 +66,7 @@ public BinderConfiguration() { * * @param propertyResolverRegistry The resolver registry to use. */ - public BinderConfiguration(@NonNull PropertyResolverRegistry propertyResolverRegistry) { + public BinderConfiguration(PropertyResolverRegistry propertyResolverRegistry) { packagesToScan = new HashSet<>(); this.propertyResolverRegistry = propertyResolverRegistry; } @@ -136,7 +136,8 @@ public Set getPackagesToScan() { } @Override - public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName) { + @Nullable + public Object postProcessBeforeInitialization(Object bean, String beanName) { if (!(bean instanceof RequestMappingHandlerAdapter adapter)) { return bean; } @@ -156,7 +157,7 @@ public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull Str } return adapter; } - + private PropertyResolverRegistry createPropertyResolverRegistry(RequestMappingHandlerAdapter adapter, ReactiveAdapterRegistry reactiveAdapterRegistry) { PropertyResolverRegistry registry = new PropertyResolverRegistry(); diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/config/package-info.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/config/package-info.java new file mode 100644 index 0000000..9085987 --- /dev/null +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/config/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package com.mattbertolini.spring.web.reactive.bind.config; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/package-info.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/package-info.java new file mode 100644 index 0000000..f3a18ef --- /dev/null +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package com.mattbertolini.spring.web.reactive.bind; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/CookieParameterRequestPropertyResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/CookieParameterRequestPropertyResolver.java index dd2b40d..a1478ca 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/CookieParameterRequestPropertyResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/CookieParameterRequestPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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,23 +20,24 @@ import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import org.springframework.http.HttpCookie; import org.springframework.lang.NonNull; -import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +import java.util.Objects; + public class CookieParameterRequestPropertyResolver implements RequestPropertyResolver { @Override - public boolean supports(@NonNull BindingProperty bindingProperty) { + public boolean supports(BindingProperty bindingProperty) { return bindingProperty.hasAnnotation(CookieParameter.class); } @NonNull @Override - public Mono resolve(@NonNull BindingProperty bindingProperty, @NonNull ServerWebExchange exchange) { + public Mono resolve(BindingProperty bindingProperty, ServerWebExchange exchange) { MultiValueMap cookies = exchange.getRequest().getCookies(); CookieParameter annotation = bindingProperty.getAnnotation(CookieParameter.class); - Assert.state(annotation != null, "No CookieParameter annotation found on type"); + Objects.requireNonNull(annotation, "No CookieParameter annotation found on type"); HttpCookie cookie = cookies.getFirst(annotation.value()); if (HttpCookie.class.isAssignableFrom(bindingProperty.getType())) { return Mono.justOrEmpty(cookie); diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterMapRequestPropertyResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterMapRequestPropertyResolver.java index ac94982..0e3dd38 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterMapRequestPropertyResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterMapRequestPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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. @@ -29,7 +29,7 @@ public class FormParameterMapRequestPropertyResolver implements RequestPropertyResolver { @Override - public boolean supports(@NonNull BindingProperty bindingProperty) { + public boolean supports(BindingProperty bindingProperty) { FormParameter annotation = bindingProperty.getAnnotation(FormParameter.class); return annotation != null && !StringUtils.hasText(annotation.value()) && Map.class.isAssignableFrom(bindingProperty.getType()); @@ -37,7 +37,7 @@ public boolean supports(@NonNull BindingProperty bindingProperty) { @Override @NonNull - public Mono resolve(@NonNull BindingProperty bindingProperty, @NonNull ServerWebExchange exchange) { + public Mono resolve(BindingProperty bindingProperty, ServerWebExchange exchange) { if (MultiValueMap.class.isAssignableFrom(bindingProperty.getType())) { return exchange.getFormData().map(Function.identity()); } diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolver.java index 749da08..4cd3ddf 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 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. @@ -21,26 +21,26 @@ import org.springframework.http.codec.multipart.FormFieldPart; import org.springframework.http.codec.multipart.Part; import org.springframework.lang.NonNull; -import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; public class FormParameterRequestPropertyResolver implements RequestPropertyResolver { @Override - public boolean supports(@NonNull BindingProperty bindingProperty) { + public boolean supports(BindingProperty bindingProperty) { FormParameter annotation = bindingProperty.getAnnotation(FormParameter.class); return annotation != null && StringUtils.hasText(annotation.value()); } @NonNull @Override - public Mono resolve(@NonNull BindingProperty bindingProperty, @NonNull ServerWebExchange exchange) { + public Mono resolve(BindingProperty bindingProperty, ServerWebExchange exchange) { FormParameter annotation = bindingProperty.getAnnotation(FormParameter.class); - Assert.state(annotation != null, "No FormParameter annotation found on type"); + Objects.requireNonNull(annotation, "No FormParameter annotation found on type"); return exchange.getMultipartData() .filter(multipartData -> multipartData.getFirst(annotation.value()) != null) .map(multipartData -> multipartData.get(annotation.value())) diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterMapRequestPropertyResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterMapRequestPropertyResolver.java index 42f3c47..1561e76 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterMapRequestPropertyResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterMapRequestPropertyResolver.java @@ -28,7 +28,7 @@ public class HeaderParameterMapRequestPropertyResolver implements RequestPropertyResolver { @Override - public boolean supports(@NonNull BindingProperty bindingProperty) { + public boolean supports(BindingProperty bindingProperty) { HeaderParameter annotation = bindingProperty.getAnnotation(HeaderParameter.class); return annotation != null && !StringUtils.hasText(annotation.value()) && Map.class.isAssignableFrom(bindingProperty.getType()); @@ -36,7 +36,7 @@ public boolean supports(@NonNull BindingProperty bindingProperty) { @Override @NonNull - public Mono resolve(@NonNull BindingProperty bindingProperty, @NonNull ServerWebExchange exchange) { + public Mono resolve(BindingProperty bindingProperty, ServerWebExchange exchange) { // HttpHeaders class extends from MultiValueMap if (MultiValueMap.class.isAssignableFrom(bindingProperty.getType())) { return Mono.just(exchange.getRequest().getHeaders()); diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterRequestPropertyResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterRequestPropertyResolver.java index 4d80411..b2a5960 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterRequestPropertyResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterRequestPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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,8 @@ import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +import java.util.Objects; + public class HeaderParameterRequestPropertyResolver implements RequestPropertyResolver { @Override public boolean supports(@NonNull BindingProperty bindingProperty) { @@ -34,10 +36,10 @@ public boolean supports(@NonNull BindingProperty bindingProperty) { @Override @NonNull - public Mono resolve(@NonNull BindingProperty bindingProperty, @NonNull ServerWebExchange request) { + public Mono resolve(BindingProperty bindingProperty, ServerWebExchange request) { HttpHeaders headers = request.getRequest().getHeaders(); HeaderParameter annotation = bindingProperty.getAnnotation(HeaderParameter.class); - Assert.state(annotation != null, "No HeaderParameter annotation found on type"); + Objects.requireNonNull(annotation, "No HeaderParameter annotation found on type"); return Mono.justOrEmpty(headers.get(annotation.value())); } } diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/PathParameterRequestPropertyResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/PathParameterRequestPropertyResolver.java index fc8ff2e..5728a6e 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/PathParameterRequestPropertyResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/PathParameterRequestPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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,7 +19,6 @@ import com.mattbertolini.spring.web.bind.annotation.PathParameter; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import org.springframework.lang.NonNull; -import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.server.ServerWebExchange; @@ -27,6 +26,7 @@ import java.util.Collections; import java.util.Map; +import java.util.Objects; public class PathParameterRequestPropertyResolver implements RequestPropertyResolver { @Override @@ -39,7 +39,7 @@ public boolean supports(@NonNull BindingProperty bindingProperty) { @Override public Mono resolve(@NonNull BindingProperty bindingProperty, @NonNull ServerWebExchange exchange) { PathParameter annotation = bindingProperty.getAnnotation(PathParameter.class); - Assert.state(annotation != null, "No PathParameter annotation found on type"); + Objects.requireNonNull(annotation, "No PathParameter annotation found on type"); Map pathVariables = exchange.getAttributeOrDefault(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Collections.emptyMap()); return Mono.justOrEmpty(pathVariables.get(annotation.value())); } diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestParameterRequestPropertyResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestParameterRequestPropertyResolver.java index d56bca2..5691b69 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestParameterRequestPropertyResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestParameterRequestPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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,13 @@ import com.mattbertolini.spring.web.bind.annotation.RequestParameter; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import org.springframework.lang.NonNull; -import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +import java.util.Objects; + public class RequestParameterRequestPropertyResolver implements RequestPropertyResolver { @Override public boolean supports(@NonNull BindingProperty bindingProperty) { @@ -36,7 +37,7 @@ public boolean supports(@NonNull BindingProperty bindingProperty) { @Override public Mono resolve(@NonNull BindingProperty bindingProperty, @NonNull ServerWebExchange serverWebExchange) { RequestParameter annotation = bindingProperty.getAnnotation(RequestParameter.class); - Assert.state(annotation != null, "No RequestParameter annotation found on type"); + Objects.requireNonNull(annotation, "No RequestParameter annotation found on type"); MultiValueMap queryParams = serverWebExchange.getRequest().getQueryParams(); return Mono.justOrEmpty(queryParams.get(annotation.value())); } diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestPropertyResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestPropertyResolver.java index ad9e7a3..d3ecd0e 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestPropertyResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestPropertyResolver.java @@ -27,6 +27,6 @@ */ public interface RequestPropertyResolver extends RequestPropertyResolverBase> { @Override - @NonNull - Mono resolve(@NonNull BindingProperty bindingProperty, @NonNull ServerWebExchange request); + @NonNull // Explicitly setting NonNull as we are overriding a Nullable parent method + Mono resolve(BindingProperty bindingProperty, ServerWebExchange request); } diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/SessionParameterRequestPropertyResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/SessionParameterRequestPropertyResolver.java index 0f97f5d..725eab3 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/SessionParameterRequestPropertyResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/SessionParameterRequestPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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,24 +19,24 @@ import com.mattbertolini.spring.web.bind.annotation.SessionParameter; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import org.springframework.lang.NonNull; -import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +import java.util.Objects; + public class SessionParameterRequestPropertyResolver implements RequestPropertyResolver { @Override - public boolean supports(@NonNull BindingProperty bindingProperty) { + public boolean supports(BindingProperty bindingProperty) { return bindingProperty.hasAnnotation(SessionParameter.class); } @NonNull @Override - public Mono resolve(@NonNull BindingProperty bindingProperty, @NonNull ServerWebExchange exchange) { + public Mono resolve(BindingProperty bindingProperty, ServerWebExchange exchange) { SessionParameter annotation = bindingProperty.getAnnotation(SessionParameter.class); - Assert.state(annotation != null, "No SessionParameter annotation found on type"); - //noinspection ReactiveStreamsNullableInLambdaInTransform + Objects.requireNonNull(annotation, "No SessionParameter annotation found on type"); return exchange.getSession() .filter(session -> session.getAttribute(annotation.value()) != null) - .map(session -> session.getAttribute(annotation.value())); + .mapNotNull(session -> session.getAttribute(annotation.value())); } } diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/package-info.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/package-info.java new file mode 100644 index 0000000..1cfb3d1 --- /dev/null +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package com.mattbertolini.spring.web.reactive.bind.resolver; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/PropertyResolverRegistry.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/PropertyResolverRegistry.java index 88dc526..5f96ef0 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/PropertyResolverRegistry.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/PropertyResolverRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/config/BinderConfiguration.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/config/BinderConfiguration.java index e331223..62c9416 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/config/BinderConfiguration.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/config/BinderConfiguration.java @@ -36,7 +36,7 @@ import com.mattbertolini.spring.web.servlet.mvc.bind.resolver.SessionParameterRequestPropertyResolver; import org.springframework.beans.factory.BeanInitializationException; import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; @@ -90,7 +90,8 @@ public Set getPackagesToScan() { } @Override - public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName) { + @Nullable + public Object postProcessBeforeInitialization(Object bean, String beanName) { if (!(bean instanceof RequestMappingHandlerAdapter adapter)) { return bean; } diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/config/package-info.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/config/package-info.java new file mode 100644 index 0000000..08c1dff --- /dev/null +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/config/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package com.mattbertolini.spring.web.servlet.mvc.bind.config; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/package-info.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/package-info.java index a382391..c479976 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/package-info.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/package-info.java @@ -1,4 +1,6 @@ @NonNullApi +@NonNullFields package com.mattbertolini.spring.web.servlet.mvc.bind; import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolver.java index 12fa0fe..ddf7071 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolver.java @@ -25,6 +25,8 @@ import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.util.WebUtils; +import java.util.Objects; + public class CookieParameterRequestPropertyResolver implements RequestPropertyResolver { @Override public boolean supports(BindingProperty bindingProperty) { @@ -37,7 +39,7 @@ public Object resolve(BindingProperty bindingProperty, NativeWebRequest request) HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); Assert.state(servletRequest != null, "A HttpServletRequest is required for this resolver and none found."); CookieParameter annotation = bindingProperty.getAnnotation(CookieParameter.class); - Assert.state(annotation != null, "No CookieParameter annotation found on type"); + Objects.requireNonNull(annotation, "No CookieParameter annotation found on type"); Cookie cookie = WebUtils.getCookie(servletRequest, annotation.value()); if (cookie == null) { return null; diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterRequestPropertyResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterRequestPropertyResolver.java index 10bd6cd..d95b75d 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterRequestPropertyResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterRequestPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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,22 +18,21 @@ import com.mattbertolini.spring.web.bind.annotation.FormParameter; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; -import org.springframework.lang.NonNull; -import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import java.util.Objects; + public class FormParameterRequestPropertyResolver extends RequestParameterRequestPropertyResolver { @Override - public boolean supports(@NonNull BindingProperty bindingProperty) { + public boolean supports(BindingProperty bindingProperty) { FormParameter annotation = bindingProperty.getAnnotation(FormParameter.class); return annotation != null && StringUtils.hasText(annotation.value()); } @Override - @NonNull - protected String getName(@NonNull BindingProperty bindingProperty) { + protected String getName(BindingProperty bindingProperty) { FormParameter annotation = bindingProperty.getAnnotation(FormParameter.class); - Assert.state(annotation != null, "No FormParameter annotation found on type"); + Objects.requireNonNull(annotation, "No FormParameter annotation found on type"); return annotation.value(); } } diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/HeaderParameterRequestPropertyResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/HeaderParameterRequestPropertyResolver.java index 7016e47..716dc49 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/HeaderParameterRequestPropertyResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/HeaderParameterRequestPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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,26 +18,30 @@ import com.mattbertolini.spring.web.bind.annotation.HeaderParameter; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; -import org.springframework.lang.NonNull; -import org.springframework.util.Assert; +import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; import org.springframework.web.context.request.NativeWebRequest; +import java.util.Objects; + /** + * Resolve HTTP header values + * * @see NativeWebRequest#getHeaderValues(String) */ public class HeaderParameterRequestPropertyResolver implements RequestPropertyResolver { @Override - public boolean supports(@NonNull BindingProperty bindingProperty) { + public boolean supports(BindingProperty bindingProperty) { HeaderParameter annotation = bindingProperty.getAnnotation(HeaderParameter.class); return annotation != null && StringUtils.hasText(annotation.value()); } @Override - public Object resolve(@NonNull BindingProperty bindingProperty, @NonNull NativeWebRequest request) { + @Nullable + public Object resolve(BindingProperty bindingProperty, NativeWebRequest request) { HeaderParameter annotation = bindingProperty.getAnnotation(HeaderParameter.class); - Assert.state(annotation != null, "No HeaderParameter annotation found on type"); + Objects.requireNonNull(annotation, "No HeaderParameter annotation found on type"); return request.getHeaderValues(annotation.value()); } } diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/PathParameterRequestPropertyResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/PathParameterRequestPropertyResolver.java index 9deef1d..fb6db43 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/PathParameterRequestPropertyResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/PathParameterRequestPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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,28 +18,29 @@ import com.mattbertolini.spring.web.bind.annotation.PathParameter; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; -import org.springframework.lang.NonNull; -import org.springframework.util.Assert; +import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.servlet.HandlerMapping; import java.util.Map; +import java.util.Objects; public class PathParameterRequestPropertyResolver implements RequestPropertyResolver { @Override - public boolean supports(@NonNull BindingProperty bindingProperty) { + public boolean supports(BindingProperty bindingProperty) { PathParameter annotation = bindingProperty.getAnnotation(PathParameter.class); return annotation != null && StringUtils.hasText(annotation.value()); } @SuppressWarnings("unchecked") @Override - public Object resolve(@NonNull BindingProperty bindingProperty, @NonNull NativeWebRequest request) { + @Nullable + public Object resolve(BindingProperty bindingProperty, NativeWebRequest request) { PathParameter annotation = bindingProperty.getAnnotation(PathParameter.class); - Assert.state(annotation != null, "No PathParameter annotation found on type"); + Objects.requireNonNull(annotation, "No PathParameter annotation found on type"); Map uriTemplateVariables = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); if (uriTemplateVariables == null) { diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestContextRequestPropertyResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestContextRequestPropertyResolver.java index a45064a..5e121ec 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestContextRequestPropertyResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestContextRequestPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import org.springframework.http.HttpMethod; -import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.support.RequestContextUtils; @@ -33,7 +33,7 @@ public class RequestContextRequestPropertyResolver implements RequestPropertyResolver { @Override - public boolean supports(@NonNull BindingProperty bindingProperty) { + public boolean supports(BindingProperty bindingProperty) { Class type = bindingProperty.getType(); return bindingProperty.hasAnnotation(RequestContext.class) && ( WebRequest.class.isAssignableFrom(type) || @@ -47,7 +47,8 @@ public boolean supports(@NonNull BindingProperty bindingProperty) { } @Override - public Object resolve(@NonNull BindingProperty bindingProperty, @NonNull NativeWebRequest request) { + @Nullable + public Object resolve(BindingProperty bindingProperty, NativeWebRequest request) { Class type = bindingProperty.getType(); if (WebRequest.class.isAssignableFrom(type)) { return request; diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolver.java index 240201d..7c08f48 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolver.java @@ -34,10 +34,10 @@ import org.springframework.web.multipart.support.MultipartResolutionDelegate; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.Map; public class RequestParameterMapRequestPropertyResolver implements RequestPropertyResolver { @@ -67,7 +67,7 @@ public Object resolve(@NonNull BindingProperty bindingProperty, @NonNull NativeW Map parameterMap = request.getParameterMap(); MultiValueMap ret = new LinkedMultiValueMap<>(parameterMap.size()); for (Map.Entry entry : parameterMap.entrySet()) { - ret.put(entry.getKey(), new LinkedList<>(Arrays.asList(entry.getValue()))); + ret.put(entry.getKey(), new ArrayList<>(Arrays.asList(entry.getValue()))); } return ret; } @@ -107,7 +107,7 @@ public Object resolve(@NonNull BindingProperty bindingProperty, @NonNull NativeW } } - private LinkedHashMap resolveServletRequestPartsToMap(NativeWebRequest request) { + private Map resolveServletRequestPartsToMap(NativeWebRequest request) { try { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) { diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolver.java index bb096cc..eb78acb 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 the original author or authors. + * Copyright 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. @@ -28,6 +28,8 @@ import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.multipart.support.MultipartResolutionDelegate; +import java.util.Objects; + public class RequestParameterRequestPropertyResolver extends AbstractNamedRequestPropertyResolver implements RequestPropertyResolver { @@ -39,7 +41,7 @@ public boolean supports(BindingProperty bindingProperty) { @Override @Nullable - protected Object resolveWithName(@NonNull BindingProperty bindingProperty, String name, @NonNull NativeWebRequest request) { + protected Object resolveWithName(BindingProperty bindingProperty, String name, NativeWebRequest request) { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); if (servletRequest != null) { try { @@ -58,7 +60,7 @@ protected Object resolveWithName(@NonNull BindingProperty bindingProperty, Strin @Override protected String getName(BindingProperty bindingProperty) { RequestParameter annotation = bindingProperty.getAnnotation(RequestParameter.class); - Assert.state(annotation != null, "No RequestParameter annotation found on type"); + Objects.requireNonNull(annotation, "No RequestParameter annotation found on type"); return annotation.value(); } } diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/SessionParameterRequestPropertyResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/SessionParameterRequestPropertyResolver.java index 7f3c7f4..9d58123 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/SessionParameterRequestPropertyResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/SessionParameterRequestPropertyResolver.java @@ -18,12 +18,12 @@ import com.mattbertolini.spring.web.bind.annotation.SessionParameter; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; -import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; -import org.springframework.util.Assert; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.RequestAttributes; +import java.util.Objects; + public class SessionParameterRequestPropertyResolver implements RequestPropertyResolver { @Override public boolean supports(BindingProperty bindingProperty) { @@ -34,7 +34,7 @@ public boolean supports(BindingProperty bindingProperty) { @Nullable public Object resolve(BindingProperty bindingProperty, NativeWebRequest request) { SessionParameter annotation = bindingProperty.getAnnotation(SessionParameter.class); - Assert.state(annotation != null, "No SessionParameter annotation found on type"); + Objects.requireNonNull(annotation, "No SessionParameter annotation found on type"); return request.getAttribute(annotation.value(), RequestAttributes.SCOPE_SESSION); } } diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/package-info.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/package-info.java index 3319042..1bbe7ff 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/package-info.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/package-info.java @@ -1,4 +1,6 @@ @NonNullApi +@NonNullFields package com.mattbertolini.spring.web.servlet.mvc.bind.resolver; import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; From 4b365aa34adba8add87d322951f429dd50ee7595 Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Wed, 17 Jul 2024 21:18:37 -0400 Subject: [PATCH 05/33] More NullAway support --- .../buildlogic/error-prone.gradle.kts | 7 +++++-- docs/build.gradle.kts | 3 ++- .../web/bind/docs/CustomRequestBean.java | 19 +++++++++++++++++++ .../spring/web/bind/docs/NestedBean.java | 3 +++ .../build.gradle.kts | 1 + .../AbstractPropertyResolverRegistryTest.java | 3 +++ .../bind/introspect/BindingPropertyTest.java | 11 ++++++++++- ...tAnnotatedRequestBeanIntrospectorTest.java | 14 ++++++++++++-- ...tractNamedRequestPropertyResolverTest.java | 3 +++ ...anParameterMethodArgumentResolverTest.java | 11 +++++++++-- .../web/reactive/bind/MockBindingContext.java | 6 +++++- .../bind/MockWebExchangeDataBinder.java | 1 + ...eParameterRequestPropertyResolverTest.java | 7 +++++++ ...mParameterRequestPropertyResolverTest.java | 11 +++++++++++ ...rameterMapRequestPropertyResolverTest.java | 13 +++++++++++++ ...tParameterRequestPropertyResolverTest.java | 9 +++++++++ ...nParameterRequestPropertyResolverTest.java | 5 +++++ .../servlet/mvc/bind/MockWebDataBinder.java | 1 + .../WebFluxBinderAutoConfiguration.java | 3 ++- .../WebMvcBinderAutoConfiguration.java | 3 ++- 20 files changed, 123 insertions(+), 11 deletions(-) diff --git a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/error-prone.gradle.kts b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/error-prone.gradle.kts index 6fa68c7..debf80f 100644 --- a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/error-prone.gradle.kts +++ b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/error-prone.gradle.kts @@ -16,8 +16,11 @@ val nullAwayAnnotations = libsCatalog.findLibrary("nullAwayAnnotations").orElseT dependencies { errorprone(errorProneCore) errorprone(nullAway) - add("compileOnly", errorProneAnnotations) - add("compileOnly", nullAwayAnnotations) +} + +project.extensions.getByType().configureEach { + dependencies.add(compileOnlyConfigurationName, errorProneAnnotations) + dependencies.add(compileOnlyConfigurationName, nullAwayAnnotations) } tasks.withType().configureEach { diff --git a/docs/build.gradle.kts b/docs/build.gradle.kts index b318e2b..612632c 100644 --- a/docs/build.gradle.kts +++ b/docs/build.gradle.kts @@ -20,9 +20,10 @@ dependencies { testImplementation(libs.assertJCore) testImplementation(libs.mockitoCore) testImplementation(libs.springTest) - testCompileOnly(libs.hamcrest) // Needed for Spring mock MVC matchers testImplementation(libs.jakartaWebsocketApi) testImplementation(libs.jakartaWebsocketClientApi) + testCompileOnly(libs.hamcrest) // Needed for Spring mock MVC matchers + testCompileOnly(libs.findbugsJsr305) } tasks.named("asciidoctor").configure { diff --git a/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/CustomRequestBean.java b/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/CustomRequestBean.java index 014bbd6..5b84e05 100644 --- a/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/CustomRequestBean.java +++ b/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/CustomRequestBean.java @@ -25,6 +25,7 @@ import com.mattbertolini.spring.web.bind.annotation.RequestContext; import com.mattbertolini.spring.web.bind.annotation.RequestParameter; import com.mattbertolini.spring.web.bind.annotation.SessionParameter; +import org.springframework.lang.Nullable; import java.time.ZoneId; import java.util.Locale; @@ -37,6 +38,7 @@ public class CustomRequestBean { // end::class[] // Query parameters + @Nullable // tag::queryParam[] @RequestParameter("different_name") private String queryParam; @@ -44,6 +46,7 @@ public class CustomRequestBean { // end::queryParam[] // Form data + @Nullable // tag::formParam[] @FormParameter("form_data") private String formData; @@ -51,6 +54,7 @@ public class CustomRequestBean { // end::formParam[] // HTTP headers + @Nullable // tag::headerParam[] @HeaderParameter("X-Custom-Header") private String headerValues; @@ -58,6 +62,7 @@ public class CustomRequestBean { // end::headerParam[] // Spring MVC/WebFlux path variables + @Nullable // tag::pathParam[] @PathParameter("pathParam") private Integer pathParam; @@ -65,6 +70,7 @@ public class CustomRequestBean { // end::pathParam[] // HTTP cookie values + @Nullable // tag::cookieParam[] @CookieParameter("cookie_value") private String cookieValue; @@ -72,6 +78,7 @@ public class CustomRequestBean { // end::cookieParam[] // HTTP session attributes + @Nullable // tag::sessionParam[] @SessionParameter("sessionAttribute") private String sessionAttribute; @@ -79,16 +86,20 @@ public class CustomRequestBean { // end::sessionParam[] // Spring derived request scoped data like locale and time zone + @Nullable @RequestContext private Locale locale; + @Nullable @RequestContext private ZoneId timeZone; // A nested Java bean with additional annotated properties + @Nullable @BeanParameter private NestedBean nestedBean; + @Nullable // tag::queryParam[] public String getQueryParam() { return queryParam; @@ -99,6 +110,7 @@ public void setQueryParam(String queryParam) { } // end::queryParam[] + @Nullable // tag::formParam[] public String getFormData() { return formData; @@ -109,6 +121,7 @@ public void setFormData(String formData) { } // end::formParam[] + @Nullable // tag::headerParam[] public String getHeaderValues() { return headerValues; @@ -119,6 +132,7 @@ public void setHeaderValues(String headerValues) { } // end::headerParam[] + @Nullable // tag::pathParam[] public Integer getPathParam() { return pathParam; @@ -129,6 +143,7 @@ public void setPathParam(Integer pathParam) { } // end::pathParam[] + @Nullable // tag::cookieParam[] public String getCookieValue() { return cookieValue; @@ -139,6 +154,7 @@ public void setCookieValue(String cookieValue) { } // end::cookieParam[] + @Nullable // tag::sessionParam[] public String getSessionAttribute() { return sessionAttribute; @@ -149,6 +165,7 @@ public void setSessionAttribute(String sessionAttribute) { } // end::sessionParam[] + @Nullable public Locale getLocale() { return locale; } @@ -157,6 +174,7 @@ public void setLocale(Locale locale) { this.locale = locale; } + @Nullable public ZoneId getTimeZone() { return timeZone; } @@ -165,6 +183,7 @@ public void setTimeZone(ZoneId timeZone) { this.timeZone = timeZone; } + @Nullable public NestedBean getNestedBean() { return nestedBean; } diff --git a/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/NestedBean.java b/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/NestedBean.java index f56d300..2f76d52 100644 --- a/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/NestedBean.java +++ b/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/NestedBean.java @@ -17,11 +17,14 @@ package com.mattbertolini.spring.web.bind.docs; import com.mattbertolini.spring.web.bind.annotation.RequestParameter; +import org.springframework.lang.Nullable; public class NestedBean { + @Nullable @RequestParameter("nested_request_param") private String nestedRequestParameter; + @Nullable public String getNestedRequestParameter() { return nestedRequestParameter; } diff --git a/spring-annotated-data-binder-core/build.gradle.kts b/spring-annotated-data-binder-core/build.gradle.kts index 9c88fce..df52b27 100644 --- a/spring-annotated-data-binder-core/build.gradle.kts +++ b/spring-annotated-data-binder-core/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { testImplementation(libs.mockitoCore) testImplementation(libs.springTest) testImplementation(libs.equalsVerifier) + testCompileOnly(libs.findbugsJsr305) } tasks.named("jar").configure { diff --git a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/AbstractPropertyResolverRegistryTest.java b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/AbstractPropertyResolverRegistryTest.java index 494e68e..75b0704 100644 --- a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/AbstractPropertyResolverRegistryTest.java +++ b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/AbstractPropertyResolverRegistryTest.java @@ -20,6 +20,7 @@ import com.mattbertolini.spring.web.bind.resolver.RequestPropertyResolverBase; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.lang.Nullable; import java.beans.PropertyDescriptor; import java.util.Collections; @@ -100,8 +101,10 @@ private static class TestingRegistry extends AbstractPropertyResolverRegistry annotationType) { } @Override - public boolean supports(@NonNull BindingProperty bindingProperty) { + public boolean supports(BindingProperty bindingProperty) { return bindingProperty.hasAnnotation(annotationType); } @Override - public Object resolve(@NonNull BindingProperty bindingProperty, @NonNull Void request) { + @Nullable + public Object resolve(BindingProperty bindingProperty, Void request) { // Don't need to worry about this method. No used in the introspector. return null; } @@ -93,9 +95,11 @@ public FakeRegistry() { @SuppressWarnings("unused") private static class SimpleType { + @Nullable @RequestParameter("data_param") private String data; + @Nullable public String getData() { return data; } @@ -107,9 +111,11 @@ public void setData(String data) { @SuppressWarnings("unused") private static class OuterBean { + @Nullable @BeanParameter private InnerBean innerBean; + @Nullable public InnerBean getInnerBean() { return innerBean; } @@ -121,9 +127,11 @@ public void setInnerBean(InnerBean innerBean) { @SuppressWarnings("unused") private static class InnerBean { + @Nullable @RequestParameter("inner_param") private String inner; + @Nullable public String getInner() { return inner; } @@ -135,9 +143,11 @@ public void setInner(String inner) { @SuppressWarnings("unused") private static class CircularReference { + @Nullable @BeanParameter private CircularReference circularReference; + @Nullable public CircularReference getCircularReference() { return circularReference; } diff --git a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/resolver/AbstractNamedRequestPropertyResolverTest.java b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/resolver/AbstractNamedRequestPropertyResolverTest.java index eef0b9a..94476ed 100644 --- a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/resolver/AbstractNamedRequestPropertyResolverTest.java +++ b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/resolver/AbstractNamedRequestPropertyResolverTest.java @@ -19,6 +19,7 @@ import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import org.junit.jupiter.api.Test; import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import java.beans.PropertyDescriptor; @@ -62,8 +63,10 @@ public boolean supports(@NonNull BindingProperty bindingProperty) { @SuppressWarnings("unused") private static class TestingBean { + @Nullable private String property; + @Nullable public String getProperty() { return property; } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java index 3578a75..aacee29 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java @@ -233,7 +233,9 @@ private MethodParameter createMethodParameter(String anAnnotatedMethod, Class } private static class MockRequestPropertyResolver implements RequestPropertyResolver { + @Nullable private final Object value; + @Nullable private final RuntimeException exception; private MockRequestPropertyResolver(@Nullable T value, @Nullable RuntimeException exception) { @@ -242,14 +244,14 @@ private MockRequestPropertyResolver(@Nullable T value, @Nullable RuntimeExce } @Override - public boolean supports(@NonNull BindingProperty bindingProperty) { + public boolean supports(BindingProperty bindingProperty) { // Not used in this test return true; } @NonNull @Override - public Mono resolve(@NonNull BindingProperty bindingProperty, @NonNull ServerWebExchange exchange) { + public Mono resolve(BindingProperty bindingProperty, ServerWebExchange exchange) { if (exception != null) { throw exception; } @@ -306,10 +308,13 @@ public void withBindingResult(@BeanParameter @Validated ABeanClass aBeanClass, B } private static class ABeanClass { + @Nullable private String propertyOne; + @Nullable private Integer propertyTwo; + @Nullable public String getPropertyOne() { return propertyOne; } @@ -318,6 +323,8 @@ public void setPropertyOne(String propertyOne) { this.propertyOne = propertyOne; } + @SuppressWarnings("unused") + @Nullable public Integer getPropertyTwo() { return propertyTwo; } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockBindingContext.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockBindingContext.java index d55ed65..eb75abf 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockBindingContext.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockBindingContext.java @@ -1,5 +1,6 @@ package com.mattbertolini.spring.web.reactive.bind; +import com.uber.nullaway.annotations.Initializer; import org.springframework.core.ResolvableType; import org.springframework.format.support.FormattingConversionServiceFactoryBean; import org.springframework.lang.Nullable; @@ -13,10 +14,12 @@ public class MockBindingContext extends BindingContext { private final BindingAwareConcurrentModel model = new BindingAwareConcurrentModel(); private MockWebExchangeDataBinder dataBinder; + @Nullable private BindingResult bindingResult; @Override - public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, Object target, String name) { + @Initializer + public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, @Nullable Object target, String name) { dataBinder = new MockWebExchangeDataBinder(target); if (bindingResult != null) { @@ -31,6 +34,7 @@ public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, Object } @Override + @Initializer public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, @Nullable Object target, String name, ResolvableType targetType) { dataBinder = new MockWebExchangeDataBinder(target); diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java index 293a137..a682e6d 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java @@ -15,6 +15,7 @@ public class MockWebExchangeDataBinder extends WebExchangeDataBinder { private boolean validateInvoked = true; private PropertyValues pvs; private List validationHints; + @Nullable private BindingResult bindingResult; public MockWebExchangeDataBinder(@Nullable Object target) { diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/CookieParameterRequestPropertyResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/CookieParameterRequestPropertyResolverTest.java index 5e3a698..ade20f3 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/CookieParameterRequestPropertyResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/CookieParameterRequestPropertyResolverTest.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.HttpCookie; +import org.springframework.lang.Nullable; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import reactor.core.publisher.Mono; @@ -105,14 +106,18 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @CookieParameter("the_cookie") private String annotated; + @Nullable private String notAnnotated; + @Nullable @CookieParameter("the_cookie") private HttpCookie cookieObject; + @Nullable public String getAnnotated() { return annotated; } @@ -121,6 +126,7 @@ public void setAnnotated(String annotated) { this.annotated = annotated; } + @Nullable public String getNotAnnotated() { return notAnnotated; } @@ -129,6 +135,7 @@ public void setNotAnnotated(String notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public HttpCookie getCookieObject() { return cookieObject; } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolverTest.java index d028bf8..68664b9 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolverTest.java @@ -31,6 +31,7 @@ import org.springframework.http.codec.multipart.FilePart; import org.springframework.http.codec.multipart.MultipartHttpMessageWriter; import org.springframework.http.codec.multipart.Part; +import org.springframework.lang.Nullable; import org.springframework.mock.http.client.reactive.MockClientHttpRequest; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; @@ -215,20 +216,26 @@ private String partContentToString(Part part) { @SuppressWarnings("unused") private static class TestingBean { + @Nullable @FormParameter("testing") private String annotated; + @Nullable private String notAnnotated; + @Nullable @FormParameter("multiple_values") private List multipleValues; + @Nullable @FormParameter private String missingValue; + @Nullable @FormParameter("file") private Part multipartValue; + @Nullable public String getAnnotated() { return annotated; } @@ -237,6 +244,7 @@ public void setAnnotated(String annotated) { this.annotated = annotated; } + @Nullable public String getNotAnnotated() { return notAnnotated; } @@ -245,6 +253,7 @@ public void setNotAnnotated(String notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public List getMultipleValues() { return multipleValues; } @@ -253,6 +262,7 @@ public void setMultipleValues(List multipleValues) { this.multipleValues = multipleValues; } + @Nullable public String getMissingValue() { return missingValue; } @@ -261,6 +271,7 @@ public void setMissingValue(String missingValue) { this.missingValue = missingValue; } + @Nullable public Part getMultipartValue() { return multipartValue; } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterMapRequestPropertyResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterMapRequestPropertyResolverTest.java index cafd7c4..14c2497 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterMapRequestPropertyResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterMapRequestPropertyResolverTest.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; +import org.springframework.lang.Nullable; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.util.MultiValueMap; @@ -121,23 +122,30 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @HeaderParameter private Map annotated; + @Nullable private Map notAnnotated; + @Nullable @HeaderParameter private MultiValueMap multivalue; + @Nullable @HeaderParameter private HttpHeaders httpHeaders; + @Nullable @HeaderParameter("irrelevant") private String withValue; + @Nullable @HeaderParameter private String notAMap; + @Nullable public Map getAnnotated() { return annotated; } @@ -146,6 +154,7 @@ public void setAnnotated(Map annotated) { this.annotated = annotated; } + @Nullable public Map getNotAnnotated() { return notAnnotated; } @@ -154,6 +163,7 @@ public void setNotAnnotated(Map notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public MultiValueMap getMultivalue() { return multivalue; } @@ -162,6 +172,7 @@ public void setMultivalue(MultiValueMap multivalue) { this.multivalue = multivalue; } + @Nullable public HttpHeaders getHttpHeaders() { return httpHeaders; } @@ -170,6 +181,7 @@ public void setHttpHeaders(HttpHeaders httpHeaders) { this.httpHeaders = httpHeaders; } + @Nullable public String getWithValue() { return withValue; } @@ -178,6 +190,7 @@ public void setWithValue(String withValue) { this.withValue = withValue; } + @Nullable public String getNotAMap() { return notAMap; } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestParameterRequestPropertyResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestParameterRequestPropertyResolverTest.java index 6335480..499106b 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestParameterRequestPropertyResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestParameterRequestPropertyResolverTest.java @@ -20,6 +20,7 @@ import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.lang.Nullable; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import reactor.core.publisher.Mono; @@ -112,17 +113,22 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @RequestParameter("testing") private String annotated; + @Nullable private String notAnnotated; + @Nullable @RequestParameter private String missingValue; + @Nullable @RequestParameter("multiple_values") private List multipleValues; + @Nullable public String getAnnotated() { return annotated; } @@ -131,6 +137,7 @@ public void setAnnotated(String annotated) { this.annotated = annotated; } + @Nullable public String getNotAnnotated() { return notAnnotated; } @@ -139,6 +146,7 @@ public void setNotAnnotated(String notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public String getMissingValue() { return missingValue; } @@ -147,6 +155,7 @@ public void setMissingValue(String missingValue) { this.missingValue = missingValue; } + @Nullable public List getMultipleValues() { return multipleValues; } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/SessionParameterRequestPropertyResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/SessionParameterRequestPropertyResolverTest.java index afcf83f..289af60 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/SessionParameterRequestPropertyResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/SessionParameterRequestPropertyResolverTest.java @@ -20,6 +20,7 @@ import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.lang.Nullable; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.mock.web.server.MockWebSession; @@ -102,11 +103,14 @@ private BindingProperty bindingProperty(String propertyName) throws Introspectio @SuppressWarnings("unused") private static class TestingBean { + @Nullable @SessionParameter("sessionKey") private String annotated; + @Nullable private String notAnnotated; + @Nullable public String getAnnotated() { return annotated; } @@ -115,6 +119,7 @@ public void setAnnotated(String annotated) { this.annotated = annotated; } + @Nullable public String getNotAnnotated() { return notAnnotated; } diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinder.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinder.java index 3217523..a152e97 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinder.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinder.java @@ -13,6 +13,7 @@ public class MockWebDataBinder extends WebDataBinder { private boolean validateInvoked = true; private PropertyValues pvs; private List validationHints; + @Nullable private BindingResult bindingResult; public MockWebDataBinder(@Nullable Object target) { diff --git a/webflux-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/reactive/bind/autoconfigure/WebFluxBinderAutoConfiguration.java b/webflux-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/reactive/bind/autoconfigure/WebFluxBinderAutoConfiguration.java index ca58901..395bf9e 100644 --- a/webflux-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/reactive/bind/autoconfigure/WebFluxBinderAutoConfiguration.java +++ b/webflux-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/reactive/bind/autoconfigure/WebFluxBinderAutoConfiguration.java @@ -29,6 +29,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Role; +import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; @@ -39,7 +40,7 @@ @ConditionalOnMissingBean(BinderConfiguration.class) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) public class WebFluxBinderAutoConfiguration { - private final List packagesToScan = new LinkedList<>(); + private final List packagesToScan = new ArrayList<>(); private final Set customResolvers = new LinkedHashSet<>(); private final Set propertyResolverRegistries = new LinkedHashSet<>(); diff --git a/webmvc-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/autoconfigure/WebMvcBinderAutoConfiguration.java b/webmvc-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/autoconfigure/WebMvcBinderAutoConfiguration.java index a4be734..4dd2976 100644 --- a/webmvc-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/autoconfigure/WebMvcBinderAutoConfiguration.java +++ b/webmvc-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/autoconfigure/WebMvcBinderAutoConfiguration.java @@ -29,6 +29,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Role; +import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; @@ -39,7 +40,7 @@ @ConditionalOnMissingBean(BinderConfiguration.class) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) public class WebMvcBinderAutoConfiguration { - private final List packagesToScan = new LinkedList<>(); + private final List packagesToScan = new ArrayList<>(); private final Set customResolvers = new LinkedHashSet<>(); private final Set propertyResolverRegistries = new LinkedHashSet<>(); From 44739b33e2daf6c4ac67ce410eb648ec0e78f86b Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Wed, 17 Jul 2024 21:20:32 -0400 Subject: [PATCH 06/33] Gradle polish --- .../com/mattbertolini/buildlogic/java-conventions.gradle.kts | 2 +- .../com/mattbertolini/buildlogic/java-library.gradle.kts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts index 5e8ee91..82c5122 100644 --- a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts @@ -61,4 +61,4 @@ tasks.named("jacocoTestReport").configure { } } -tasks.test { finalizedBy(tasks.jacocoTestReport) } +tasks.named("test").configure { finalizedBy(tasks.jacocoTestReport) } diff --git a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-library.gradle.kts b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-library.gradle.kts index 96acfd0..271e920 100644 --- a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-library.gradle.kts +++ b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-library.gradle.kts @@ -2,6 +2,5 @@ package com.mattbertolini.buildlogic plugins { `java-library` + id("com.mattbertolini.buildlogic.java-conventions") } - -apply(plugin = "com.mattbertolini.buildlogic.java-conventions") From 4242f9186b84bd5260bb76138269fefc03af8070 Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Wed, 17 Jul 2024 21:22:17 -0400 Subject: [PATCH 07/33] Gradle polish --- .../com/mattbertolini/buildlogic/java-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts index 82c5122..0edb384 100644 --- a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts @@ -61,4 +61,4 @@ tasks.named("jacocoTestReport").configure { } } -tasks.named("test").configure { finalizedBy(tasks.jacocoTestReport) } +tasks.named("test").configure { finalizedBy(tasks.named("jacocoTestReport")) } From 1d6c55fcb3e3c457e26d0416ef8ff2b464043f0c Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Wed, 17 Jul 2024 21:22:35 -0400 Subject: [PATCH 08/33] Gradle polish --- .../com/mattbertolini/buildlogic/java-conventions.gradle.kts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts index 0edb384..45e280a 100644 --- a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts @@ -61,4 +61,6 @@ tasks.named("jacocoTestReport").configure { } } -tasks.named("test").configure { finalizedBy(tasks.named("jacocoTestReport")) } +tasks.named("test").configure { + finalizedBy(tasks.named("jacocoTestReport")) +} From 9c5bd1f11bd0e04395dea27ea047d37bcad2b256 Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Wed, 17 Jul 2024 21:29:53 -0400 Subject: [PATCH 09/33] Gradle polish --- .../com/mattbertolini/buildlogic/java-conventions.gradle.kts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts index 45e280a..a7953b1 100644 --- a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts @@ -6,8 +6,6 @@ plugins { id("com.mattbertolini.buildlogic.error-prone") } -val versionCatalog = versionCatalogs.named("libs") - java { toolchain { languageVersion.set(JavaLanguageVersion.of(JavaVersion.VERSION_17.majorVersion)) @@ -48,8 +46,9 @@ tasks.named("javadoc").configure { } } +val versionCatalog = versionCatalogs.named("libs") val jacocoVersion: String = versionCatalog.findVersion("jacoco").orElseThrow().toString() -configure { +jacoco { toolVersion = jacocoVersion } From 3569d9b176bb84e4cf01a62fcb3fc3f44ac45030 Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Wed, 17 Jul 2024 21:31:02 -0400 Subject: [PATCH 10/33] Gradle polish --- .../com/mattbertolini/buildlogic/java-conventions.gradle.kts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts index a7953b1..4230816 100644 --- a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts @@ -6,6 +6,8 @@ plugins { id("com.mattbertolini.buildlogic.error-prone") } +val versionCatalog = versionCatalogs.named("libs") + java { toolchain { languageVersion.set(JavaLanguageVersion.of(JavaVersion.VERSION_17.majorVersion)) @@ -46,7 +48,6 @@ tasks.named("javadoc").configure { } } -val versionCatalog = versionCatalogs.named("libs") val jacocoVersion: String = versionCatalog.findVersion("jacoco").orElseThrow().toString() jacoco { toolVersion = jacocoVersion From 7ada3ce041b643a4448e94ccc67dad3f00524fb6 Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Wed, 17 Jul 2024 21:39:25 -0400 Subject: [PATCH 11/33] More NullAway support --- .../web/servlet/mvc/bind/PropertyResolverRegistryTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/PropertyResolverRegistryTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/PropertyResolverRegistryTest.java index bc4964b..c96ea78 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/PropertyResolverRegistryTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/PropertyResolverRegistryTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.web.context.request.NativeWebRequest; import java.util.Collections; @@ -46,12 +47,13 @@ void addsResolversFromRegistry() { private static class FakeResolver implements RequestPropertyResolver { @Override - public boolean supports(@NonNull BindingProperty bindingProperty) { + public boolean supports(BindingProperty bindingProperty) { return false; } @Override - public Object resolve(@NonNull BindingProperty bindingProperty, @NonNull NativeWebRequest request) { + @Nullable + public Object resolve(BindingProperty bindingProperty, NativeWebRequest request) { return null; } } From d03ce0a4f56b87e86d08632bbe2262f8cb7991b0 Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Thu, 18 Jul 2024 21:34:40 -0400 Subject: [PATCH 12/33] More NullAway support --- build.gradle.kts | 2 -- .../buildlogic/java-conventions.gradle.kts | 1 + .../bind/MockWebExchangeDataBinder.java | 4 ++++ ...rameterMapRequestPropertyResolverTest.java | 11 +++++++++++ ...rParameterRequestPropertyResolverTest.java | 9 +++++++++ ...rameterMapRequestPropertyResolverTest.java | 9 +++++++++ ...hParameterRequestPropertyResolverTest.java | 7 +++++++ ...equestBodyRequestPropertyResolverTest.java | 6 +++++- ...estContextRequestPropertyResolverTest.java | 19 +++++++++++++++++++ ...rameterMapRequestPropertyResolverTest.java | 11 +++++++++++ ...anParameterMethodArgumentResolverTest.java | 11 +++++++++-- .../servlet/mvc/bind/MockWebDataBinder.java | 6 ++++++ ...eParameterRequestPropertyResolverTest.java | 7 +++++++ 13 files changed, 98 insertions(+), 5 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7cfa1e7..2deccc7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,8 +6,6 @@ plugins { } allprojects { - apply(plugin = "com.mattbertolini.buildlogic.project-conventions") - group = "com.mattbertolini" version = "0.7.0-SNAPSHOT" } diff --git a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts index 4230816..9724867 100644 --- a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/java-conventions.gradle.kts @@ -3,6 +3,7 @@ package com.mattbertolini.buildlogic plugins { java jacoco + id("com.mattbertolini.buildlogic.project-conventions") id("com.mattbertolini.buildlogic.error-prone") } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java index a682e6d..0334793 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java @@ -1,5 +1,6 @@ package com.mattbertolini.spring.web.reactive.bind; +import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.PropertyValues; import org.springframework.lang.Nullable; import org.springframework.validation.BindingResult; @@ -7,6 +8,7 @@ import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -20,6 +22,8 @@ public class MockWebExchangeDataBinder extends WebExchangeDataBinder { public MockWebExchangeDataBinder(@Nullable Object target) { super(target); + pvs = new MutablePropertyValues(); + validationHints = new ArrayList<>(); } @Override diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterMapRequestPropertyResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterMapRequestPropertyResolverTest.java index 79864f2..b78388c 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterMapRequestPropertyResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterMapRequestPropertyResolverTest.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.util.MultiValueMap; @@ -104,20 +105,26 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @FormParameter private Map annotated; + @Nullable private Map notAnnotated; + @Nullable @FormParameter private MultiValueMap multivalue; + @Nullable @FormParameter("name") private String withName; + @Nullable @FormParameter private String notAMap; + @Nullable public Map getAnnotated() { return annotated; } @@ -126,6 +133,7 @@ public void setAnnotated(Map annotated) { this.annotated = annotated; } + @Nullable public Map getNotAnnotated() { return notAnnotated; } @@ -134,6 +142,7 @@ public void setNotAnnotated(Map notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public MultiValueMap getMultivalue() { return multivalue; } @@ -142,6 +151,7 @@ public void setMultivalue(MultiValueMap multivalue) { this.multivalue = multivalue; } + @Nullable public String getWithName() { return withName; } @@ -150,6 +160,7 @@ public void setWithName(String withName) { this.withName = withName; } + @Nullable public String getNotAMap() { return notAMap; } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterRequestPropertyResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterRequestPropertyResolverTest.java index 062525a..8621a9e 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterRequestPropertyResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterRequestPropertyResolverTest.java @@ -20,6 +20,7 @@ import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.lang.Nullable; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import reactor.core.publisher.Mono; @@ -114,17 +115,22 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @HeaderParameter("X-HeaderName") private String annotated; + @Nullable private String notAnnotated; + @Nullable @HeaderParameter private String missingValue; + @Nullable @HeaderParameter("X-Multiple") private List multipleValues; + @Nullable public String getAnnotated() { return annotated; } @@ -133,6 +139,7 @@ public void setAnnotated(String annotated) { this.annotated = annotated; } + @Nullable public String getNotAnnotated() { return notAnnotated; } @@ -141,6 +148,7 @@ public void setNotAnnotated(String notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public String getMissingValue() { return missingValue; } @@ -149,6 +157,7 @@ public void setMissingValue(String missingValue) { this.missingValue = missingValue; } + @Nullable public List getMultipleValues() { return multipleValues; } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/PathParameterMapRequestPropertyResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/PathParameterMapRequestPropertyResolverTest.java index fcb14b1..82862f9 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/PathParameterMapRequestPropertyResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/PathParameterMapRequestPropertyResolverTest.java @@ -20,6 +20,7 @@ import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.lang.Nullable; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.web.reactive.HandlerMapping; @@ -103,17 +104,22 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @PathParameter private Map annotated; + @Nullable private Map notAnnotated; + @Nullable @PathParameter("irrelevant") private String withValue; + @Nullable @PathParameter private String notAMap; + @Nullable public Map getAnnotated() { return annotated; } @@ -122,6 +128,7 @@ public void setAnnotated(Map annotated) { this.annotated = annotated; } + @Nullable public Map getNotAnnotated() { return notAnnotated; } @@ -130,6 +137,7 @@ public void setNotAnnotated(Map notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public String getWithValue() { return withValue; } @@ -138,6 +146,7 @@ public void setWithValue(String withValue) { this.withValue = withValue; } + @Nullable public String getNotAMap() { return notAMap; } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/PathParameterRequestPropertyResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/PathParameterRequestPropertyResolverTest.java index 52d293d..28ae69b 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/PathParameterRequestPropertyResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/PathParameterRequestPropertyResolverTest.java @@ -20,6 +20,7 @@ import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.lang.Nullable; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.web.reactive.HandlerMapping; @@ -119,14 +120,18 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @PathParameter("pathParamName") private String annotated; + @Nullable private String notAnnotated; + @Nullable @PathParameter private String missingValue; + @Nullable public String getAnnotated() { return annotated; } @@ -135,6 +140,7 @@ public void setAnnotated(String annotated) { this.annotated = annotated; } + @Nullable public String getNotAnnotated() { return notAnnotated; } @@ -143,6 +149,7 @@ public void setNotAnnotated(String notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public String getMissingValue() { return missingValue; } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestBodyRequestPropertyResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestBodyRequestPropertyResolverTest.java index 32aede7..d6d339b 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestBodyRequestPropertyResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestBodyRequestPropertyResolverTest.java @@ -26,6 +26,7 @@ import org.springframework.http.MediaType; import org.springframework.http.codec.DecoderHttpMessageReader; import org.springframework.http.codec.HttpMessageReader; +import org.springframework.lang.Nullable; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import reactor.core.publisher.Mono; @@ -105,12 +106,14 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @RequestBody private String annotated; + @Nullable private String notAnnotated; - + @Nullable public String getAnnotated() { return annotated; } @@ -119,6 +122,7 @@ public void setAnnotated(String annotated) { this.annotated = annotated; } + @Nullable public String getNotAnnotated() { return notAnnotated; } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestContextRequestPropertyResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestContextRequestPropertyResolverTest.java index 00b17f2..2c9372b 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestContextRequestPropertyResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestContextRequestPropertyResolverTest.java @@ -23,6 +23,7 @@ import org.springframework.context.i18n.TimeZoneAwareLocaleContext; import org.springframework.http.HttpMethod; import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.lang.Nullable; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.mock.web.server.MockWebSession; @@ -216,32 +217,42 @@ private static class NotKnown {} @SuppressWarnings("unused") private static class TestingBean { + @Nullable private ServerWebExchange notAnnotated; + @Nullable @RequestContext private NotKnown notKnown; + @Nullable @RequestContext private ServerWebExchange serverWebExchange; + @Nullable @RequestContext private ServerHttpRequest serverHttpRequest; + @Nullable @RequestContext private WebSession webSession; + @Nullable @RequestContext private HttpMethod httpMethod; + @Nullable @RequestContext private Locale locale; + @Nullable @RequestContext private TimeZone timeZone; + @Nullable @RequestContext private ZoneId zoneId; + @Nullable public ServerWebExchange getNotAnnotated() { return notAnnotated; } @@ -250,6 +261,7 @@ public void setNotAnnotated(ServerWebExchange notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public NotKnown getNotKnown() { return notKnown; } @@ -258,6 +270,7 @@ public void setNotKnown(NotKnown notKnown) { this.notKnown = notKnown; } + @Nullable public ServerWebExchange getServerWebExchange() { return serverWebExchange; } @@ -266,6 +279,7 @@ public void setServerWebExchange(ServerWebExchange serverWebExchange) { this.serverWebExchange = serverWebExchange; } + @Nullable public ServerHttpRequest getServerHttpRequest() { return serverHttpRequest; } @@ -274,6 +288,7 @@ public void setServerHttpRequest(ServerHttpRequest serverHttpRequest) { this.serverHttpRequest = serverHttpRequest; } + @Nullable public WebSession getWebSession() { return webSession; } @@ -282,6 +297,7 @@ public void setWebSession(WebSession webSession) { this.webSession = webSession; } + @Nullable public HttpMethod getHttpMethod() { return httpMethod; } @@ -290,6 +306,7 @@ public void setHttpMethod(HttpMethod httpMethod) { this.httpMethod = httpMethod; } + @Nullable public Locale getLocale() { return locale; } @@ -298,6 +315,7 @@ public void setLocale(Locale locale) { this.locale = locale; } + @Nullable public TimeZone getTimeZone() { return timeZone; } @@ -306,6 +324,7 @@ public void setTimeZone(TimeZone timeZone) { this.timeZone = timeZone; } + @Nullable public ZoneId getZoneId() { return zoneId; } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestParameterMapRequestPropertyResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestParameterMapRequestPropertyResolverTest.java index 450576c..f27affb 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestParameterMapRequestPropertyResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestParameterMapRequestPropertyResolverTest.java @@ -20,6 +20,7 @@ import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.lang.Nullable; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.util.MultiValueMap; @@ -100,20 +101,26 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @RequestParameter private Map annotated; + @Nullable private Map notAnnotated; + @Nullable @RequestParameter private MultiValueMap multivalue; + @Nullable @RequestParameter private String notAMap; + @Nullable @RequestParameter("irrelevant") private Map valuePresent; + @Nullable public Map getAnnotated() { return annotated; } @@ -122,6 +129,7 @@ public void setAnnotated(Map annotated) { this.annotated = annotated; } + @Nullable public Map getNotAnnotated() { return notAnnotated; } @@ -130,6 +138,7 @@ public void setNotAnnotated(Map notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public MultiValueMap getMultivalue() { return multivalue; } @@ -138,6 +147,7 @@ public void setMultivalue(MultiValueMap multivalue) { this.multivalue = multivalue; } + @Nullable public String getNotAMap() { return notAMap; } @@ -146,6 +156,7 @@ public void setNotAMap(String notAMap) { this.notAMap = notAMap; } + @Nullable public Map getValuePresent() { return valuePresent; } diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolverTest.java index d3b8d13..aef8d7c 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolverTest.java @@ -251,7 +251,9 @@ private MethodParameter createMethodParameter(String anAnnotatedMethod, Class } private static class MockRequestPropertyResolver implements RequestPropertyResolver { + @Nullable private final Object value; + @Nullable private final RuntimeException exception; private MockRequestPropertyResolver(@Nullable T value, @Nullable RuntimeException exception) { @@ -260,13 +262,14 @@ private MockRequestPropertyResolver(@Nullable T value, @Nullable RuntimeExce } @Override - public boolean supports(@NonNull BindingProperty bindingProperty) { + public boolean supports(BindingProperty bindingProperty) { // Not used in this test return true; } @Override - public Object resolve(@NonNull BindingProperty bindingProperty, @NonNull NativeWebRequest request) { + @Nullable + public Object resolve(BindingProperty bindingProperty, NativeWebRequest request) { if (exception != null) { throw exception; } @@ -323,9 +326,12 @@ public void withBindingResult(@BeanParameter @Validated ABeanClass aBeanClass, B } private static class ABeanClass { + @Nullable private String propertyOne; + @Nullable private Integer propertyTwo; + @Nullable public String getPropertyOne() { return propertyOne; } @@ -334,6 +340,7 @@ public void setPropertyOne(String propertyOne) { this.propertyOne = propertyOne; } + @Nullable public Integer getPropertyTwo() { return propertyTwo; } diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinder.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinder.java index a152e97..d6fa6b0 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinder.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinder.java @@ -1,10 +1,12 @@ package com.mattbertolini.spring.web.servlet.mvc.bind; +import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.PropertyValues; import org.springframework.lang.Nullable; import org.springframework.validation.BindingResult; import org.springframework.web.bind.WebDataBinder; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -18,10 +20,14 @@ public class MockWebDataBinder extends WebDataBinder { public MockWebDataBinder(@Nullable Object target) { super(target); + pvs = new MutablePropertyValues(); + validationHints = new ArrayList<>(); } public MockWebDataBinder(@Nullable Object target, String objectName) { super(target, objectName); + pvs = new MutablePropertyValues(); + validationHints = new ArrayList<>(); } @Override diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolverTest.java index 80abe2a..9932fae 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolverTest.java @@ -21,6 +21,7 @@ import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.lang.Nullable; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; @@ -107,14 +108,18 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @CookieParameter("the_cookie") private String annotated; + @Nullable private String notAnnotated; + @Nullable @CookieParameter("the_cookie") private Cookie cookieObject; + @Nullable public String getAnnotated() { return annotated; } @@ -123,6 +128,7 @@ public void setAnnotated(String annotated) { this.annotated = annotated; } + @Nullable public String getNotAnnotated() { return notAnnotated; } @@ -131,6 +137,7 @@ public void setNotAnnotated(String notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public Cookie getCookieObject() { return cookieObject; } From 2b83942dd443988a3bf92804cb12f5594af3079a Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Thu, 18 Jul 2024 21:44:40 -0400 Subject: [PATCH 13/33] More NullAway support --- .../CachedAnnotatedRequestBeanIntrospector.java | 7 ++----- ...ScanningAnnotatedRequestBeanIntrospector.java | 10 ++++------ .../DefaultAnnotatedRequestBeanIntrospector.java | 16 +++++++--------- .../bind/introspect/ResolvedPropertyData.java | 12 ++++-------- .../BeanParameterMethodArgumentResolverTest.java | 2 +- .../BeanParameterMethodArgumentResolverTest.java | 1 + .../mvc/bind/MockWebDataBinderFactory.java | 1 + ...aderParameterRequestPropertyResolverTest.java | 9 +++++++++ ...sionParameterRequestPropertyResolverTest.java | 5 +++++ 9 files changed, 34 insertions(+), 29 deletions(-) diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/CachedAnnotatedRequestBeanIntrospector.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/CachedAnnotatedRequestBeanIntrospector.java index 6ac201e..537f6b8 100644 --- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/CachedAnnotatedRequestBeanIntrospector.java +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/CachedAnnotatedRequestBeanIntrospector.java @@ -16,8 +16,6 @@ package com.mattbertolini.spring.web.bind.introspect; -import org.springframework.lang.NonNull; - import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -26,14 +24,13 @@ public class CachedAnnotatedRequestBeanIntrospector implements AnnotatedRequestB private final AnnotatedRequestBeanIntrospector delegate; private final ConcurrentMap, Map> cache; - public CachedAnnotatedRequestBeanIntrospector(@NonNull AnnotatedRequestBeanIntrospector delegate) { + public CachedAnnotatedRequestBeanIntrospector(AnnotatedRequestBeanIntrospector delegate) { this.delegate = delegate; cache = new ConcurrentHashMap<>(); } @Override - @NonNull - public Map getResolverMapFor(@NonNull Class targetType) { + public Map getResolverMapFor(Class targetType) { return cache.computeIfAbsent(targetType, delegate::getResolverMapFor); } } diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/ClassPathScanningAnnotatedRequestBeanIntrospector.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/ClassPathScanningAnnotatedRequestBeanIntrospector.java index 2a64fb4..961cff4 100644 --- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/ClassPathScanningAnnotatedRequestBeanIntrospector.java +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/ClassPathScanningAnnotatedRequestBeanIntrospector.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 the original author or authors. + * Copyright 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. @@ -23,7 +23,6 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.type.filter.AnnotationTypeFilter; -import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -39,7 +38,7 @@ public class ClassPathScanningAnnotatedRequestBeanIntrospector implements Annota private final CachedAnnotatedRequestBeanIntrospector introspectorCache; private final Set basePackages; - public ClassPathScanningAnnotatedRequestBeanIntrospector(@NonNull AnnotatedRequestBeanIntrospector delegate, @Nullable Set basePackages) { + public ClassPathScanningAnnotatedRequestBeanIntrospector(AnnotatedRequestBeanIntrospector delegate, @Nullable Set basePackages) { this.basePackages = new HashSet<>(); if (basePackages != null) { this.basePackages.addAll(basePackages); @@ -50,8 +49,7 @@ public ClassPathScanningAnnotatedRequestBeanIntrospector(@NonNull AnnotatedReque } @Override - @NonNull - public Map getResolverMapFor(@NonNull Class targetType) { + public Map getResolverMapFor(Class targetType) { return introspectorCache.getResolverMapFor(targetType); } @@ -62,7 +60,7 @@ public void afterPropertiesSet() { } } - private void scanAndLoadRequestBeans(@NonNull String basePackage) { + private void scanAndLoadRequestBeans(String basePackage) { ClassLoader classLoader = ClassPathScanningAnnotatedRequestBeanIntrospector.class.getClassLoader(); LOGGER.debug("Searching for @RequestBean annotated classes in package [" + basePackage + "]"); Set candidateComponents = scanner.findCandidateComponents(basePackage); diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/DefaultAnnotatedRequestBeanIntrospector.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/DefaultAnnotatedRequestBeanIntrospector.java index c7d04b9..5962946 100644 --- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/DefaultAnnotatedRequestBeanIntrospector.java +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/DefaultAnnotatedRequestBeanIntrospector.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 the original author or authors. + * Copyright 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. @@ -21,7 +21,6 @@ import com.mattbertolini.spring.web.bind.resolver.RequestPropertyResolverBase; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; -import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import java.beans.PropertyDescriptor; @@ -39,7 +38,7 @@ public class DefaultAnnotatedRequestBeanIntrospector implements AnnotatedRequest private final AbstractPropertyResolverRegistry registry; - public DefaultAnnotatedRequestBeanIntrospector(@NonNull AbstractPropertyResolverRegistry registry) { + public DefaultAnnotatedRequestBeanIntrospector(AbstractPropertyResolverRegistry registry) { this.registry = registry; } @@ -52,18 +51,17 @@ public DefaultAnnotatedRequestBeanIntrospector(@NonNull AbstractPropertyResolver * @throws CircularReferenceException If a circular reference is found while traversing the object graph. */ @Override - @NonNull - public Map getResolverMapFor(@NonNull Class targetType) { + public Map getResolverMapFor(Class targetType) { Set> cycleClasses = new LinkedHashSet<>(); Map propertyData = new HashMap<>(); recursiveGetResolverMapFor(targetType, null, propertyData, cycleClasses); return Collections.unmodifiableMap(propertyData); } - private void recursiveGetResolverMapFor(@NonNull final Class targetType, + private void recursiveGetResolverMapFor(final Class targetType, @Nullable final String prefix, - @NonNull Map propertyData, - @NonNull final Set> cycleClasses) { + Map propertyData, + final Set> cycleClasses) { PropertyDescriptor[] propertyDescriptors; try { propertyDescriptors = BeanUtils.getPropertyDescriptors(targetType); @@ -102,7 +100,7 @@ private void recursiveGetResolverMapFor(@NonNull final Class targetType, * @param propertyDescriptor The property descriptor to find a name for. Required. * @return The full property name path */ - private String getPropertyName(@Nullable String prefix, @NonNull PropertyDescriptor propertyDescriptor) { + private String getPropertyName(@Nullable String prefix, PropertyDescriptor propertyDescriptor) { if (prefix == null) { return propertyDescriptor.getName(); } diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/ResolvedPropertyData.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/ResolvedPropertyData.java index d2e236c..074c118 100644 --- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/ResolvedPropertyData.java +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/ResolvedPropertyData.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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. @@ -17,7 +17,6 @@ package com.mattbertolini.spring.web.bind.introspect; import com.mattbertolini.spring.web.bind.resolver.RequestPropertyResolverBase; -import org.springframework.lang.NonNull; import java.util.Objects; @@ -27,26 +26,23 @@ public final class ResolvedPropertyData { private final RequestPropertyResolverBase resolver; public ResolvedPropertyData( - @NonNull String propertyName, - @NonNull BindingProperty bindingProperty, - @NonNull RequestPropertyResolverBase resolver + String propertyName, + BindingProperty bindingProperty, + RequestPropertyResolverBase resolver ) { this.propertyName = propertyName; this.bindingProperty = bindingProperty; this.resolver = resolver; } - @NonNull public String getPropertyName() { return propertyName; } - @NonNull public BindingProperty getBindingProperty() { return bindingProperty; } - @NonNull public RequestPropertyResolverBase getResolver() { return resolver; } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java index aacee29..bdf0be8 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolverTest.java @@ -307,6 +307,7 @@ public void withBindingResult(@BeanParameter @Validated ABeanClass aBeanClass, B } } + @SuppressWarnings("unused") private static class ABeanClass { @Nullable private String propertyOne; @@ -323,7 +324,6 @@ public void setPropertyOne(String propertyOne) { this.propertyOne = propertyOne; } - @SuppressWarnings("unused") @Nullable public Integer getPropertyTwo() { return propertyTwo; diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolverTest.java index aef8d7c..9be2838 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolverTest.java @@ -325,6 +325,7 @@ public void withBindingResult(@BeanParameter @Validated ABeanClass aBeanClass, B } } + @SuppressWarnings("unused") private static class ABeanClass { @Nullable private String propertyOne; diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinderFactory.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinderFactory.java index eee8741..5d147a9 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinderFactory.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinderFactory.java @@ -10,6 +10,7 @@ public class MockWebDataBinderFactory implements WebDataBinderFactory { private MockWebDataBinder binder; + @Nullable private BindingResult bindingResult; @Override diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/HeaderParameterRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/HeaderParameterRequestPropertyResolverTest.java index d625bc0..46e649d 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/HeaderParameterRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/HeaderParameterRequestPropertyResolverTest.java @@ -20,6 +20,7 @@ import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.lang.Nullable; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.web.context.request.ServletWebRequest; @@ -100,17 +101,22 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @HeaderParameter("X-HeaderName") private String annotated; + @Nullable private String notAnnotated; + @Nullable @HeaderParameter private String missingValue; + @Nullable @HeaderParameter("X-Multiple") private List multipleValues; + @Nullable public String getAnnotated() { return annotated; } @@ -119,6 +125,7 @@ public void setAnnotated(String annotated) { this.annotated = annotated; } + @Nullable public String getNotAnnotated() { return notAnnotated; } @@ -127,6 +134,7 @@ public void setNotAnnotated(String notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public String getMissingValue() { return missingValue; } @@ -135,6 +143,7 @@ public void setMissingValue(String missingValue) { this.missingValue = missingValue; } + @Nullable public List getMultipleValues() { return multipleValues; } diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/SessionParameterRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/SessionParameterRequestPropertyResolverTest.java index 72c7123..862e30a 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/SessionParameterRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/SessionParameterRequestPropertyResolverTest.java @@ -20,6 +20,7 @@ import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.lang.Nullable; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; import org.springframework.web.context.request.ServletWebRequest; @@ -91,11 +92,14 @@ private BindingProperty bindingProperty(String propertyName) throws Introspectio @SuppressWarnings("unused") private static class TestingBean { + @Nullable @SessionParameter("sessionKey") private String annotated; + @Nullable private String notAnnotated; + @Nullable public String getAnnotated() { return annotated; } @@ -104,6 +108,7 @@ public void setAnnotated(String annotated) { this.annotated = annotated; } + @Nullable public String getNotAnnotated() { return notAnnotated; } From 270b27bcccd2782b6cc97945f17f7f52a56e73b4 Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Thu, 18 Jul 2024 21:48:07 -0400 Subject: [PATCH 14/33] More NullAway support --- ...estContextRequestPropertyResolverTest.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestContextRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestContextRequestPropertyResolverTest.java index 7b6f168..e3792d4 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestContextRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestContextRequestPropertyResolverTest.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.HttpMethod; +import org.springframework.lang.Nullable; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; import org.springframework.web.context.request.NativeWebRequest; @@ -189,35 +190,46 @@ private static class NotKnown {} @SuppressWarnings("unused") private static class TestingBean { + @Nullable private WebRequest notAnnotated; + @Nullable @RequestContext private NotKnown notKnown; + @Nullable @RequestContext private HttpServletRequest httpServletRequest; + @Nullable @RequestContext private ServletRequest servletRequest; + @Nullable @RequestContext private WebRequest webRequest; + @Nullable @RequestContext private HttpSession httpSession; + @Nullable @RequestContext private HttpMethod httpMethod; + @Nullable @RequestContext private Locale locale; + @Nullable @RequestContext private TimeZone timeZone; + @Nullable @RequestContext private ZoneId zoneId; + @Nullable public WebRequest getNotAnnotated() { return notAnnotated; } @@ -226,6 +238,7 @@ public void setNotAnnotated(WebRequest notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public NotKnown getNotKnown() { return notKnown; } @@ -234,6 +247,7 @@ public void setNotKnown(NotKnown notKnown) { this.notKnown = notKnown; } + @Nullable public HttpServletRequest getHttpServletRequest() { return httpServletRequest; } @@ -242,6 +256,7 @@ public void setHttpServletRequest(HttpServletRequest httpServletRequest) { this.httpServletRequest = httpServletRequest; } + @Nullable public ServletRequest getServletRequest() { return servletRequest; } @@ -250,6 +265,7 @@ public void setServletRequest(ServletRequest servletRequest) { this.servletRequest = servletRequest; } + @Nullable public WebRequest getWebRequest() { return webRequest; } @@ -258,6 +274,7 @@ public void setWebRequest(WebRequest webRequest) { this.webRequest = webRequest; } + @Nullable public HttpSession getHttpSession() { return httpSession; } @@ -266,6 +283,7 @@ public void setHttpSession(HttpSession httpSession) { this.httpSession = httpSession; } + @Nullable public HttpMethod getHttpMethod() { return httpMethod; } @@ -274,6 +292,7 @@ public void setHttpMethod(HttpMethod httpMethod) { this.httpMethod = httpMethod; } + @Nullable public Locale getLocale() { return locale; } @@ -282,6 +301,7 @@ public void setLocale(Locale locale) { this.locale = locale; } + @Nullable public TimeZone getTimeZone() { return timeZone; } @@ -290,6 +310,7 @@ public void setTimeZone(TimeZone timeZone) { this.timeZone = timeZone; } + @Nullable public ZoneId getZoneId() { return zoneId; } From d4e84c3147e871325851dc99d6e9b7bc741515f2 Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Fri, 19 Jul 2024 08:06:16 -0400 Subject: [PATCH 15/33] Docs updates --- README.md | 2 +- RELEASE_NOTES.md | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e31bc4c..6a83ab5 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ public class ExampleConfiguration { ### Build Requirements -* Java 17 or above to build. The release jars are compiled to Java 8 bytecode. Integration tests are compiled to Java 17 bytecode. +* Java 17 or above to build and use. To build: diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 2dec21b..b33d72c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,8 +1,14 @@ # Release Notes -## 0.7.0-SNAPSHOT +## 1.0.0-SNAPSHOT Released N/A +- Breaking change +- Updated to support Spring 6.x and Spring Boot 3.x. This brings with it a minimum Java version of 17. With the Spring + upgrade comes a switch to the Jakara EE Servlet API instead of Java EE. +- Update build to Gradle 8.9 +- Added support for ErrorProne and NullAway for compile time checks of nullability. + ## 0.6.0 Released 2023-12-01 From f3eb43922c48d2e63dae968e509b3e195db46371 Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Sun, 21 Jul 2024 21:35:01 -0400 Subject: [PATCH 16/33] More NullAway support --- docs/build.gradle.kts | 1 + integration-tests/build.gradle.kts | 2 ++ .../test/web/bind/PathParameterBean.java | 13 +++++++++++++ .../web/bind/PathParameterController.java | 6 ++++++ .../web/bind/support/MapValueResolver.java | 5 +++++ ...eParameterRequestPropertyResolverTest.java | 2 +- ...mParameterRequestPropertyResolverTest.java | 2 +- ...rParameterRequestPropertyResolverTest.java | 2 +- ...hParameterRequestPropertyResolverTest.java | 2 +- ...tParameterRequestPropertyResolverTest.java | 2 +- ...nParameterRequestPropertyResolverTest.java | 2 +- .../BeanParameterMethodArgumentResolver.java | 2 +- .../mvc/bind/MockWebDataBinderFactory.java | 3 +++ ...eParameterRequestPropertyResolverTest.java | 2 +- ...rameterMapRequestPropertyResolverTest.java | 19 +++++++++++++++++++ ...mParameterRequestPropertyResolverTest.java | 15 ++++++++++++++- ...rameterMapRequestPropertyResolverTest.java | 13 +++++++++++++ ...rParameterRequestPropertyResolverTest.java | 2 +- ...rameterMapRequestPropertyResolverTest.java | 9 +++++++++ ...hParameterRequestPropertyResolverTest.java | 9 ++++++++- ...equestBodyRequestPropertyResolverTest.java | 5 +++++ ...rameterMapRequestPropertyResolverTest.java | 19 +++++++++++++++++++ ...tParameterRequestPropertyResolverTest.java | 15 ++++++++++++++- ...nParameterRequestPropertyResolverTest.java | 2 +- 24 files changed, 141 insertions(+), 13 deletions(-) diff --git a/docs/build.gradle.kts b/docs/build.gradle.kts index 612632c..330fa0c 100644 --- a/docs/build.gradle.kts +++ b/docs/build.gradle.kts @@ -13,6 +13,7 @@ dependencies { implementation(project(":spring-webmvc-annotated-data-binder")) implementation(project(":spring-webflux-annotated-data-binder")) implementation(libs.jakartaServletApi) // Version defined in Spring BOM file + compileOnly(libs.findbugsJsr305) add("asciidoctorExt", libs.springAsciidoctorExtBlockSwitch) diff --git a/integration-tests/build.gradle.kts b/integration-tests/build.gradle.kts index faeea26..7c89684 100644 --- a/integration-tests/build.gradle.kts +++ b/integration-tests/build.gradle.kts @@ -11,11 +11,13 @@ dependencies { implementation(libs.jacksonDatabind) implementation(libs.jakartaWebsocketApi) implementation(libs.jakartaWebsocketClientApi) + compileOnly(libs.findbugsJsr305) testImplementation(libs.junitJupiterApi) testImplementation(libs.assertJCore) testImplementation(libs.springTest) testCompileOnly(libs.hamcrest) // Needed for Spring mock MVC matchers + testCompileOnly(libs.findbugsJsr305) } tasks.named("jacocoTestReport").configure { diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterBean.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterBean.java index d035c99..ff02106 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterBean.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterBean.java @@ -19,27 +19,35 @@ import com.mattbertolini.spring.web.bind.annotation.BeanParameter; import com.mattbertolini.spring.web.bind.annotation.PathParameter; import jakarta.validation.constraints.NotEmpty; +import org.springframework.lang.Nullable; import java.util.Map; public class PathParameterBean { + @Nullable @PathParameter("annotated_field") private String annotatedField; + @Nullable private String annotatedSetter; + @Nullable private String annotatedGetter; + @Nullable @PathParameter private Map simpleMap; + @Nullable @NotEmpty @PathParameter("validated") private String validated; + @Nullable @BeanParameter private NestedBean nestedBean; + @Nullable public String getAnnotatedField() { return annotatedField; } @@ -48,6 +56,7 @@ public void setAnnotatedField(String annotatedField) { this.annotatedField = annotatedField; } + @Nullable public String getAnnotatedSetter() { return annotatedSetter; } @@ -57,6 +66,7 @@ public void setAnnotatedSetter(String annotatedSetter) { this.annotatedSetter = annotatedSetter; } + @Nullable @PathParameter("annotated_getter") public String getAnnotatedGetter() { return annotatedGetter; @@ -66,6 +76,7 @@ public void setAnnotatedGetter(String annotatedGetter) { this.annotatedGetter = annotatedGetter; } + @Nullable public Map getSimpleMap() { return simpleMap; } @@ -74,6 +85,7 @@ public void setSimpleMap(Map simpleMap) { this.simpleMap = simpleMap; } + @Nullable public String getValidated() { return validated; } @@ -82,6 +94,7 @@ public void setValidated(String validated) { this.validated = validated; } + @Nullable public NestedBean getNestedBean() { return nestedBean; } diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterController.java index 0d4c3cb..4768633 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterController.java @@ -20,6 +20,7 @@ import com.mattbertolini.spring.web.bind.annotation.BeanParameter; import jakarta.validation.Valid; import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -29,21 +30,25 @@ @SuppressWarnings("MVCPathVariableInspection") @RestController public class PathParameterController { + @Nullable @GetMapping(value = "/annotatedField/{annotated_field}", produces = MediaType.TEXT_PLAIN_VALUE) public String annotatedField(@BeanParameter PathParameterBean pathParameterBean) { return pathParameterBean.getAnnotatedField(); } + @Nullable @GetMapping(value = "/annotatedSetter/{annotated_setter}", produces = MediaType.TEXT_PLAIN_VALUE) public String annotatedSetter(@BeanParameter PathParameterBean pathParameterBean) { return pathParameterBean.getAnnotatedSetter(); } + @Nullable @GetMapping(value = "/annotatedGetter/{annotated_getter}", produces = MediaType.TEXT_PLAIN_VALUE) public String annotatedGetter(@BeanParameter PathParameterBean pathParameterBean) { return pathParameterBean.getAnnotatedGetter(); } + @Nullable @GetMapping(value = "/simpleMap/{simple_map}") public String simpleMap(@BeanParameter PathParameterBean pathParameterBean) { Map simpleMap = pathParameterBean.getSimpleMap(); @@ -55,6 +60,7 @@ public String bindingResult(@BeanParameter PathParameterBean pathParameterBean, return Integer.toString(bindingResult.getErrorCount()); } + @Nullable @GetMapping(value = {"/validated", "/validated/{validated}"}, produces = MediaType.TEXT_PLAIN_VALUE) public String validated(@Valid @BeanParameter PathParameterBean pathParameterBean) { return pathParameterBean.getValidated(); diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/MapValueResolver.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/MapValueResolver.java index 51e649e..0933c05 100644 --- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/MapValueResolver.java +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/support/MapValueResolver.java @@ -23,6 +23,11 @@ import java.util.Map; import java.util.Set; +/** + * Implementation of the {@link DataBinder.ValueResolver} to assist in mapping binding data for constructor binding. + * + * @param values The Map of values to pass to the binder + */ public record MapValueResolver(Map values) implements DataBinder.ValueResolver { @Override @Nullable diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/CookieParameterRequestPropertyResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/CookieParameterRequestPropertyResolverTest.java index ade20f3..653bac1 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/CookieParameterRequestPropertyResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/CookieParameterRequestPropertyResolverTest.java @@ -61,7 +61,7 @@ void throwsExceptionIfResolveCalledWithNoAnnotation() throws Exception { .build(); MockServerWebExchange exchange = MockServerWebExchange.from(request); BindingProperty bindingProperty = bindingProperty("notAnnotated"); - assertThatExceptionOfType(IllegalStateException.class) + assertThatExceptionOfType(NullPointerException.class) .isThrownBy(() -> resolver.resolve(bindingProperty, exchange)); } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolverTest.java index 68664b9..52b31c2 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolverTest.java @@ -90,7 +90,7 @@ void throwsExceptionIfResolveCalledWithNoAnnotation() throws Exception { .build(); MockServerWebExchange exchange = MockServerWebExchange.from(request); BindingProperty bindingProperty = bindingProperty("notAnnotated"); - assertThatExceptionOfType(IllegalStateException.class) + assertThatExceptionOfType(NullPointerException.class) .isThrownBy(() -> resolver.resolve(bindingProperty, exchange)); } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterRequestPropertyResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterRequestPropertyResolverTest.java index 8621a9e..3c6ef03 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterRequestPropertyResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterRequestPropertyResolverTest.java @@ -68,7 +68,7 @@ void throwsExceptionIfResolveCalledWithNoAnnotation() throws Exception { .build(); MockServerWebExchange exchange = MockServerWebExchange.from(request); BindingProperty bindingProperty = bindingProperty("notAnnotated"); - assertThatExceptionOfType(IllegalStateException.class) + assertThatExceptionOfType(NullPointerException.class) .isThrownBy(() -> resolver.resolve(bindingProperty, exchange)); } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/PathParameterRequestPropertyResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/PathParameterRequestPropertyResolverTest.java index 28ae69b..0b843f1 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/PathParameterRequestPropertyResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/PathParameterRequestPropertyResolverTest.java @@ -77,7 +77,7 @@ void throwsExceptionIfResolveCalledWithNoAnnotation() throws Exception { .build(); MockServerWebExchange exchange = MockServerWebExchange.from(request); BindingProperty bindingProperty = bindingProperty("notAnnotated"); - assertThatExceptionOfType(IllegalStateException.class) + assertThatExceptionOfType(NullPointerException.class) .isThrownBy(() -> resolver.resolve(bindingProperty, exchange)); } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestParameterRequestPropertyResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestParameterRequestPropertyResolverTest.java index 499106b..46867c1 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestParameterRequestPropertyResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestParameterRequestPropertyResolverTest.java @@ -67,7 +67,7 @@ void throwsExceptionIfResolveCalledWithNoAnnotation() throws Exception { .build(); MockServerWebExchange exchange = MockServerWebExchange.from(request); BindingProperty bindingProperty = bindingProperty("notAnnotated"); - assertThatExceptionOfType(IllegalStateException.class) + assertThatExceptionOfType(NullPointerException.class) .isThrownBy(() -> resolver.resolve(bindingProperty, exchange)); } diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/SessionParameterRequestPropertyResolverTest.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/SessionParameterRequestPropertyResolverTest.java index 289af60..992d8d5 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/SessionParameterRequestPropertyResolverTest.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/resolver/SessionParameterRequestPropertyResolverTest.java @@ -59,7 +59,7 @@ void throwsExceptionIfResolveCalledWithNoAnnotation() throws Exception { .build(); MockServerWebExchange exchange = MockServerWebExchange.from(request); BindingProperty bindingProperty = bindingProperty("notAnnotated"); - assertThatExceptionOfType(IllegalStateException.class) + assertThatExceptionOfType(NullPointerException.class) .isThrownBy(() -> resolver.resolve(bindingProperty, exchange)); } diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java index 01c3557..38c1c0f 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java @@ -72,7 +72,7 @@ protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest requ @SuppressWarnings("unchecked") private Map memoizedGetValuesToBind(Class targetType, NativeWebRequest request) { - Map memoizedValues = (Map) request.getAttribute(BIND_VALUES_ATTRIBUTE_KEY, NativeWebRequest.SCOPE_REQUEST); + /* Nullable */ Map memoizedValues = (Map) request.getAttribute(BIND_VALUES_ATTRIBUTE_KEY, NativeWebRequest.SCOPE_REQUEST); if (memoizedValues != null) { return memoizedValues; } diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinderFactory.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinderFactory.java index 5d147a9..c365a9b 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinderFactory.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinderFactory.java @@ -1,5 +1,6 @@ package com.mattbertolini.spring.web.servlet.mvc.bind; +import com.uber.nullaway.annotations.Initializer; import org.springframework.core.ResolvableType; import org.springframework.format.support.FormattingConversionServiceFactoryBean; import org.springframework.lang.Nullable; @@ -13,6 +14,7 @@ public class MockWebDataBinderFactory implements WebDataBinderFactory { @Nullable private BindingResult bindingResult; + @Initializer @Override public WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName, ResolvableType targetType) throws Exception { binder = new MockWebDataBinder(target, objectName); @@ -31,6 +33,7 @@ public WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object return binder; } + @Initializer @Override public WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception { binder = new MockWebDataBinder(target, objectName); diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolverTest.java index 9932fae..c50caf0 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/CookieParameterRequestPropertyResolverTest.java @@ -64,7 +64,7 @@ void supportsReturnsFalseOnMissingAnnotation() throws Exception { void throwsExceptionIfResolveCalledWithNoAnnotation() throws Exception { // Unlikely to happen as the library always checks the supports method. BindingProperty bindingProperty = bindingProperty("notAnnotated"); - assertThatExceptionOfType(IllegalStateException.class) + assertThatExceptionOfType(NullPointerException.class) .isThrownBy(() -> resolver.resolve(bindingProperty, request)); } diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterMapRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterMapRequestPropertyResolverTest.java index b87facf..18d3c73 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterMapRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterMapRequestPropertyResolverTest.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartHttpServletRequest; @@ -298,32 +299,42 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @FormParameter private Map annotated; + @Nullable private Map notAnnotated; + @Nullable @FormParameter private MultiValueMap multivalue; + @Nullable @FormParameter("name") private String withName; + @Nullable @FormParameter private String notAMap; + @Nullable @FormParameter private Map multipartFileMap; + @Nullable @FormParameter private MultiValueMap multiValueMultipartMap; + @Nullable @FormParameter private Map partMap; + @Nullable @FormParameter private MultiValueMap multiValuePartMap; + @Nullable public Map getAnnotated() { return annotated; } @@ -332,6 +343,7 @@ public void setAnnotated(Map annotated) { this.annotated = annotated; } + @Nullable public Map getNotAnnotated() { return notAnnotated; } @@ -340,6 +352,7 @@ public void setNotAnnotated(Map notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public MultiValueMap getMultivalue() { return multivalue; } @@ -348,6 +361,7 @@ public void setMultivalue(MultiValueMap multivalue) { this.multivalue = multivalue; } + @Nullable public String getWithName() { return withName; } @@ -356,6 +370,7 @@ public void setWithName(String withName) { this.withName = withName; } + @Nullable public String getNotAMap() { return notAMap; } @@ -364,6 +379,7 @@ public void setNotAMap(String notAMap) { this.notAMap = notAMap; } + @Nullable public Map getMultipartFileMap() { return multipartFileMap; } @@ -372,6 +388,7 @@ public void setMultipartFileMap(Map multipartFileMap) { this.multipartFileMap = multipartFileMap; } + @Nullable public MultiValueMap getMultiValueMultipartMap() { return multiValueMultipartMap; } @@ -380,6 +397,7 @@ public void setMultiValueMultipartMap(MultiValueMap multi this.multiValueMultipartMap = multiValueMultipartMap; } + @Nullable public Map getPartMap() { return partMap; } @@ -388,6 +406,7 @@ public void setPartMap(Map partMap) { this.partMap = partMap; } + @Nullable public MultiValueMap getMultiValuePartMap() { return multiValuePartMap; } diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterRequestPropertyResolverTest.java index aa0845a..2f010e8 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/FormParameterRequestPropertyResolverTest.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartHttpServletRequest; @@ -73,7 +74,7 @@ void supportsReturnsFalseOnMissingAnnotationValue() throws Exception { void throwsExceptionIfResolveCalledWithNoAnnotation() throws Exception { // Unlikely to happen as the library always checks the supports method. BindingProperty bindingProperty = bindingProperty("notAnnotated"); - assertThatExceptionOfType(IllegalStateException.class) + assertThatExceptionOfType(NullPointerException.class) .isThrownBy(() -> resolver.resolve(bindingProperty, request)); } @@ -172,23 +173,30 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @FormParameter("testing") private String annotated; + @Nullable private String notAnnotated; + @Nullable @FormParameter("multiple_values") private List multipleValues; + @Nullable @FormParameter private String missingValue; + @Nullable @FormParameter("multipart_file") private MultipartFile multipartFile; + @Nullable @FormParameter("part") private Part part; + @Nullable public String getAnnotated() { return annotated; } @@ -197,6 +205,7 @@ public void setAnnotated(String annotated) { this.annotated = annotated; } + @Nullable public String getNotAnnotated() { return notAnnotated; } @@ -205,6 +214,7 @@ public void setNotAnnotated(String notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public List getMultipleValues() { return multipleValues; } @@ -213,6 +223,7 @@ public void setMultipleValues(List multipleValues) { this.multipleValues = multipleValues; } + @Nullable public String getMissingValue() { return missingValue; } @@ -221,6 +232,7 @@ public void setMissingValue(String missingValue) { this.missingValue = missingValue; } + @Nullable public MultipartFile getMultipartFile() { return multipartFile; } @@ -229,6 +241,7 @@ public void setMultipartFile(MultipartFile multipartFile) { this.multipartFile = multipartFile; } + @Nullable public Part getPart() { return part; } diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/HeaderParameterMapRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/HeaderParameterMapRequestPropertyResolverTest.java index 2ca33be..211474d 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/HeaderParameterMapRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/HeaderParameterMapRequestPropertyResolverTest.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; +import org.springframework.lang.Nullable; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.util.MultiValueMap; import org.springframework.web.context.request.NativeWebRequest; @@ -130,23 +131,30 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @HeaderParameter private Map annotated; + @Nullable private Map notAnnotated; + @Nullable @HeaderParameter private MultiValueMap multivalue; + @Nullable @HeaderParameter private HttpHeaders httpHeaders; + @Nullable @HeaderParameter("irrelevant") private String withValue; + @Nullable @HeaderParameter private String notAMap; + @Nullable public Map getAnnotated() { return annotated; } @@ -155,6 +163,7 @@ public void setAnnotated(Map annotated) { this.annotated = annotated; } + @Nullable public Map getNotAnnotated() { return notAnnotated; } @@ -163,6 +172,7 @@ public void setNotAnnotated(Map notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public MultiValueMap getMultivalue() { return multivalue; } @@ -171,6 +181,7 @@ public void setMultivalue(MultiValueMap multivalue) { this.multivalue = multivalue; } + @Nullable public HttpHeaders getHttpHeaders() { return httpHeaders; } @@ -179,6 +190,7 @@ public void setHttpHeaders(HttpHeaders httpHeaders) { this.httpHeaders = httpHeaders; } + @Nullable public String getWithValue() { return withValue; } @@ -187,6 +199,7 @@ public void setWithValue(String withValue) { this.withValue = withValue; } + @Nullable public String getNotAMap() { return notAMap; } diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/HeaderParameterRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/HeaderParameterRequestPropertyResolverTest.java index 46e649d..61235ee 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/HeaderParameterRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/HeaderParameterRequestPropertyResolverTest.java @@ -65,7 +65,7 @@ void supportsReturnsFalseOnMissingAnnotationValue() throws Exception { void throwsExceptionIfResolveCalledWithNoAnnotation() throws Exception { // Unlikely to happen as the library always checks the supports method. BindingProperty bindingProperty = bindingProperty("notAnnotated"); - assertThatExceptionOfType(IllegalStateException.class) + assertThatExceptionOfType(NullPointerException.class) .isThrownBy(() -> resolver.resolve(bindingProperty, request)); } diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/PathParameterMapRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/PathParameterMapRequestPropertyResolverTest.java index d1e4051..1336608 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/PathParameterMapRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/PathParameterMapRequestPropertyResolverTest.java @@ -20,6 +20,7 @@ import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.lang.Nullable; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.servlet.HandlerMapping; @@ -100,17 +101,22 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @PathParameter private Map annotated; + @Nullable private Map notAnnotated; + @Nullable @PathParameter("irrelevant") private String withValue; + @Nullable @PathParameter private String notAMap; + @Nullable public Map getAnnotated() { return annotated; } @@ -119,6 +125,7 @@ public void setAnnotated(Map annotated) { this.annotated = annotated; } + @Nullable public Map getNotAnnotated() { return notAnnotated; } @@ -127,6 +134,7 @@ public void setNotAnnotated(Map notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public String getWithValue() { return withValue; } @@ -135,6 +143,7 @@ public void setWithValue(String withValue) { this.withValue = withValue; } + @Nullable public String getNotAMap() { return notAMap; } diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/PathParameterRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/PathParameterRequestPropertyResolverTest.java index 8f5dc34..6114c88 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/PathParameterRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/PathParameterRequestPropertyResolverTest.java @@ -20,6 +20,7 @@ import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.lang.Nullable; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.servlet.HandlerMapping; @@ -67,7 +68,7 @@ void supportsReturnsFalseOnMissingAnnotationValue() throws Exception { void throwsExceptionIfResolveCalledWithNoAnnotation() throws Exception { // Unlikely to happen as the library always checks the supports method. BindingProperty bindingProperty = bindingProperty("notAnnotated"); - assertThatExceptionOfType(IllegalStateException.class) + assertThatExceptionOfType(NullPointerException.class) .isThrownBy(() -> resolver.resolve(bindingProperty, request)); } @@ -110,14 +111,18 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @PathParameter("pathParamName") private String annotated; + @Nullable private String notAnnotated; + @Nullable @PathParameter private String missingValue; + @Nullable public String getAnnotated() { return annotated; } @@ -126,6 +131,7 @@ public void setAnnotated(String annotated) { this.annotated = annotated; } + @Nullable public String getNotAnnotated() { return notAnnotated; } @@ -134,6 +140,7 @@ public void setNotAnnotated(String notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public String getMissingValue() { return missingValue; } diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestBodyRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestBodyRequestPropertyResolverTest.java index 24be44c..459ff3c 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestBodyRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestBodyRequestPropertyResolverTest.java @@ -24,6 +24,7 @@ import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.lang.Nullable; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor; @@ -106,11 +107,14 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @RequestBody private String annotated; + @Nullable private String notAnnotated; + @Nullable public String getAnnotated() { return annotated; } @@ -119,6 +123,7 @@ public void setAnnotated(String annotated) { this.annotated = annotated; } + @Nullable public String getNotAnnotated() { return notAnnotated; } diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolverTest.java index 0c28dc7..b90ffca 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterMapRequestPropertyResolverTest.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartHttpServletRequest; @@ -298,32 +299,42 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @RequestParameter private Map annotated; + @Nullable private Map notAnnotated; + @Nullable @RequestParameter private MultiValueMap multivalue; + @Nullable @RequestParameter private String notAMap; + @Nullable @RequestParameter("irrelevant") private Map valuePresent; + @Nullable @RequestParameter private Map multipartFileMap; + @Nullable @RequestParameter private MultiValueMap multiValueMultipartMap; + @Nullable @RequestParameter private Map partMap; + @Nullable @RequestParameter private MultiValueMap multiValuePartMap; + @Nullable public Map getAnnotated() { return annotated; } @@ -332,6 +343,7 @@ public void setAnnotated(Map annotated) { this.annotated = annotated; } + @Nullable public Map getNotAnnotated() { return notAnnotated; } @@ -340,6 +352,7 @@ public void setNotAnnotated(Map notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public MultiValueMap getMultivalue() { return multivalue; } @@ -348,6 +361,7 @@ public void setMultivalue(MultiValueMap multivalue) { this.multivalue = multivalue; } + @Nullable public String getNotAMap() { return notAMap; } @@ -356,6 +370,7 @@ public void setNotAMap(String notAMap) { this.notAMap = notAMap; } + @Nullable public Map getValuePresent() { return valuePresent; } @@ -364,6 +379,7 @@ public void setValuePresent(Map valuePresent) { this.valuePresent = valuePresent; } + @Nullable public Map getMultipartFileMap() { return multipartFileMap; } @@ -372,6 +388,7 @@ public void setMultipartFileMap(Map multipartFileMap) { this.multipartFileMap = multipartFileMap; } + @Nullable public MultiValueMap getMultiValueMultipartMap() { return multiValueMultipartMap; } @@ -380,6 +397,7 @@ public void setMultiValueMultipartMap(MultiValueMap multi this.multiValueMultipartMap = multiValueMultipartMap; } + @Nullable public Map getPartMap() { return partMap; } @@ -388,6 +406,7 @@ public void setPartMap(Map partMap) { this.partMap = partMap; } + @Nullable public MultiValueMap getMultiValuePartMap() { return multiValuePartMap; } diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolverTest.java index 869e469..94d8b33 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolverTest.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartHttpServletRequest; @@ -73,7 +74,7 @@ void supportsReturnsFalseOnMissingAnnotationValue() throws Exception { void throwsExceptionIfResolveCalledWithNoAnnotation() throws Exception { // Unlikely to happen as the library always checks the supports method. BindingProperty bindingProperty = bindingProperty("notAnnotated"); - assertThatExceptionOfType(IllegalStateException.class) + assertThatExceptionOfType(NullPointerException.class) .isThrownBy(() -> resolver.resolve(bindingProperty, request)); } @@ -172,23 +173,30 @@ private BindingProperty bindingProperty(String property) throws IntrospectionExc @SuppressWarnings("unused") private static class TestingBean { + @Nullable @RequestParameter("testing") private String annotated; + @Nullable private String notAnnotated; + @Nullable @RequestParameter private String missingValue; + @Nullable @RequestParameter("multiple_values") private List multipleValues; + @Nullable @RequestParameter("multipart_file") private MultipartFile multipartFile; + @Nullable @RequestParameter("part") private Part part; + @Nullable public String getAnnotated() { return annotated; } @@ -197,6 +205,7 @@ public void setAnnotated(String annotated) { this.annotated = annotated; } + @Nullable public String getNotAnnotated() { return notAnnotated; } @@ -205,6 +214,7 @@ public void setNotAnnotated(String notAnnotated) { this.notAnnotated = notAnnotated; } + @Nullable public String getMissingValue() { return missingValue; } @@ -213,6 +223,7 @@ public void setMissingValue(String missingValue) { this.missingValue = missingValue; } + @Nullable public List getMultipleValues() { return multipleValues; } @@ -221,6 +232,7 @@ public void setMultipleValues(List multipleValues) { this.multipleValues = multipleValues; } + @Nullable public MultipartFile getMultipartFile() { return multipartFile; } @@ -229,6 +241,7 @@ public void setMultipartFile(MultipartFile multipartFile) { this.multipartFile = multipartFile; } + @Nullable public Part getPart() { return part; } diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/SessionParameterRequestPropertyResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/SessionParameterRequestPropertyResolverTest.java index 862e30a..6dd4ad4 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/SessionParameterRequestPropertyResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/SessionParameterRequestPropertyResolverTest.java @@ -61,7 +61,7 @@ void supportsReturnsFalseOnMissingAnnotation() throws Exception { void throwsExceptionIfResolveCalledWithNoAnnotation() throws Exception { // Unlikely to happen as the library always checks the supports method. BindingProperty bindingProperty = bindingProperty("notAnnotated"); - assertThatExceptionOfType(IllegalStateException.class) + assertThatExceptionOfType(NullPointerException.class) .isThrownBy(() -> resolver.resolve(bindingProperty, request)); } From e84cfe9c49f547eb87971b17d4469a6ccf8ac8dd Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Sun, 21 Jul 2024 22:38:17 -0400 Subject: [PATCH 17/33] More NullAway support --- .../test/web/bind/CookieParameterBean.java | 11 +++++++ .../test/web/bind/DirectFieldAccessBean.java | 19 ++++++++++-- .../test/web/bind/FormParameterBean.java | 29 +++++++++++++++++++ .../web/bind/FormParameterController.java | 3 +- .../spring/test/web/bind/JsonBody.java | 3 ++ .../spring/test/web/bind/NestedBean.java | 15 ++++++++++ .../web/bind/PathParameterController.java | 6 ++-- .../test/web/bind/SessionParameterBean.java | 11 +++++++ 8 files changed, 92 insertions(+), 5 deletions(-) diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/CookieParameterBean.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/CookieParameterBean.java index 16fb669..d1ccdc7 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/CookieParameterBean.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/CookieParameterBean.java @@ -19,22 +19,29 @@ import com.mattbertolini.spring.web.bind.annotation.BeanParameter; import com.mattbertolini.spring.web.bind.annotation.CookieParameter; import jakarta.validation.constraints.NotEmpty; +import org.springframework.lang.Nullable; public class CookieParameterBean { + @Nullable @CookieParameter("annotated_field") private String annotatedField; + @Nullable private String annotatedSetter; + @Nullable private String annotatedGetter; + @Nullable @NotEmpty @CookieParameter("validated") private String validated; + @Nullable @BeanParameter private NestedBean nestedBean; + @Nullable public String getAnnotatedField() { return annotatedField; } @@ -43,6 +50,7 @@ public void setAnnotatedField(String annotatedField) { this.annotatedField = annotatedField; } + @Nullable public String getAnnotatedSetter() { return annotatedSetter; } @@ -52,6 +60,7 @@ public void setAnnotatedSetter(String annotatedSetter) { this.annotatedSetter = annotatedSetter; } + @Nullable @CookieParameter("annotated_getter") public String getAnnotatedGetter() { return annotatedGetter; @@ -61,6 +70,7 @@ public void setAnnotatedGetter(String annotatedGetter) { this.annotatedGetter = annotatedGetter; } + @Nullable public String getValidated() { return validated; } @@ -69,6 +79,7 @@ public void setValidated(String validated) { this.validated = validated; } + @Nullable public NestedBean getNestedBean() { return nestedBean; } diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/DirectFieldAccessBean.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/DirectFieldAccessBean.java index 132f5a8..594ad93 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/DirectFieldAccessBean.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/DirectFieldAccessBean.java @@ -23,55 +23,70 @@ import com.mattbertolini.spring.web.bind.annotation.RequestBody; import com.mattbertolini.spring.web.bind.annotation.RequestParameter; import com.mattbertolini.spring.web.bind.annotation.SessionParameter; +import org.springframework.lang.Nullable; @SuppressWarnings("unused") public class DirectFieldAccessBean { + @Nullable @CookieParameter("cookie_parameter") private String cookieParameter; + @Nullable @FormParameter("form_parameter") private String formParameter; - + + @Nullable @HeaderParameter("header_parameter") private String headerParameter; + @Nullable @PathParameter("path_parameter") private String pathParameter; - + + @Nullable @RequestParameter("request_parameter") private String requestParameter; + @Nullable @SessionParameter("session_parameter") private String sessionParameter; + @Nullable public String getCookieParameter() { return cookieParameter; } + @Nullable public String getFormParameter() { return formParameter; } + @Nullable public String getHeaderParameter() { return headerParameter; } + @Nullable public String getPathParameter() { return pathParameter; } + @Nullable public String getRequestParameter() { return requestParameter; } + @Nullable public String getSessionParameter() { return sessionParameter; } public static class RequestBodyBean { + @Nullable @RequestBody private JsonBody requestBody; + @Nullable public JsonBody getRequestBody() { return requestBody; } diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterBean.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterBean.java index 9989e41..257eef3 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterBean.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterBean.java @@ -21,32 +21,41 @@ import jakarta.servlet.http.Part; import jakarta.validation.constraints.NotEmpty; import org.springframework.http.codec.multipart.FilePart; +import org.springframework.lang.Nullable; import org.springframework.util.MultiValueMap; import org.springframework.web.multipart.MultipartFile; import java.util.Map; public class FormParameterBean { + @Nullable @FormParameter("annotated_field") private String annotatedField; + @Nullable private String annotatedSetter; + @Nullable private String annotatedGetter; + @Nullable @FormParameter private Map simpleMap; + @Nullable @FormParameter private MultiValueMap multiValueMap; + @Nullable @NotEmpty @FormParameter("validated") private String validated; + @Nullable @BeanParameter private NestedBean nestedBean; + @Nullable public String getAnnotatedField() { return annotatedField; } @@ -55,6 +64,7 @@ public void setAnnotatedField(String annotatedField) { this.annotatedField = annotatedField; } + @Nullable public String getAnnotatedSetter() { return annotatedSetter; } @@ -64,6 +74,7 @@ public void setAnnotatedSetter(String annotatedSetter) { this.annotatedSetter = annotatedSetter; } + @Nullable @FormParameter("annotated_getter") public String getAnnotatedGetter() { return annotatedGetter; @@ -73,6 +84,7 @@ public void setAnnotatedGetter(String annotatedGetter) { this.annotatedGetter = annotatedGetter; } + @Nullable public Map getSimpleMap() { return simpleMap; } @@ -81,6 +93,7 @@ public void setSimpleMap(Map simpleMap) { this.simpleMap = simpleMap; } + @Nullable public MultiValueMap getMultiValueMap() { return multiValueMap; } @@ -89,6 +102,7 @@ public void setMultiValueMap(MultiValueMap multiValueMap) { this.multiValueMap = multiValueMap; } + @Nullable public String getValidated() { return validated; } @@ -97,6 +111,7 @@ public void setValidated(String validated) { this.validated = validated; } + @Nullable public NestedBean getNestedBean() { return nestedBean; } @@ -109,24 +124,31 @@ public void setNestedBean(NestedBean nestedBean) { * Test bean for multipart binding in a Servlet/WebMVC application */ static class ServletMultipartBean { + @Nullable @FormParameter("file") private MultipartFile multipartFile; + @Nullable @FormParameter("part") private Part part; + @Nullable @FormParameter private Map multipartFileMap; + @Nullable @FormParameter private MultiValueMap multiValueMultipartMap; + @Nullable @FormParameter private Map partMap; + @Nullable @FormParameter private MultiValueMap multiValuePartMap; + @Nullable public MultipartFile getMultipartFile() { return multipartFile; } @@ -135,6 +157,7 @@ public void setMultipartFile(MultipartFile multipartFile) { this.multipartFile = multipartFile; } + @Nullable public Part getPart() { return part; } @@ -143,6 +166,7 @@ public void setPart(Part part) { this.part = part; } + @Nullable public Map getMultipartFileMap() { return multipartFileMap; } @@ -151,6 +175,7 @@ public void setMultipartFileMap(Map multipartFileMap) { this.multipartFileMap = multipartFileMap; } + @Nullable public MultiValueMap getMultiValueMultipartMap() { return multiValueMultipartMap; } @@ -159,6 +184,7 @@ public void setMultiValueMultipartMap(MultiValueMap multi this.multiValueMultipartMap = multiValueMultipartMap; } + @Nullable public Map getPartMap() { return partMap; } @@ -167,6 +193,7 @@ public void setPartMap(Map partMap) { this.partMap = partMap; } + @Nullable public MultiValueMap getMultiValuePartMap() { return multiValuePartMap; } @@ -180,9 +207,11 @@ public void setMultiValuePartMap(MultiValueMap multiValuePartMap) * Test bean for multipart binding in a WebFlux application */ static class WebfluxMultipartBean { + @Nullable @FormParameter("file") private FilePart filePart; + @Nullable public FilePart getFilePart() { return filePart; } diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterController.java index c4141fc..78af77c 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterController.java @@ -37,6 +37,7 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; @RestController @@ -59,7 +60,7 @@ public String handleRequest(@BeanParameter FormParameterBean formParameterBean) @PostMapping(value = "/simpleMap", produces = MediaType.TEXT_PLAIN_VALUE) public String simpleMap(@BeanParameter FormParameterBean formParameterBean) { Map simpleMap = formParameterBean.getSimpleMap(); - return simpleMap.get("simple-map"); + return Objects.requireNonNull(simpleMap).get("simple-map"); } @PostMapping(value = "/multiValueMap", produces = MediaType.TEXT_PLAIN_VALUE) diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/JsonBody.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/JsonBody.java index 667ad64..220b93d 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/JsonBody.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/JsonBody.java @@ -19,13 +19,16 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.lang.Nullable; @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_EMPTY) public class JsonBody { + @Nullable @JsonProperty("json_property") private String property; + @Nullable public String getProperty() { return property; } diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/NestedBean.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/NestedBean.java index 3132b99..5e7c066 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/NestedBean.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/NestedBean.java @@ -23,27 +23,35 @@ import com.mattbertolini.spring.web.bind.annotation.RequestBody; import com.mattbertolini.spring.web.bind.annotation.RequestParameter; import com.mattbertolini.spring.web.bind.annotation.SessionParameter; +import org.springframework.lang.Nullable; @SuppressWarnings("unused") public class NestedBean { + @Nullable @RequestParameter("nested_query_param") private String queryParam; + @Nullable @FormParameter("nested_form_param") private String formData; + @Nullable @CookieParameter("nested_cookie_param") private String cookieValue; + @Nullable @HeaderParameter("nested_header_param") private String headerValue; + @Nullable @PathParameter("nested_path_param") private String pathVariable; + @Nullable @SessionParameter("nested_session_param") private String sessionAttribute; + @Nullable public String getQueryParam() { return queryParam; } @@ -52,6 +60,7 @@ public void setQueryParam(String queryParam) { this.queryParam = queryParam; } + @Nullable public String getFormData() { return formData; } @@ -60,6 +69,7 @@ public void setFormData(String formData) { this.formData = formData; } + @Nullable public String getCookieValue() { return cookieValue; } @@ -68,6 +78,7 @@ public void setCookieValue(String cookieValue) { this.cookieValue = cookieValue; } + @Nullable public String getHeaderValue() { return headerValue; } @@ -76,6 +87,7 @@ public void setHeaderValue(String headerValue) { this.headerValue = headerValue; } + @Nullable public String getPathVariable() { return pathVariable; } @@ -84,6 +96,7 @@ public void setPathVariable(String pathVariable) { this.pathVariable = pathVariable; } + @Nullable public String getSessionAttribute() { return sessionAttribute; } @@ -93,9 +106,11 @@ public void setSessionAttribute(String sessionAttribute) { } public static class RequestBodyBean { + @Nullable @RequestBody private JsonBody requestBody; + @Nullable public JsonBody getRequestBody() { return requestBody; } diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterController.java index 4768633..387126b 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/PathParameterController.java @@ -26,6 +26,7 @@ import org.springframework.web.bind.annotation.RestController; import java.util.Map; +import java.util.Objects; @SuppressWarnings("MVCPathVariableInspection") @RestController @@ -52,7 +53,7 @@ public String annotatedGetter(@BeanParameter PathParameterBean pathParameterBean @GetMapping(value = "/simpleMap/{simple_map}") public String simpleMap(@BeanParameter PathParameterBean pathParameterBean) { Map simpleMap = pathParameterBean.getSimpleMap(); - return simpleMap.get("simple_map"); + return Objects.requireNonNull(simpleMap).get("simple_map"); } @GetMapping(value = "/bindingResult/{validated}", produces = MediaType.TEXT_PLAIN_VALUE) @@ -74,9 +75,10 @@ public String validatedWithBindingResult(@Valid @BeanParameter PathParameterBean return "valid"; } + @Nullable @GetMapping(value = "/nested/{nested_path_param}", produces = MediaType.TEXT_PLAIN_VALUE) public String nestedBean(@BeanParameter PathParameterBean pathParameterBean) { - return pathParameterBean.getNestedBean().getPathVariable(); + return Objects.requireNonNull(pathParameterBean.getNestedBean()).getPathVariable(); } @GetMapping(value = "/record/{annotated_field}", produces = MediaType.TEXT_PLAIN_VALUE) diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/SessionParameterBean.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/SessionParameterBean.java index 18c0ef6..937869e 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/SessionParameterBean.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/SessionParameterBean.java @@ -19,23 +19,30 @@ import com.mattbertolini.spring.web.bind.annotation.BeanParameter; import com.mattbertolini.spring.web.bind.annotation.SessionParameter; import jakarta.validation.constraints.NotEmpty; +import org.springframework.lang.Nullable; public class SessionParameterBean { + @Nullable @SessionParameter("annotated_field") private String annotatedField; + @Nullable private String annotatedSetter; + @Nullable private String annotatedGetter; + @Nullable public String getAnnotatedField() { return annotatedField; } + @Nullable @NotEmpty @SessionParameter("validated") private String validated; + @Nullable @BeanParameter private NestedBean nestedBean; @@ -43,6 +50,7 @@ public void setAnnotatedField(String annotatedField) { this.annotatedField = annotatedField; } + @Nullable public String getAnnotatedSetter() { return annotatedSetter; } @@ -52,6 +60,7 @@ public void setAnnotatedSetter(String annotatedSetter) { this.annotatedSetter = annotatedSetter; } + @Nullable @SessionParameter("annotated_getter") public String getAnnotatedGetter() { return annotatedGetter; @@ -61,6 +70,7 @@ public void setAnnotatedGetter(String annotatedGetter) { this.annotatedGetter = annotatedGetter; } + @Nullable public String getValidated() { return validated; } @@ -69,6 +79,7 @@ public void setValidated(String validated) { this.validated = validated; } + @Nullable public NestedBean getNestedBean() { return nestedBean; } From d131ab03624a3036bb10c05969578528f09a3eba Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Mon, 22 Jul 2024 19:19:06 -0400 Subject: [PATCH 18/33] More NullAway support --- .../web/bind/CookieParameterController.java | 10 ++++++- .../web/bind/DirectFieldAccessController.java | 12 +++++++- .../web/bind/FormParameterController.java | 18 ++++++++---- .../test/web/bind/HeaderParameterBean.java | 17 +++++++++++ .../web/bind/HeaderParameterController.java | 16 +++++++--- .../test/web/bind/RequestContextBean.java | 25 ++++++++++++++++ .../web/bind/RequestContextController.java | 21 ++++++++++---- .../test/web/bind/RequestParameterBean.java | 29 ++++++++++++++++++- .../web/bind/RequestParameterController.java | 25 +++++++++++----- 9 files changed, 147 insertions(+), 26 deletions(-) diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/CookieParameterController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/CookieParameterController.java index bc68286..33b970a 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/CookieParameterController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/CookieParameterController.java @@ -20,22 +20,28 @@ import com.mattbertolini.spring.web.bind.annotation.BeanParameter; import jakarta.validation.Valid; import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.Objects; + @RestController public class CookieParameterController { + @Nullable @GetMapping(value = "/annotatedField", produces = MediaType.TEXT_PLAIN_VALUE) public String annotatedField(@BeanParameter CookieParameterBean cookieParameterBean) { return cookieParameterBean.getAnnotatedField(); } + @Nullable @GetMapping(value = "/annotatedSetter", produces = MediaType.TEXT_PLAIN_VALUE) public String annotatedSetter(@BeanParameter CookieParameterBean cookieParameterBean) { return cookieParameterBean.getAnnotatedSetter(); } + @Nullable @GetMapping(value = "/annotatedGetter", produces = MediaType.TEXT_PLAIN_VALUE) public String annotatedGetter(@BeanParameter CookieParameterBean cookieParameterBean) { return cookieParameterBean.getAnnotatedGetter(); @@ -46,6 +52,7 @@ public String bindingResult(@BeanParameter CookieParameterBean cookieParameterBe return Integer.toString(bindingResult.getErrorCount()); } + @Nullable @GetMapping(value = "/validated", produces = MediaType.TEXT_PLAIN_VALUE) public String validated(@Valid @BeanParameter CookieParameterBean cookieParameterBean) { return cookieParameterBean.getValidated(); @@ -59,9 +66,10 @@ public String validatedWithBindingResult(@Valid @BeanParameter CookieParameterBe return "valid"; } + @Nullable @GetMapping(value = "/nested", produces = MediaType.TEXT_PLAIN_VALUE) public String nestedBean(@BeanParameter CookieParameterBean cookieParameterBean) { - return cookieParameterBean.getNestedBean().getCookieValue(); + return Objects.requireNonNull(cookieParameterBean.getNestedBean()).getCookieValue(); } @GetMapping(value = "/record", produces = MediaType.TEXT_PLAIN_VALUE) diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/DirectFieldAccessController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/DirectFieldAccessController.java index ac1b24e..8b392ac 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/DirectFieldAccessController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/DirectFieldAccessController.java @@ -18,45 +18,55 @@ import com.mattbertolini.spring.web.bind.annotation.BeanParameter; import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.Objects; + @RestController public class DirectFieldAccessController { + @Nullable @GetMapping(value = "/cookieParameter", produces = MediaType.TEXT_PLAIN_VALUE) public String cookieParameter(@BeanParameter DirectFieldAccessBean directFieldAccessBean) { return directFieldAccessBean.getCookieParameter(); } + @Nullable @PostMapping(value = "/formParameter", produces = MediaType.TEXT_PLAIN_VALUE) public String formParameter(@BeanParameter DirectFieldAccessBean directFieldAccessBean) { return directFieldAccessBean.getFormParameter(); } + @Nullable @GetMapping(value = "/headerParameter", produces = MediaType.TEXT_PLAIN_VALUE) public String headerParameter(@BeanParameter DirectFieldAccessBean directFieldAccessBean) { return directFieldAccessBean.getHeaderParameter(); } + @Nullable @SuppressWarnings("MVCPathVariableInspection") @GetMapping(value = "/pathParameter/{path_parameter}", produces = MediaType.TEXT_PLAIN_VALUE) public String pathParameter(@BeanParameter DirectFieldAccessBean directFieldAccessBean) { return directFieldAccessBean.getPathParameter(); } + @Nullable @GetMapping(value = "/requestParameter", produces = MediaType.TEXT_PLAIN_VALUE) public String requestParameter(@BeanParameter DirectFieldAccessBean directFieldAccessBean) { return directFieldAccessBean.getRequestParameter(); } + @Nullable @GetMapping(value = "/sessionParameter", produces = MediaType.TEXT_PLAIN_VALUE) public String sessionParameter(@BeanParameter DirectFieldAccessBean directFieldAccessBean) { return directFieldAccessBean.getSessionParameter(); } + @Nullable @PostMapping(value = "/requestBody", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) public String requestBody(@BeanParameter DirectFieldAccessBean.RequestBodyBean directFieldAccessBean) { - return directFieldAccessBean.getRequestBody().getProperty(); + return Objects.requireNonNull(directFieldAccessBean.getRequestBody()).getProperty(); } } diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterController.java index 78af77c..b494ded 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterController.java @@ -24,6 +24,7 @@ import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.MediaType; import org.springframework.http.codec.multipart.FilePart; +import org.springframework.lang.Nullable; import org.springframework.util.MultiValueMap; import org.springframework.util.StreamUtils; import org.springframework.validation.BindingResult; @@ -42,31 +43,36 @@ @RestController public class FormParameterController { + @Nullable @PostMapping(value = "/annotatedField", produces = MediaType.TEXT_PLAIN_VALUE) public String annotatedField(@BeanParameter FormParameterBean formParameterBean) { return formParameterBean.getAnnotatedField(); } + @Nullable @PostMapping(value = "/annotatedSetter", produces = MediaType.TEXT_PLAIN_VALUE) public String annotatedSetter(@BeanParameter FormParameterBean formParameterBean) { return formParameterBean.getAnnotatedSetter(); } + @Nullable @PostMapping(value = "/annotatedGetter", produces = MediaType.TEXT_PLAIN_VALUE) public String handleRequest(@BeanParameter FormParameterBean formParameterBean) { return formParameterBean.getAnnotatedGetter(); } + @Nullable @PostMapping(value = "/simpleMap", produces = MediaType.TEXT_PLAIN_VALUE) public String simpleMap(@BeanParameter FormParameterBean formParameterBean) { Map simpleMap = formParameterBean.getSimpleMap(); return Objects.requireNonNull(simpleMap).get("simple-map"); } + @Nullable @PostMapping(value = "/multiValueMap", produces = MediaType.TEXT_PLAIN_VALUE) public String multiValueMap(@BeanParameter FormParameterBean formParameterBean) { MultiValueMap multiValueMap = formParameterBean.getMultiValueMap(); - return multiValueMap.getFirst("multi-value-map"); + return Objects.requireNonNull(multiValueMap).getFirst("multi-value-map"); } @SuppressWarnings("unused") @@ -75,6 +81,7 @@ public String bindingResult(@BeanParameter FormParameterBean formParameterBean, return Integer.toString(bindingResult.getErrorCount()); } + @Nullable @PostMapping(value = "/validated", produces = MediaType.TEXT_PLAIN_VALUE) public String validated(@Valid @BeanParameter FormParameterBean formParameterBean) { return formParameterBean.getValidated(); @@ -95,7 +102,7 @@ public String multipartFile(@BeanParameter FormParameterBean.ServletMultipartBea if (multipartFile == null || multipartFile.isEmpty()) { throw new RuntimeException("Multipart file is null or empty"); } - return new String(multipartFile.getBytes()); + return new String(multipartFile.getBytes(), StandardCharsets.UTF_8); } @PostMapping(value = "/part", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @@ -112,7 +119,7 @@ public String multipartFileMap(@BeanParameter FormParameterBean.ServletMultipart Map multipartFileMap = formParameterBean.getMultipartFileMap(); MultipartFile fileOne = multipartFileMap.get("fileOne"); MultipartFile fileTwo = multipartFileMap.get("fileTwo"); - return new String(fileOne.getBytes()) + ", " + new String(fileTwo.getBytes()); + return new String(fileOne.getBytes(), StandardCharsets.UTF_8) + ", " + new String(fileTwo.getBytes(), StandardCharsets.UTF_8); } @PostMapping(value = "/multiValueMultipartFileMap", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @@ -121,7 +128,7 @@ public String multiValueMultipartFileMap(@BeanParameter FormParameterBean.Servle List files = multiValueMultipartMap.get("file"); return files.stream().map(multipartFile -> { try { - return new String(multipartFile.getBytes()); + return new String(multipartFile.getBytes(), StandardCharsets.UTF_8); } catch (IOException e) { throw new RuntimeException(e); } @@ -162,9 +169,10 @@ public String webFluxFilePart(@BeanParameter FormParameterBean.WebfluxMultipartB return new String(data.toByteArray(), StandardCharsets.UTF_8); } + @Nullable @PostMapping(value = "/nested", produces = MediaType.TEXT_PLAIN_VALUE) public String nestedBeanParameter(@BeanParameter FormParameterBean formParameterBean) { - return formParameterBean.getNestedBean().getFormData(); + return Objects.requireNonNull(formParameterBean.getNestedBean()).getFormData(); } @PostMapping(value = "/record", produces = MediaType.TEXT_PLAIN_VALUE) diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/HeaderParameterBean.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/HeaderParameterBean.java index e2fea6c..a25aaf9 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/HeaderParameterBean.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/HeaderParameterBean.java @@ -20,34 +20,44 @@ import com.mattbertolini.spring.web.bind.annotation.HeaderParameter; import jakarta.validation.constraints.NotEmpty; import org.springframework.http.HttpHeaders; +import org.springframework.lang.Nullable; import org.springframework.util.MultiValueMap; import java.util.Map; public class HeaderParameterBean { + @Nullable @HeaderParameter("x-annotated-field") private String annotatedField; + @Nullable private String annotatedSetter; + @Nullable private String annotatedGetter; + @Nullable @HeaderParameter private Map simpleMap; + @Nullable @HeaderParameter private MultiValueMap multiValueMap; + @Nullable @HeaderParameter private HttpHeaders httpHeaders; + @Nullable @NotEmpty @HeaderParameter("validated") private String validated; + @Nullable @BeanParameter private NestedBean nestedBean; + @Nullable public String getAnnotatedField() { return annotatedField; } @@ -56,6 +66,7 @@ public void setAnnotatedField(String annotatedField) { this.annotatedField = annotatedField; } + @Nullable public String getAnnotatedSetter() { return annotatedSetter; } @@ -65,6 +76,7 @@ public void setAnnotatedSetter(String annotatedSetter) { this.annotatedSetter = annotatedSetter; } + @Nullable @HeaderParameter("x-annotated-getter") public String getAnnotatedGetter() { return annotatedGetter; @@ -74,6 +86,7 @@ public void setAnnotatedGetter(String annotatedGetter) { this.annotatedGetter = annotatedGetter; } + @Nullable public Map getSimpleMap() { return simpleMap; } @@ -82,6 +95,7 @@ public void setSimpleMap(Map simpleMap) { this.simpleMap = simpleMap; } + @Nullable public MultiValueMap getMultiValueMap() { return multiValueMap; } @@ -90,6 +104,7 @@ public void setMultiValueMap(MultiValueMap multiValueMap) { this.multiValueMap = multiValueMap; } + @Nullable public HttpHeaders getHttpHeaders() { return httpHeaders; } @@ -98,6 +113,7 @@ public void setHttpHeaders(HttpHeaders httpHeaders) { this.httpHeaders = httpHeaders; } + @Nullable public String getValidated() { return validated; } @@ -106,6 +122,7 @@ public void setValidated(String validated) { this.validated = validated; } + @Nullable public NestedBean getNestedBean() { return nestedBean; } diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/HeaderParameterController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/HeaderParameterController.java index 09f708b..2f0ddbd 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/HeaderParameterController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/HeaderParameterController.java @@ -21,46 +21,52 @@ import jakarta.validation.Valid; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; import org.springframework.util.MultiValueMap; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Map; +import java.util.Objects; @RestController public class HeaderParameterController { + @Nullable @GetMapping(value = "/annotatedField", produces = MediaType.TEXT_PLAIN_VALUE) public String annotatedField(@BeanParameter HeaderParameterBean headerParameterBean) { return headerParameterBean.getAnnotatedField(); } + @Nullable @GetMapping(value = "/annotatedSetter", produces = MediaType.TEXT_PLAIN_VALUE) public String annotatedSetter(@BeanParameter HeaderParameterBean headerParameterBean) { return headerParameterBean.getAnnotatedSetter(); } + @Nullable @GetMapping(value = "/annotatedGetter", produces = MediaType.TEXT_PLAIN_VALUE) public String annotatedGetter(@BeanParameter HeaderParameterBean headerParameterBean) { return headerParameterBean.getAnnotatedGetter(); } + @Nullable @GetMapping(value = "/simpleMap", produces = MediaType.TEXT_PLAIN_VALUE) public String simpleMap(@BeanParameter HeaderParameterBean headerParameterBean) { Map simpleMap = headerParameterBean.getSimpleMap(); - return simpleMap.get("x-simple-map"); + return Objects.requireNonNull(simpleMap).get("x-simple-map"); } @GetMapping(value = "/multiValueMap", produces = MediaType.TEXT_PLAIN_VALUE) public String multiValueMap(@BeanParameter HeaderParameterBean headerParameterBean) { MultiValueMap multiValueMap = headerParameterBean.getMultiValueMap(); - return multiValueMap.getFirst("x-multivalue-map"); + return Objects.requireNonNull(multiValueMap).getFirst("x-multivalue-map"); } @GetMapping(value = "/httpHeaders", produces = MediaType.TEXT_PLAIN_VALUE) public String httpHeaders(@BeanParameter HeaderParameterBean headerParameterBean) { HttpHeaders httpHeaders = headerParameterBean.getHttpHeaders(); - return httpHeaders.getFirst("x-http-headers"); + return Objects.requireNonNull(httpHeaders).getFirst("x-http-headers"); } @GetMapping(value = "/bindingResult", produces = MediaType.TEXT_PLAIN_VALUE) @@ -68,6 +74,7 @@ public String bindingResult(@BeanParameter HeaderParameterBean headerParameterBe return Integer.toString(bindingResult.getErrorCount()); } + @Nullable @GetMapping(value = "/validated", produces = MediaType.TEXT_PLAIN_VALUE) public String validated(@Valid @BeanParameter HeaderParameterBean headerParameterBean) { return headerParameterBean.getValidated(); @@ -81,9 +88,10 @@ public String validatedWithBindingResult(@Valid @BeanParameter HeaderParameterBe return "valid"; } + @Nullable @GetMapping(value = "/nested", produces = MediaType.TEXT_PLAIN_VALUE) public String nestedBean(@BeanParameter HeaderParameterBean headerParameterBean) { - return headerParameterBean.getNestedBean().getHeaderValue(); + return Objects.requireNonNull(headerParameterBean.getNestedBean()).getHeaderValue(); } @GetMapping(value = "/record", produces = MediaType.TEXT_PLAIN_VALUE) diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestContextBean.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestContextBean.java index b2c6fec..c1464b1 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestContextBean.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestContextBean.java @@ -19,6 +19,7 @@ import com.mattbertolini.spring.web.bind.annotation.RequestContext; import jakarta.servlet.http.HttpSession; import org.springframework.http.HttpMethod; +import org.springframework.lang.Nullable; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebSession; @@ -29,38 +30,51 @@ @SuppressWarnings("unused") public class RequestContextBean { + @Nullable @RequestContext private NativeWebRequest webRequestField; + @Nullable private NativeWebRequest webRequestSetter; + @Nullable private NativeWebRequest webRequestGetter; + @Nullable @RequestContext private ServerWebExchange exchangeField; + @Nullable private ServerWebExchange exchangeSetter; + @Nullable private ServerWebExchange exchangeGetter; + @Nullable @RequestContext private Locale locale; + @Nullable @RequestContext private TimeZone timeZone; + @Nullable @RequestContext private ZoneId zoneId; + @Nullable @RequestContext private HttpMethod method; + @Nullable @RequestContext private HttpSession httpSession; + @Nullable @RequestContext private WebSession webSession; + @Nullable public NativeWebRequest getWebRequestField() { return webRequestField; } @@ -69,6 +83,7 @@ public void setWebRequestField(NativeWebRequest webRequestField) { this.webRequestField = webRequestField; } + @Nullable public NativeWebRequest getWebRequestSetter() { return webRequestSetter; } @@ -78,6 +93,7 @@ public void setWebRequestSetter(NativeWebRequest webRequestSetter) { this.webRequestSetter = webRequestSetter; } + @Nullable @RequestContext public NativeWebRequest getWebRequestGetter() { return webRequestGetter; @@ -87,6 +103,7 @@ public void setWebRequestGetter(NativeWebRequest webRequestGetter) { this.webRequestGetter = webRequestGetter; } + @Nullable public ServerWebExchange getExchangeField() { return exchangeField; } @@ -95,6 +112,7 @@ public void setExchangeField(ServerWebExchange exchangeField) { this.exchangeField = exchangeField; } + @Nullable public ServerWebExchange getExchangeSetter() { return exchangeSetter; } @@ -104,6 +122,7 @@ public void setExchangeSetter(ServerWebExchange exchangeSetter) { this.exchangeSetter = exchangeSetter; } + @Nullable @RequestContext public ServerWebExchange getExchangeGetter() { return exchangeGetter; @@ -113,6 +132,7 @@ public void setExchangeGetter(ServerWebExchange exchangeGetter) { this.exchangeGetter = exchangeGetter; } + @Nullable public Locale getLocale() { return locale; } @@ -121,6 +141,7 @@ public void setLocale(Locale locale) { this.locale = locale; } + @Nullable public TimeZone getTimeZone() { return timeZone; } @@ -129,6 +150,7 @@ public void setTimeZone(TimeZone timeZone) { this.timeZone = timeZone; } + @Nullable public ZoneId getZoneId() { return zoneId; } @@ -137,6 +159,7 @@ public void setZoneId(ZoneId zoneId) { this.zoneId = zoneId; } + @Nullable public HttpMethod getMethod() { return method; } @@ -145,6 +168,7 @@ public void setMethod(HttpMethod method) { this.method = method; } + @Nullable public HttpSession getHttpSession() { return httpSession; } @@ -153,6 +177,7 @@ public void setHttpSession(HttpSession httpSession) { this.httpSession = httpSession; } + @Nullable public WebSession getWebSession() { return webSession; } diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestContextController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestContextController.java index 821209b..7dd8312 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestContextController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestContextController.java @@ -19,9 +19,12 @@ import com.mattbertolini.spring.test.web.bind.records.RequestContextRecord; import com.mattbertolini.spring.web.bind.annotation.BeanParameter; import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.Objects; + import static org.assertj.core.api.Assertions.assertThat; @RestController @@ -65,14 +68,16 @@ public String exchangeGetter(@BeanParameter RequestContextBean requestContextBea } // Locale + @Nullable @GetMapping(value = "/locale", produces = MediaType.TEXT_PLAIN_VALUE) public String locale(@BeanParameter RequestContextBean requestContextBean) { - return requestContextBean.getLocale().toString(); + return Objects.requireNonNull(requestContextBean.getLocale()).toString(); } + @Nullable @GetMapping(value = "/timeZone", produces = MediaType.TEXT_PLAIN_VALUE) public String timeZone(@BeanParameter RequestContextBean requestContextBean) { - return requestContextBean.getTimeZone().toString(); + return Objects.requireNonNull(requestContextBean.getTimeZone()).toString(); } @GetMapping(value = "/timeZoneRecord", produces = MediaType.TEXT_PLAIN_VALUE) @@ -80,23 +85,27 @@ public String javaRecordTimeZone(@BeanParameter RequestContextRecord requestCont return requestContextRecord.timeZone().toString(); } + @Nullable @GetMapping(value = "/zoneId", produces = MediaType.TEXT_PLAIN_VALUE) public String zoneId(@BeanParameter RequestContextBean requestContextBean) { - return requestContextBean.getZoneId().toString(); + return Objects.requireNonNull(requestContextBean.getZoneId()).toString(); } + @Nullable @GetMapping(value = "/method", produces = MediaType.TEXT_PLAIN_VALUE) public String method(@BeanParameter RequestContextBean requestContextBean) { - return requestContextBean.getMethod().toString(); + return Objects.requireNonNull(requestContextBean.getMethod()).toString(); } + @Nullable @GetMapping(value = "/session", produces = MediaType.TEXT_PLAIN_VALUE) public String httpSession(@BeanParameter RequestContextBean requestContextBean) { - return (String) requestContextBean.getHttpSession().getAttribute("name"); + return (String) Objects.requireNonNull(requestContextBean.getHttpSession()).getAttribute("name"); } + @Nullable @GetMapping(value = "/webSession", produces = MediaType.TEXT_PLAIN_VALUE) public String webSession(@BeanParameter RequestContextBean requestContextBean) { - return requestContextBean.getWebSession().getAttribute("name"); + return Objects.requireNonNull(requestContextBean.getWebSession()).getAttribute("name"); } } diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterBean.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterBean.java index 066908c..d792122 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterBean.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterBean.java @@ -20,32 +20,41 @@ import com.mattbertolini.spring.web.bind.annotation.RequestParameter; import jakarta.servlet.http.Part; import jakarta.validation.constraints.NotEmpty; +import org.springframework.lang.Nullable; import org.springframework.util.MultiValueMap; import org.springframework.web.multipart.MultipartFile; import java.util.Map; public class RequestParameterBean { + @Nullable @RequestParameter("annotated_field") private String annotatedField; + @Nullable private String annotatedSetter; + @Nullable private String annotatedGetter; + @Nullable @RequestParameter private Map simpleMap; + @Nullable @RequestParameter private MultiValueMap multiValueMap; + @Nullable @NotEmpty @RequestParameter("validated") private String validated; + @Nullable @BeanParameter private NestedBean nestedBean; + @Nullable public String getAnnotatedField() { return annotatedField; } @@ -54,6 +63,7 @@ public void setAnnotatedField(String annotatedField) { this.annotatedField = annotatedField; } + @Nullable public String getAnnotatedSetter() { return annotatedSetter; } @@ -63,6 +73,7 @@ public void setAnnotatedSetter(String annotatedSetter) { this.annotatedSetter = annotatedSetter; } + @Nullable @RequestParameter("annotated_getter") public String getAnnotatedGetter() { return annotatedGetter; @@ -72,6 +83,7 @@ public void setAnnotatedGetter(String annotatedGetter) { this.annotatedGetter = annotatedGetter; } + @Nullable public Map getSimpleMap() { return simpleMap; } @@ -80,6 +92,7 @@ public void setSimpleMap(Map simpleMap) { this.simpleMap = simpleMap; } + @Nullable public MultiValueMap getMultiValueMap() { return multiValueMap; } @@ -88,6 +101,7 @@ public void setMultiValueMap(MultiValueMap multiValueMap) { this.multiValueMap = multiValueMap; } + @Nullable public String getValidated() { return validated; } @@ -96,6 +110,7 @@ public void setValidated(String validated) { this.validated = validated; } + @Nullable public NestedBean getNestedBean() { return nestedBean; } @@ -107,25 +122,32 @@ public void setNestedBean(NestedBean nestedBean) { /** * Test bean for multipart binding in a Servlet/WebMVC application */ - static class ServletMultipartBean { + public static class ServletMultipartBean { + @Nullable @RequestParameter("file") private MultipartFile multipartFile; + @Nullable @RequestParameter("part") private Part part; + @Nullable @RequestParameter private Map multipartFileMap; + @Nullable @RequestParameter private MultiValueMap multiValueMultipartMap; + @Nullable @RequestParameter private Map partMap; + @Nullable @RequestParameter private MultiValueMap multiValuePartMap; + @Nullable public MultipartFile getMultipartFile() { return multipartFile; } @@ -134,6 +156,7 @@ public void setMultipartFile(MultipartFile multipartFile) { this.multipartFile = multipartFile; } + @Nullable public Part getPart() { return part; } @@ -142,6 +165,7 @@ public void setPart(Part part) { this.part = part; } + @Nullable public Map getMultipartFileMap() { return multipartFileMap; } @@ -150,6 +174,7 @@ public void setMultipartFileMap(Map multipartFileMap) { this.multipartFileMap = multipartFileMap; } + @Nullable public MultiValueMap getMultiValueMultipartMap() { return multiValueMultipartMap; } @@ -158,6 +183,7 @@ public void setMultiValueMultipartMap(MultiValueMap multi this.multiValueMultipartMap = multiValueMultipartMap; } + @Nullable public Map getPartMap() { return partMap; } @@ -166,6 +192,7 @@ public void setPartMap(Map partMap) { this.partMap = partMap; } + @Nullable public MultiValueMap getMultiValuePartMap() { return multiValuePartMap; } diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterController.java index dc206be..acfefc6 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterController.java @@ -21,6 +21,7 @@ import jakarta.servlet.http.Part; import jakarta.validation.Valid; import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; import org.springframework.util.MultiValueMap; import org.springframework.util.StreamUtils; import org.springframework.validation.BindingResult; @@ -33,35 +34,41 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; @RestController public class RequestParameterController { + @Nullable @GetMapping(value = "/annotatedField", produces = MediaType.TEXT_PLAIN_VALUE) public String annotatedField(@BeanParameter RequestParameterBean requestParameterBean) { return requestParameterBean.getAnnotatedField(); } + @Nullable @GetMapping(value = "/annotatedSetter", produces = MediaType.TEXT_PLAIN_VALUE) public String annotatedSetter(@BeanParameter RequestParameterBean requestParameterBean) { return requestParameterBean.getAnnotatedSetter(); } + @Nullable @GetMapping(value = "/annotatedGetter", produces = MediaType.TEXT_PLAIN_VALUE) public String annotatedGetter(@BeanParameter RequestParameterBean requestParameterBean) { return requestParameterBean.getAnnotatedGetter(); } + @Nullable @GetMapping(value = "/simpleMap", produces = MediaType.TEXT_PLAIN_VALUE) public String simpleMap(@BeanParameter RequestParameterBean requestParameterBean) { Map simpleMap = requestParameterBean.getSimpleMap(); - return simpleMap.get("simpleMap"); + return Objects.requireNonNull(simpleMap).get("simpleMap"); } + @Nullable @GetMapping(value = "/multiValueMap", produces = MediaType.TEXT_PLAIN_VALUE) public String multiValueMap(@BeanParameter RequestParameterBean requestParameterBean) { MultiValueMap multiValueMap = requestParameterBean.getMultiValueMap(); - return multiValueMap.getFirst("multiValueMap"); + return Objects.requireNonNull(multiValueMap).getFirst("multiValueMap"); } @GetMapping(value = "/bindingResult", produces = MediaType.TEXT_PLAIN_VALUE) @@ -69,6 +76,7 @@ public String bindingResult(@BeanParameter RequestParameterBean requestParameter return Integer.toString(bindingResult.getErrorCount()); } + @Nullable @GetMapping(value = "/validated", produces = MediaType.TEXT_PLAIN_VALUE) public String validated(@Valid @BeanParameter RequestParameterBean requestParameterBean) { return requestParameterBean.getValidated(); @@ -88,7 +96,7 @@ public String multipartFile(@BeanParameter RequestParameterBean.ServletMultipart if (multipartFile == null || multipartFile.isEmpty()) { throw new RuntimeException("Multipart file is null or empty"); } - return new String(multipartFile.getBytes()); + return new String(multipartFile.getBytes(), StandardCharsets.UTF_8); } @PostMapping(value = "/part", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @@ -103,9 +111,9 @@ public String part(@BeanParameter RequestParameterBean.ServletMultipartBean requ @PostMapping(value = "/multipartFileMap", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public String multipartFileMap(@BeanParameter RequestParameterBean.ServletMultipartBean requestParameterBean) throws Exception { Map multipartFileMap = requestParameterBean.getMultipartFileMap(); - MultipartFile fileOne = multipartFileMap.get("fileOne"); + MultipartFile fileOne = Objects.requireNonNull(multipartFileMap).get("fileOne"); MultipartFile fileTwo = multipartFileMap.get("fileTwo"); - return new String(fileOne.getBytes()) + ", " + new String(fileTwo.getBytes()); + return new String(fileOne.getBytes(), StandardCharsets.UTF_8) + ", " + new String(fileTwo.getBytes(), StandardCharsets.UTF_8); } @PostMapping(value = "/multiValueMultipartFileMap", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @@ -114,7 +122,7 @@ public String multiValueMultipartFileMap(@BeanParameter RequestParameterBean.Ser List files = multiValueMultipartMap.get("file"); return files.stream().map(multipartFile -> { try { - return new String(multipartFile.getBytes()); + return new String(multipartFile.getBytes(), StandardCharsets.UTF_8); } catch (IOException e) { throw new RuntimeException(e); } @@ -133,7 +141,7 @@ public String partMap(@BeanParameter RequestParameterBean.ServletMultipartBean r @PostMapping(value = "/multiValuePartMap", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public String multiValuePartMap(@BeanParameter RequestParameterBean.ServletMultipartBean requestParameterBean) { MultiValueMap multiValuePartMap = requestParameterBean.getMultiValuePartMap(); - List files = multiValuePartMap.get("file"); + List files = Objects.requireNonNull(multiValuePartMap).get("file"); return files.stream().map(part -> { try { return StreamUtils.copyToString(part.getInputStream(), StandardCharsets.UTF_8); @@ -143,9 +151,10 @@ public String multiValuePartMap(@BeanParameter RequestParameterBean.ServletMulti }).collect(Collectors.joining(", ")); } + @Nullable @GetMapping(value = "/nested", produces = MediaType.TEXT_PLAIN_VALUE) public String nestedBeanParameter(@BeanParameter RequestParameterBean requestParameterBean) { - return requestParameterBean.getNestedBean().getQueryParam(); + return Objects.requireNonNull(requestParameterBean.getNestedBean()).getQueryParam(); } @GetMapping(value = "/record", produces = MediaType.TEXT_PLAIN_VALUE) From 4e0bd4ba6430be4db5ca71a24b94b1eacd03df7c Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Mon, 22 Jul 2024 19:26:32 -0400 Subject: [PATCH 19/33] More NullAway support --- .../web/bind/FormParameterController.java | 1 + .../spring/test/web/bind/RequestBodyBean.java | 20 ++++++++++++++++++- .../test/web/bind/RequestBodyController.java | 19 +++++++++++++----- .../web/bind/RequestParameterController.java | 1 + .../web/bind/SessionParameterController.java | 10 +++++++++- 5 files changed, 44 insertions(+), 7 deletions(-) diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterController.java index b494ded..fd5693f 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/FormParameterController.java @@ -41,6 +41,7 @@ import java.util.Objects; import java.util.stream.Collectors; +@SuppressWarnings("NullAway") @RestController public class FormParameterController { @Nullable diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestBodyBean.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestBodyBean.java index 1cf0f01..a0d9c7e 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestBodyBean.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestBodyBean.java @@ -20,6 +20,7 @@ import com.mattbertolini.spring.web.bind.annotation.RequestBody; import jakarta.validation.constraints.NotNull; import org.springframework.http.codec.multipart.Part; +import org.springframework.lang.Nullable; import org.springframework.util.MultiValueMap; import reactor.core.publisher.Flux; @@ -27,9 +28,11 @@ public class RequestBodyBean { @SuppressWarnings("unused") public static class AnnotatedField { + @Nullable @RequestBody private JsonBody jsonBody; + @Nullable public JsonBody getJsonBody() { return jsonBody; } @@ -41,8 +44,10 @@ public void setJsonBody(JsonBody jsonBody) { @SuppressWarnings("unused") public static class AnnotatedSetter { + @Nullable private JsonBody jsonBody; + @Nullable public JsonBody getJsonBody() { return jsonBody; } @@ -55,8 +60,10 @@ public void setJsonBody(JsonBody jsonBody) { @SuppressWarnings("unused") public static class AnnotatedGetter { + @Nullable private JsonBody jsonBody; + @Nullable @RequestBody public JsonBody getJsonBody() { return jsonBody; @@ -69,9 +76,11 @@ public void setJsonBody(JsonBody jsonBody) { @SuppressWarnings("unused") public static class BindingResult { + @Nullable @RequestBody private JsonBody jsonBody; + @Nullable public JsonBody getJsonBody() { return jsonBody; } @@ -83,10 +92,13 @@ public void setJsonBody(JsonBody jsonBody) { @SuppressWarnings("unused") public static class Validation { + @SuppressWarnings("MultipleNullnessAnnotations") + @Nullable @NotNull @RequestBody private JsonBody jsonBody; - + + @Nullable public JsonBody getJsonBody() { return jsonBody; } @@ -98,9 +110,11 @@ public void setJsonBody(JsonBody jsonBody) { @SuppressWarnings("unused") public static class Nested { + @Nullable @BeanParameter private NestedBean.RequestBodyBean nestedBean; + @Nullable public NestedBean.RequestBodyBean getNestedBean() { return nestedBean; } @@ -112,9 +126,11 @@ public void setNestedBean(NestedBean.RequestBodyBean nestedBean) { @SuppressWarnings("unused") public static class WebFluxMultipartMultiValueMap { + @Nullable @RequestBody private MultiValueMap parts; + @Nullable public MultiValueMap getParts() { return parts; } @@ -126,9 +142,11 @@ public void setParts(MultiValueMap parts) { @SuppressWarnings("unused") public static class WebFluxMultipartFlux { + @Nullable @RequestBody private Flux parts; + @Nullable public Flux getParts() { return parts; } diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestBodyController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestBodyController.java index 8c3dffc..1bb1ea9 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestBodyController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestBodyController.java @@ -21,6 +21,7 @@ import jakarta.validation.Valid; import org.springframework.http.MediaType; import org.springframework.http.codec.multipart.Part; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.validation.BindingResult; @@ -28,26 +29,31 @@ import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; +import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; +@SuppressWarnings("NullAway") @RestController public class RequestBodyController { + @Nullable @PostMapping(value = "/annotatedField", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) public String annotatedField(@BeanParameter RequestBodyBean.AnnotatedField requestBodyBean) { JsonBody jsonBody = requestBodyBean.getJsonBody(); - return jsonBody.getProperty(); + return Objects.requireNonNull(jsonBody).getProperty(); } + @Nullable @PostMapping(value = "/annotatedSetter", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) public String annotatedSetter(@BeanParameter RequestBodyBean.AnnotatedSetter requestBodyBean) { JsonBody jsonBody = requestBodyBean.getJsonBody(); - return jsonBody.getProperty(); + return Objects.requireNonNull(jsonBody).getProperty(); } + @Nullable @PostMapping(value = "/annotatedGetter", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) public String annotatedGetter(@BeanParameter RequestBodyBean.AnnotatedGetter requestBodyBean) { JsonBody jsonBody = requestBodyBean.getJsonBody(); - return jsonBody.getProperty(); + return Objects.requireNonNull(jsonBody).getProperty(); } @SuppressWarnings("unused") @@ -56,9 +62,10 @@ public String bindingResult(@BeanParameter RequestBodyBean.BindingResult request return Integer.toString(bindingResult.getErrorCount()); } + @Nullable @PostMapping(value = "/validated", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) public String validated(@Valid @BeanParameter RequestBodyBean.Validation requestBodyBean) { - return requestBodyBean.getJsonBody().getProperty(); + return Objects.requireNonNull(requestBodyBean.getJsonBody()).getProperty(); } @SuppressWarnings("unused") @@ -70,9 +77,10 @@ public String validatedWithBindingResult(@Valid @BeanParameter RequestBodyBean.V return "valid"; } + @Nullable @PostMapping(value = "/nested", produces = MediaType.TEXT_PLAIN_VALUE) public String nestedBeanParameter(@BeanParameter RequestBodyBean.Nested requestBodyBean) { - return requestBodyBean.getNestedBean().getRequestBody().getProperty(); + return Objects.requireNonNull(Objects.requireNonNull(requestBodyBean.getNestedBean()).getRequestBody()).getProperty(); } @PostMapping(value = "/multipartMultiValueMap", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @@ -93,6 +101,7 @@ public String multipartFlux(@BeanParameter RequestBodyBean.WebFluxMultipartFlux return "multipartFlux " + count.get(); } + @Nullable @PostMapping(value = "/record", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) public String javaRecord(@BeanParameter RequestBodyRecord requestBodyRecord) { JsonBody jsonBody = requestBodyRecord.jsonBody(); diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterController.java index acfefc6..d2d8e46 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/RequestParameterController.java @@ -37,6 +37,7 @@ import java.util.Objects; import java.util.stream.Collectors; +@SuppressWarnings("NullAway") @RestController public class RequestParameterController { @Nullable diff --git a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/SessionParameterController.java b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/SessionParameterController.java index 858bddf..1a7b8c2 100644 --- a/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/SessionParameterController.java +++ b/integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/SessionParameterController.java @@ -20,22 +20,28 @@ import com.mattbertolini.spring.web.bind.annotation.BeanParameter; import jakarta.validation.Valid; import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.Objects; + @RestController public class SessionParameterController { + @Nullable @GetMapping(value = "/annotatedField", produces = MediaType.TEXT_PLAIN_VALUE) public String annotatedField(@BeanParameter SessionParameterBean sessionParameterBean) { return sessionParameterBean.getAnnotatedField(); } + @Nullable @GetMapping(value = "/annotatedSetter", produces = MediaType.TEXT_PLAIN_VALUE) public String annotatedSetter(@BeanParameter SessionParameterBean sessionParameterBean) { return sessionParameterBean.getAnnotatedSetter(); } + @Nullable @GetMapping(value = "/annotatedGetter", produces = MediaType.TEXT_PLAIN_VALUE) public String annotatedGetter(@BeanParameter SessionParameterBean sessionParameterBean) { return sessionParameterBean.getAnnotatedGetter(); @@ -46,6 +52,7 @@ public String bindingResult(@BeanParameter SessionParameterBean sessionParameter return Integer.toString(bindingResult.getErrorCount()); } + @Nullable @GetMapping(value = "/validated", produces = MediaType.TEXT_PLAIN_VALUE) public String validated(@Valid @BeanParameter SessionParameterBean sessionParameterBean) { return sessionParameterBean.getValidated(); @@ -59,9 +66,10 @@ public String validatedWithBindingResult(@Valid @BeanParameter SessionParameterB return "valid"; } + @Nullable @GetMapping(value = "/nested", produces = MediaType.TEXT_PLAIN_VALUE) public String nestedBean(@BeanParameter SessionParameterBean sessionParameterBean) { - return sessionParameterBean.getNestedBean().getSessionAttribute(); + return Objects.requireNonNull(sessionParameterBean.getNestedBean()).getSessionAttribute(); } @GetMapping(value = "/record", produces = MediaType.TEXT_PLAIN_VALUE) From 4e6ef39241dc4ea58c2c8d77f6ba34da34c76349 Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Mon, 22 Jul 2024 20:01:07 -0400 Subject: [PATCH 20/33] Add license plugin --- LICENSE_HEADER.txt | 13 +++++++++++++ buildSrc/build.gradle.kts | 1 + .../buildlogic/license-conventions.gradle.kts | 13 +++++++++++++ .../buildlogic/project-conventions.gradle.kts | 4 ++++ 4 files changed, 31 insertions(+) create mode 100644 LICENSE_HEADER.txt create mode 100644 buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/license-conventions.gradle.kts diff --git a/LICENSE_HEADER.txt b/LICENSE_HEADER.txt new file mode 100644 index 0000000..d95ab40 --- /dev/null +++ b/LICENSE_HEADER.txt @@ -0,0 +1,13 @@ +Copyright ${year} the original author or authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 1316170..cbccb00 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -9,4 +9,5 @@ repositories { dependencies { implementation("net.ltgt.gradle:gradle-errorprone-plugin:4.0.1") + implementation("gradle.plugin.com.hierynomus.gradle.plugins:license-gradle-plugin:0.16.1") } diff --git a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/license-conventions.gradle.kts b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/license-conventions.gradle.kts new file mode 100644 index 0000000..6c33189 --- /dev/null +++ b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/license-conventions.gradle.kts @@ -0,0 +1,13 @@ +package com.mattbertolini.buildlogic + +import java.time.LocalDate + +plugins { + id("com.github.hierynomus.license") +} + +license { + mapping("java", "SLASHSTAR_STYLE") + header = isolated.rootProject.projectDirectory.file("LICENSE_HEADER.txt").asFile + ext.set("year", LocalDate.now().year) +} diff --git a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/project-conventions.gradle.kts b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/project-conventions.gradle.kts index a7b73b0..42b6537 100644 --- a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/project-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/project-conventions.gradle.kts @@ -1,5 +1,9 @@ package com.mattbertolini.buildlogic +plugins { + id("com.mattbertolini.buildlogic.license-conventions") +} + // Configuring archive tasks to have repeatable builds tasks.withType().configureEach { isReproducibleFileOrder = true From 679858ceaa1e8ac91f24d08263b8b469fa7e8494 Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Mon, 22 Jul 2024 20:06:32 -0400 Subject: [PATCH 21/33] Update license headers --- .../spring/web/bind/docs/CustomRequestBean.java | 5 ++--- .../spring/web/bind/docs/ExampleController.java | 5 ++--- .../spring/web/bind/docs/ExampleService.java | 5 ++--- .../spring/web/bind/docs/NestedBean.java | 5 ++--- .../docs/webflux/ExampleWebFluxContext.java | 5 ++--- .../web/bind/docs/webmvc/ExampleMvcContext.java | 5 ++--- .../web/bind/docs/webmvc/example-context.xml | 17 +++++++++++++++++ .../WebFluxDocsJavaConfigIntegrationTest.java | 5 ++--- .../WebMvcDocsJavaConfigIntegrationTest.java | 5 ++--- .../WebMvcDocsXmlConfigIntegrationTest.java | 5 ++--- .../test/web/bind/CookieParameterBean.java | 5 ++--- .../web/bind/CookieParameterController.java | 5 ++--- .../test/web/bind/DirectFieldAccessBean.java | 5 ++--- .../web/bind/DirectFieldAccessController.java | 5 ++--- .../spring/test/web/bind/FormParameterBean.java | 5 ++--- .../test/web/bind/FormParameterController.java | 5 ++--- .../test/web/bind/HeaderParameterBean.java | 5 ++--- .../web/bind/HeaderParameterController.java | 5 ++--- .../spring/test/web/bind/JsonBody.java | 5 ++--- .../spring/test/web/bind/NestedBean.java | 5 ++--- .../spring/test/web/bind/PathParameterBean.java | 5 ++--- .../test/web/bind/PathParameterController.java | 5 ++--- .../spring/test/web/bind/RequestBodyBean.java | 5 ++--- .../test/web/bind/RequestBodyController.java | 5 ++--- .../test/web/bind/RequestContextBean.java | 5 ++--- .../test/web/bind/RequestContextController.java | 5 ++--- .../test/web/bind/RequestParameterBean.java | 5 ++--- .../web/bind/RequestParameterController.java | 5 ++--- .../test/web/bind/SessionParameterBean.java | 5 ++--- .../web/bind/SessionParameterController.java | 5 ++--- .../web/bind/records/CookieParameterRecord.java | 5 ++--- .../web/bind/records/FormParameterRecord.java | 5 ++--- .../web/bind/records/HeaderParameterRecord.java | 5 ++--- .../web/bind/records/PathParameterRecord.java | 5 ++--- .../web/bind/records/RequestBodyRecord.java | 5 ++--- .../web/bind/records/RequestContextRecord.java | 5 ++--- .../bind/records/RequestParameterRecord.java | 5 ++--- .../bind/records/SessionParameterRecord.java | 5 ++--- .../test/CookieParameterIntegrationTest.java | 5 ++--- .../test/DirectFieldAccessIntegrationTest.java | 5 ++--- .../test/FormParameterIntegrationTest.java | 5 ++--- .../test/HeaderParameterIntegrationTest.java | 5 ++--- .../test/PathParameterIntegrationTest.java | 5 ++--- .../test/RequestBodyIntegrationTest.java | 5 ++--- .../test/RequestContextIntegrationTest.java | 5 ++--- .../test/RequestParameterIntegrationTest.java | 5 ++--- .../spring/web/reactive/test/SessionFilter.java | 5 ++--- .../web/reactive/test/SessionMutator.java | 5 ++--- .../test/SessionParameterIntegrationTest.java | 5 ++--- .../test/CookieParameterIntegrationTest.java | 5 ++--- .../test/DirectFieldAccessIntegrationTest.java | 5 ++--- .../mvc/test/FormParameterIntegrationTest.java | 5 ++--- .../test/HeaderParameterIntegrationTest.java | 5 ++--- .../mvc/test/PathParameterIntegrationTest.java | 5 ++--- .../mvc/test/RequestBodyIntegrationTest.java | 5 ++--- .../mvc/test/RequestContextIntegrationTest.java | 5 ++--- .../test/RequestParameterIntegrationTest.java | 5 ++--- .../test/SessionParameterIntegrationTest.java | 5 ++--- .../bind/AbstractPropertyResolverRegistry.java | 3 +-- .../web/bind/PropertyResolutionException.java | 3 +-- .../bind/RequestPropertyBindingException.java | 3 +-- .../web/bind/annotation/BeanParameter.java | 5 ++--- .../web/bind/annotation/CookieParameter.java | 5 ++--- .../web/bind/annotation/FormParameter.java | 5 ++--- .../web/bind/annotation/HeaderParameter.java | 5 ++--- .../web/bind/annotation/PathParameter.java | 5 ++--- .../spring/web/bind/annotation/RequestBean.java | 5 ++--- .../spring/web/bind/annotation/RequestBody.java | 5 ++--- .../web/bind/annotation/RequestContext.java | 5 ++--- .../web/bind/annotation/RequestParameter.java | 5 ++--- .../web/bind/annotation/SessionParameter.java | 5 ++--- .../web/bind/annotation/package-info.java | 15 +++++++++++++++ .../AnnotatedRequestBeanIntrospector.java | 3 +-- .../web/bind/introspect/BindingProperty.java | 3 +-- .../CachedAnnotatedRequestBeanIntrospector.java | 5 ++--- .../introspect/CircularReferenceException.java | 5 ++--- ...canningAnnotatedRequestBeanIntrospector.java | 3 +-- ...DefaultAnnotatedRequestBeanIntrospector.java | 3 +-- .../RequestBeanIntrospectionException.java | 5 ++--- .../bind/introspect/ResolvedPropertyData.java | 3 +-- .../web/bind/introspect/package-info.java | 15 +++++++++++++++ .../spring/web/bind/package-info.java | 15 +++++++++++++++ .../AbstractNamedRequestPropertyResolver.java | 3 +-- .../resolver/RequestPropertyResolverBase.java | 3 +-- .../spring/web/bind/resolver/package-info.java | 15 +++++++++++++++ .../web/bind/support/MapValueResolver.java | 3 +-- .../spring/web/bind/support/package-info.java | 15 +++++++++++++++ .../AbstractPropertyResolverRegistryTest.java | 5 ++--- .../bind/introspect/BindingPropertyTest.java | 5 ++--- ...ingAnnotatedRequestBeanIntrospectorTest.java | 5 ++--- ...ultAnnotatedRequestBeanIntrospectorTest.java | 5 ++--- .../introspect/ResolvedPropertyDataTest.java | 3 +-- .../web/bind/introspect/scan/IgnoredBean.java | 5 ++--- .../web/bind/introspect/scan/ScannedBean.java | 5 ++--- .../scan/subbackage/SubpackageBean.java | 5 ++--- ...bstractNamedRequestPropertyResolverTest.java | 5 ++--- .../BeanParameterMethodArgumentResolver.java | 3 +-- .../reactive/bind/PropertyResolverRegistry.java | 5 ++--- .../bind/config/BinderConfiguration.java | 3 +-- .../web/reactive/bind/config/package-info.java | 15 +++++++++++++++ .../spring/web/reactive/bind/package-info.java | 15 +++++++++++++++ .../CookieParameterRequestPropertyResolver.java | 3 +-- ...FormParameterMapRequestPropertyResolver.java | 3 +-- .../FormParameterRequestPropertyResolver.java | 3 +-- ...aderParameterMapRequestPropertyResolver.java | 5 ++--- .../HeaderParameterRequestPropertyResolver.java | 3 +-- ...PathParameterMapRequestPropertyResolver.java | 5 ++--- .../PathParameterRequestPropertyResolver.java | 3 +-- .../RequestBodyRequestPropertyResolver.java | 3 +-- .../RequestContextRequestPropertyResolver.java | 5 ++--- ...uestParameterMapRequestPropertyResolver.java | 5 ++--- ...RequestParameterRequestPropertyResolver.java | 3 +-- .../bind/resolver/RequestPropertyResolver.java | 5 ++--- ...SessionParameterRequestPropertyResolver.java | 3 +-- .../reactive/bind/resolver/package-info.java | 15 +++++++++++++++ ...BeanParameterMethodArgumentResolverTest.java | 5 ++--- .../web/reactive/bind/MockBindingContext.java | 15 +++++++++++++++ .../bind/MockWebExchangeDataBinder.java | 15 +++++++++++++++ .../bind/PropertyResolverRegistryTest.java | 15 +++++++++++++++ .../bind/config/BinderConfigurationTest.java | 15 +++++++++++++++ ...kieParameterRequestPropertyResolverTest.java | 5 ++--- ...ParameterMapRequestPropertyResolverTest.java | 5 ++--- ...ormParameterRequestPropertyResolverTest.java | 5 ++--- ...ParameterMapRequestPropertyResolverTest.java | 5 ++--- ...derParameterRequestPropertyResolverTest.java | 5 ++--- ...ParameterMapRequestPropertyResolverTest.java | 5 ++--- ...athParameterRequestPropertyResolverTest.java | 5 ++--- .../RequestBodyRequestPropertyResolverTest.java | 5 ++--- ...questContextRequestPropertyResolverTest.java | 5 ++--- ...ParameterMapRequestPropertyResolverTest.java | 5 ++--- ...estParameterRequestPropertyResolverTest.java | 5 ++--- ...ionParameterRequestPropertyResolverTest.java | 5 ++--- .../BeanParameterMethodArgumentResolver.java | 3 +-- .../mvc/bind/PropertyResolverRegistry.java | 3 +-- .../mvc/bind/config/BinderConfiguration.java | 3 +-- .../servlet/mvc/bind/config/package-info.java | 15 +++++++++++++++ .../web/servlet/mvc/bind/package-info.java | 15 +++++++++++++++ .../CookieParameterRequestPropertyResolver.java | 3 +-- ...FormParameterMapRequestPropertyResolver.java | 5 ++--- .../FormParameterRequestPropertyResolver.java | 3 +-- ...aderParameterMapRequestPropertyResolver.java | 5 ++--- .../HeaderParameterRequestPropertyResolver.java | 3 +-- ...PathParameterMapRequestPropertyResolver.java | 5 ++--- .../PathParameterRequestPropertyResolver.java | 3 +-- .../RequestBodyRequestPropertyResolver.java | 5 ++--- .../RequestContextRequestPropertyResolver.java | 3 +-- ...uestParameterMapRequestPropertyResolver.java | 3 +-- ...RequestParameterRequestPropertyResolver.java | 3 +-- .../bind/resolver/RequestPropertyResolver.java | 3 +-- ...SessionParameterRequestPropertyResolver.java | 3 +-- .../servlet/mvc/bind/resolver/package-info.java | 15 +++++++++++++++ ...BeanParameterMethodArgumentResolverTest.java | 3 +-- .../web/servlet/mvc/bind/MockWebDataBinder.java | 15 +++++++++++++++ .../mvc/bind/MockWebDataBinderFactory.java | 15 +++++++++++++++ .../mvc/bind/PropertyResolverRegistryTest.java | 15 +++++++++++++++ .../bind/config/BinderConfigurationTest.java | 15 +++++++++++++++ ...kieParameterRequestPropertyResolverTest.java | 3 +-- ...ThrowingMockMultipartHttpServletRequest.java | 15 +++++++++++++++ ...ParameterMapRequestPropertyResolverTest.java | 3 +-- ...ormParameterRequestPropertyResolverTest.java | 5 ++--- ...ParameterMapRequestPropertyResolverTest.java | 5 ++--- ...derParameterRequestPropertyResolverTest.java | 5 ++--- ...ParameterMapRequestPropertyResolverTest.java | 5 ++--- ...athParameterRequestPropertyResolverTest.java | 5 ++--- .../RequestBodyRequestPropertyResolverTest.java | 5 ++--- ...questContextRequestPropertyResolverTest.java | 5 ++--- ...ParameterMapRequestPropertyResolverTest.java | 5 ++--- ...estParameterRequestPropertyResolverTest.java | 5 ++--- ...ionParameterRequestPropertyResolverTest.java | 5 ++--- .../WebFluxBinderAutoConfiguration.java | 3 +-- .../WebFluxBinderAutoConfigurationTest.java | 5 ++--- .../WebMvcBinderAutoConfiguration.java | 3 +-- .../WebMvcBinderAutoConfigurationTest.java | 5 ++--- 173 files changed, 582 insertions(+), 417 deletions(-) diff --git a/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/CustomRequestBean.java b/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/CustomRequestBean.java index 5b84e05..fbef8ec 100644 --- a/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/CustomRequestBean.java +++ b/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/CustomRequestBean.java @@ -1,11 +1,11 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.mattbertolini.spring.web.bind.docs; import com.mattbertolini.spring.web.bind.annotation.BeanParameter; diff --git a/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/ExampleController.java b/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/ExampleController.java index 16b679c..6e41cef 100644 --- a/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/ExampleController.java +++ b/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/ExampleController.java @@ -1,11 +1,11 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.mattbertolini.spring.web.bind.docs; import com.mattbertolini.spring.web.bind.annotation.BeanParameter; diff --git a/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/ExampleService.java b/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/ExampleService.java index 6fecc4f..f8968f2 100644 --- a/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/ExampleService.java +++ b/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/ExampleService.java @@ -1,11 +1,11 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.mattbertolini.spring.web.bind.docs; public class ExampleService { diff --git a/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/NestedBean.java b/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/NestedBean.java index 2f76d52..0c27da8 100644 --- a/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/NestedBean.java +++ b/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/NestedBean.java @@ -1,11 +1,11 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.mattbertolini.spring.web.bind.docs; import com.mattbertolini.spring.web.bind.annotation.RequestParameter; diff --git a/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/webflux/ExampleWebFluxContext.java b/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/webflux/ExampleWebFluxContext.java index edcb4af..1082e89 100644 --- a/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/webflux/ExampleWebFluxContext.java +++ b/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/webflux/ExampleWebFluxContext.java @@ -1,11 +1,11 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.mattbertolini.spring.web.bind.docs.webflux; import com.mattbertolini.spring.web.bind.docs.ExampleController; diff --git a/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/webmvc/ExampleMvcContext.java b/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/webmvc/ExampleMvcContext.java index 79edb17..08926fe 100644 --- a/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/webmvc/ExampleMvcContext.java +++ b/docs/src/main/java/com/mattbertolini/spring/web/bind/docs/webmvc/ExampleMvcContext.java @@ -1,11 +1,11 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 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 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.mattbertolini.spring.web.bind.docs.webmvc; import com.mattbertolini.spring.web.bind.docs.ExampleController; diff --git a/docs/src/main/resources/com/mattbertolini/spring/web/bind/docs/webmvc/example-context.xml b/docs/src/main/resources/com/mattbertolini/spring/web/bind/docs/webmvc/example-context.xml index dd4b8e7..f504c59 100644 --- a/docs/src/main/resources/com/mattbertolini/spring/web/bind/docs/webmvc/example-context.xml +++ b/docs/src/main/resources/com/mattbertolini/spring/web/bind/docs/webmvc/example-context.xml @@ -1,4 +1,21 @@ + Date: Mon, 22 Jul 2024 20:08:17 -0400 Subject: [PATCH 22/33] Disable license plugin --- .../mattbertolini/buildlogic/project-conventions.gradle.kts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/project-conventions.gradle.kts b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/project-conventions.gradle.kts index 42b6537..f1cc10b 100644 --- a/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/project-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/com/mattbertolini/buildlogic/project-conventions.gradle.kts @@ -1,8 +1,8 @@ package com.mattbertolini.buildlogic -plugins { - id("com.mattbertolini.buildlogic.license-conventions") -} +//plugins { +// id("com.mattbertolini.buildlogic.license-conventions") +//} // Configuring archive tasks to have repeatable builds tasks.withType().configureEach { From 84f4f6927323b0a4ba246c017cb7d29f704347fa Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Mon, 22 Jul 2024 21:20:12 -0400 Subject: [PATCH 23/33] Fix workflow --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 848cd16..fba6eca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,7 @@ on: - '*' pull_request: branches: - - $default-branch + - '*' jobs: build: From 74dc3e57919daf5474c491906c614c80985fd84d Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Mon, 22 Jul 2024 21:34:45 -0400 Subject: [PATCH 24/33] Fix issues --- .../web/bind/resolver/RequestPropertyResolverBase.java | 1 - .../DefaultAnnotatedRequestBeanIntrospectorTest.java | 1 - .../bind/BeanParameterMethodArgumentResolver.java | 2 +- .../resolver/FormParameterRequestPropertyResolver.java | 4 ++-- .../HeaderParameterRequestPropertyResolver.java | 1 - .../RequestContextRequestPropertyResolver.java | 3 +-- .../mvc/bind/BeanParameterMethodArgumentResolver.java | 10 +++++----- .../RequestParameterRequestPropertyResolver.java | 2 -- .../bind/BeanParameterMethodArgumentResolverTest.java | 1 - .../servlet/mvc/bind/PropertyResolverRegistryTest.java | 1 - .../autoconfigure/WebFluxBinderAutoConfiguration.java | 1 - .../autoconfigure/WebMvcBinderAutoConfiguration.java | 1 - 12 files changed, 9 insertions(+), 19 deletions(-) diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/RequestPropertyResolverBase.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/RequestPropertyResolverBase.java index 5488090..eff6e55 100644 --- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/RequestPropertyResolverBase.java +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/resolver/RequestPropertyResolverBase.java @@ -16,7 +16,6 @@ package com.mattbertolini.spring.web.bind.resolver; import com.mattbertolini.spring.web.bind.introspect.BindingProperty; -import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; /** diff --git a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/DefaultAnnotatedRequestBeanIntrospectorTest.java b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/DefaultAnnotatedRequestBeanIntrospectorTest.java index f559031..0cad5b5 100644 --- a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/DefaultAnnotatedRequestBeanIntrospectorTest.java +++ b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/DefaultAnnotatedRequestBeanIntrospectorTest.java @@ -21,7 +21,6 @@ import com.mattbertolini.spring.web.bind.resolver.RequestPropertyResolverBase; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import java.lang.annotation.Annotation; diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java index 1915289..e317e75 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java @@ -57,7 +57,7 @@ public boolean supportsParameter(MethodParameter parameter) { @Override protected Mono constructAttribute(WebExchangeDataBinder binder, ServerWebExchange exchange) { - Assert.state(binder.getTargetType() != null, "WebExchangeDataBinder must have a target type"); + Objects.requireNonNull(binder.getTargetType(), "WebExchangeDataBinder must have a target type"); Collection propertyData = introspector.getResolversFor(Objects.requireNonNull(binder.getTargetType().getRawClass())); return getValuesToBind(propertyData, exchange) .map(MapValueResolver::new) diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolver.java index a1d72bf..bf28974 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolver.java @@ -52,8 +52,8 @@ public Mono resolve(BindingProperty bindingProperty, ServerWebExchange e @NonNull private Object getPartValues(@NonNull List parts) { List values = parts.stream() - .map(value -> value instanceof FormFieldPart ? ((FormFieldPart) value).value() : value) - .collect(Collectors.toList()); + .map(value -> value instanceof FormFieldPart formFieldPart ? formFieldPart.value() : value) + .toList(); return values.size() == 1 ? values.get(0) : values; } } diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterRequestPropertyResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterRequestPropertyResolver.java index 45e15de..4bc6e14 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterRequestPropertyResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/HeaderParameterRequestPropertyResolver.java @@ -19,7 +19,6 @@ import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import org.springframework.http.HttpHeaders; import org.springframework.lang.NonNull; -import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestContextRequestPropertyResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestContextRequestPropertyResolver.java index f47affc..2397406 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestContextRequestPropertyResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/RequestContextRequestPropertyResolver.java @@ -79,8 +79,7 @@ public Mono resolve(@NonNull BindingProperty bindingProperty, @NonNull S @Nullable private TimeZone getTimeZone(@NonNull ServerWebExchange exchange) { LocaleContext localeContext = exchange.getLocaleContext(); - if (localeContext instanceof TimeZoneAwareLocaleContext) { - TimeZoneAwareLocaleContext timeZoneAwareLocaleContext = (TimeZoneAwareLocaleContext) localeContext; + if (localeContext instanceof TimeZoneAwareLocaleContext timeZoneAwareLocaleContext) { return timeZoneAwareLocaleContext.getTimeZone(); } return null; diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java index 6162975..cd83ca1 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java @@ -24,7 +24,7 @@ import org.springframework.beans.BeanUtils; import org.springframework.beans.MutablePropertyValues; import org.springframework.core.MethodParameter; -import org.springframework.util.Assert; +import org.springframework.core.ResolvableType; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.RequestAttributes; @@ -56,15 +56,15 @@ public boolean supportsReturnType(MethodParameter returnType) { @Override protected void constructAttribute(WebDataBinder binder, NativeWebRequest request) { - Assert.state(binder.getTargetType() != null, "WebDataBinder must have a target type"); - Map valuesToBind = memoizedGetValuesToBind(Objects.requireNonNull(binder.getTargetType().getRawClass()), request); + ResolvableType targetType = Objects.requireNonNull(binder.getTargetType(), "WebDataBinder must have a target type"); + Map valuesToBind = memoizedGetValuesToBind(Objects.requireNonNull(targetType.getRawClass()), request); binder.construct(new MapValueResolver(valuesToBind)); } @Override protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) { - Assert.state(binder.getTarget() != null, "WebDataBinder must have a target object"); - Map valuesToBind = memoizedGetValuesToBind(binder.getTarget().getClass(), request); + Object target = Objects.requireNonNull(binder.getTarget(), "WebDataBinder must have a target object"); + Map valuesToBind = memoizedGetValuesToBind(target.getClass(), request); binder.bind(new MutablePropertyValues(valuesToBind)); request.removeAttribute(BIND_VALUES_ATTRIBUTE_KEY, RequestAttributes.SCOPE_REQUEST); } diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolver.java index 70830fa..d05d999 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/resolver/RequestParameterRequestPropertyResolver.java @@ -20,9 +20,7 @@ import com.mattbertolini.spring.web.bind.introspect.BindingProperty; import com.mattbertolini.spring.web.bind.resolver.AbstractNamedRequestPropertyResolver; import jakarta.servlet.http.HttpServletRequest; -import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; -import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.multipart.support.MultipartResolutionDelegate; diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolverTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolverTest.java index dd0d6aa..1b3b10e 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolverTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolverTest.java @@ -27,7 +27,6 @@ import org.springframework.beans.PropertyValue; import org.springframework.beans.PropertyValues; import org.springframework.core.MethodParameter; -import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.validation.BindException; diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/PropertyResolverRegistryTest.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/PropertyResolverRegistryTest.java index 7e3bcec..9cdec6d 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/PropertyResolverRegistryTest.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/PropertyResolverRegistryTest.java @@ -19,7 +19,6 @@ import com.mattbertolini.spring.web.servlet.mvc.bind.resolver.RequestPropertyResolver; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.web.context.request.NativeWebRequest; diff --git a/webflux-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/reactive/bind/autoconfigure/WebFluxBinderAutoConfiguration.java b/webflux-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/reactive/bind/autoconfigure/WebFluxBinderAutoConfiguration.java index 029b537..b61fab7 100644 --- a/webflux-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/reactive/bind/autoconfigure/WebFluxBinderAutoConfiguration.java +++ b/webflux-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/reactive/bind/autoconfigure/WebFluxBinderAutoConfiguration.java @@ -30,7 +30,6 @@ import java.util.ArrayList; import java.util.LinkedHashSet; -import java.util.LinkedList; import java.util.List; import java.util.Set; diff --git a/webmvc-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/autoconfigure/WebMvcBinderAutoConfiguration.java b/webmvc-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/autoconfigure/WebMvcBinderAutoConfiguration.java index c90deb6..64d1d57 100644 --- a/webmvc-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/autoconfigure/WebMvcBinderAutoConfiguration.java +++ b/webmvc-annotated-data-binder-spring-boot-starter/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/autoconfigure/WebMvcBinderAutoConfiguration.java @@ -30,7 +30,6 @@ import java.util.ArrayList; import java.util.LinkedHashSet; -import java.util.LinkedList; import java.util.List; import java.util.Set; From 95b6fb59576c8af9c5bfb4aeafef11e78ccf7898 Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Mon, 22 Jul 2024 21:42:12 -0400 Subject: [PATCH 25/33] Fix issues --- .../bind/BeanParameterMethodArgumentResolver.java | 9 +++++---- .../resolver/FormParameterRequestPropertyResolver.java | 1 - 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java index e317e75..3dd62dc 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java @@ -26,6 +26,7 @@ import org.springframework.beans.MutablePropertyValues; import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; +import org.springframework.core.ResolvableType; import org.springframework.lang.NonNull; import org.springframework.util.Assert; import org.springframework.web.bind.support.WebExchangeDataBinder; @@ -57,8 +58,8 @@ public boolean supportsParameter(MethodParameter parameter) { @Override protected Mono constructAttribute(WebExchangeDataBinder binder, ServerWebExchange exchange) { - Objects.requireNonNull(binder.getTargetType(), "WebExchangeDataBinder must have a target type"); - Collection propertyData = introspector.getResolversFor(Objects.requireNonNull(binder.getTargetType().getRawClass())); + ResolvableType targetType = Objects.requireNonNull(binder.getTargetType(), "WebExchangeDataBinder must have a target type"); + Collection propertyData = introspector.getResolversFor(Objects.requireNonNull(targetType.getRawClass())); return getValuesToBind(propertyData, exchange) .map(MapValueResolver::new) .doOnNext(binder::construct) @@ -68,8 +69,8 @@ protected Mono constructAttribute(WebExchangeDataBinder binder, ServerWebE @Override @NonNull protected Mono bindRequestParameters(WebExchangeDataBinder binder, ServerWebExchange exchange) { - Assert.state(binder.getTarget() != null, "WebExchangeDataBinder must have a target object"); - Collection propertyData = introspector.getResolversFor(binder.getTarget().getClass()); + Object target = Objects.requireNonNull(binder.getTarget(), "WebExchangeDataBinder must have a target object"); + Collection propertyData = introspector.getResolversFor(target.getClass()); return getValuesToBind(propertyData, exchange) .map(MutablePropertyValues::new) .doOnNext(binder::bind) diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolver.java index bf28974..ab011e2 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/resolver/FormParameterRequestPropertyResolver.java @@ -26,7 +26,6 @@ import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; public class FormParameterRequestPropertyResolver implements RequestPropertyResolver { @Override From ce2a473a911ff9e395aab2431b9d18283bd6688c Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Mon, 22 Jul 2024 21:48:03 -0400 Subject: [PATCH 26/33] Remove unused import --- .../web/reactive/bind/BeanParameterMethodArgumentResolver.java | 1 - 1 file changed, 1 deletion(-) diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java index 3dd62dc..cdedf6e 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java @@ -28,7 +28,6 @@ import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ResolvableType; import org.springframework.lang.NonNull; -import org.springframework.util.Assert; import org.springframework.web.bind.support.WebExchangeDataBinder; import org.springframework.web.reactive.result.method.annotation.ModelAttributeMethodArgumentResolver; import org.springframework.web.server.ServerWebExchange; From aa2ab9ed1932aea9b2c2daf91717104d72b15aa9 Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Mon, 22 Jul 2024 21:51:03 -0400 Subject: [PATCH 27/33] Update to record --- .../bind/introspect/ResolvedPropertyData.java | 52 ++----------------- ...tAnnotatedRequestBeanIntrospectorTest.java | 4 +- .../introspect/ResolvedPropertyDataTest.java | 8 +-- .../BeanParameterMethodArgumentResolver.java | 6 +-- .../BeanParameterMethodArgumentResolver.java | 6 +-- 5 files changed, 16 insertions(+), 60 deletions(-) diff --git a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/ResolvedPropertyData.java b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/ResolvedPropertyData.java index 68a505a..d9b84cb 100644 --- a/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/ResolvedPropertyData.java +++ b/spring-annotated-data-binder-core/src/main/java/com/mattbertolini/spring/web/bind/introspect/ResolvedPropertyData.java @@ -17,51 +17,7 @@ import com.mattbertolini.spring.web.bind.resolver.RequestPropertyResolverBase; -import java.util.Objects; - -public final class ResolvedPropertyData { - private final String propertyName; - private final BindingProperty bindingProperty; - private final RequestPropertyResolverBase resolver; - - public ResolvedPropertyData( - String propertyName, - BindingProperty bindingProperty, - RequestPropertyResolverBase resolver - ) { - this.propertyName = propertyName; - this.bindingProperty = bindingProperty; - this.resolver = resolver; - } - - public String getPropertyName() { - return propertyName; - } - - public BindingProperty getBindingProperty() { - return bindingProperty; - } - - public RequestPropertyResolverBase getResolver() { - return resolver; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof ResolvedPropertyData)) { - return false; - } - ResolvedPropertyData that = (ResolvedPropertyData) o; - return Objects.equals(propertyName, that.propertyName) && - Objects.equals(bindingProperty, that.bindingProperty) && - Objects.equals(resolver, that.resolver); - } - - @Override - public int hashCode() { - return Objects.hash(propertyName, bindingProperty, resolver); - } -} +public record ResolvedPropertyData( + String propertyName, + BindingProperty bindingProperty, + RequestPropertyResolverBase resolver) {} diff --git a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/DefaultAnnotatedRequestBeanIntrospectorTest.java b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/DefaultAnnotatedRequestBeanIntrospectorTest.java index 0cad5b5..9379785 100644 --- a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/DefaultAnnotatedRequestBeanIntrospectorTest.java +++ b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/DefaultAnnotatedRequestBeanIntrospectorTest.java @@ -52,7 +52,7 @@ void returnsResolversForType() { Collection resolvers = introspector.getResolversFor(SimpleType.class); assertThat(resolvers).hasSize(1); ResolvedPropertyData data = resolvers.iterator().next(); - assertThat(data.getPropertyName()).isEqualTo("data"); + assertThat(data.propertyName()).isEqualTo("data"); } @Test @@ -62,7 +62,7 @@ void returnsResolversUsingNestedTypes() { Collection resolvers = introspector.getResolversFor(OuterBean.class); assertThat(resolvers).hasSize(1); ResolvedPropertyData data = resolvers.iterator().next(); - assertThat(data.getPropertyName()).isEqualTo("innerBean.inner"); + assertThat(data.propertyName()).isEqualTo("innerBean.inner"); } private static class FakeResolver implements RequestPropertyResolverBase { diff --git a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/ResolvedPropertyDataTest.java b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/ResolvedPropertyDataTest.java index 2a7122a..f6737ea 100644 --- a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/ResolvedPropertyDataTest.java +++ b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/introspect/ResolvedPropertyDataTest.java @@ -39,20 +39,20 @@ void setUp() throws Exception { @Test void returnsPropertyName() { - assertThat(propertyData.getPropertyName()).isEqualTo("name"); + assertThat(propertyData.propertyName()).isEqualTo("name"); } @Test void returnsBindingProperty() throws Exception { BindingProperty expected = BindingProperty.forPropertyDescriptor( new PropertyDescriptor("property", TestingClass.class)); - assertThat(propertyData.getBindingProperty()).isEqualTo(expected); + assertThat(propertyData.bindingProperty()).isEqualTo(expected); } @Test void returnsResolver() { - assertThat(propertyData.getResolver()).isNotNull(); - assertThat(propertyData.getResolver()).isInstanceOf(StubResolver.class); + assertThat(propertyData.resolver()).isNotNull(); + assertThat(propertyData.resolver()).isInstanceOf(StubResolver.class); } @Test diff --git a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java index cdedf6e..bdcc049 100644 --- a/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java +++ b/spring-webflux-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/reactive/bind/BeanParameterMethodArgumentResolver.java @@ -78,9 +78,9 @@ protected Mono bindRequestParameters(WebExchangeDataBinder binder, ServerW private Mono> getValuesToBind(Collection propertyData, ServerWebExchange exchange) { return Flux.fromIterable(propertyData).flatMap(data -> { - BindingProperty bindingProperty = data.getBindingProperty(); - RequestPropertyResolver resolver = (RequestPropertyResolver) data.getResolver(); - return resolver.resolve(bindingProperty, exchange).map(resolvedValue -> Tuples.of(data.getPropertyName(), resolvedValue)); + BindingProperty bindingProperty = data.bindingProperty(); + RequestPropertyResolver resolver = (RequestPropertyResolver) data.resolver(); + return resolver.resolve(bindingProperty, exchange).map(resolvedValue -> Tuples.of(data.propertyName(), resolvedValue)); }).collectMap(Tuple2::getT1, Tuple2::getT2) .onErrorMap(e -> new RequestPropertyBindingException("Unable to resolve property. " + e.getMessage(), e)) .doOnSuccess(valuesMap -> valuesMap.values().removeIf(Objects::isNull)); diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java index cd83ca1..8eee749 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java @@ -84,11 +84,11 @@ private Map getValuesToBind(Class targetType, NativeWebReques Map values = new HashMap<>(); Collection propertyData = introspector.getResolversFor(targetType); for (ResolvedPropertyData data : propertyData) { - RequestPropertyResolver resolver = (RequestPropertyResolver) data.getResolver(); + RequestPropertyResolver resolver = (RequestPropertyResolver) data.resolver(); try { - Object value = resolver.resolve(data.getBindingProperty(), request); + Object value = resolver.resolve(data.bindingProperty(), request); if (value != null) { - String propertyName = data.getPropertyName(); + String propertyName = data.propertyName(); values.put(propertyName, value); } } catch (Exception e) { From 55b86b162df3fc94ef20a70be4f26003b546381a Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Mon, 22 Jul 2024 21:58:25 -0400 Subject: [PATCH 28/33] Update constant usage --- .../servlet/mvc/bind/BeanParameterMethodArgumentResolver.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java index 8eee749..024a212 100644 --- a/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java +++ b/spring-webmvc-annotated-data-binder/src/main/java/com/mattbertolini/spring/web/servlet/mvc/bind/BeanParameterMethodArgumentResolver.java @@ -71,12 +71,12 @@ protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest requ @SuppressWarnings("unchecked") private Map memoizedGetValuesToBind(Class targetType, NativeWebRequest request) { - /* Nullable */ Map memoizedValues = (Map) request.getAttribute(BIND_VALUES_ATTRIBUTE_KEY, NativeWebRequest.SCOPE_REQUEST); + /* Nullable */ Map memoizedValues = (Map) request.getAttribute(BIND_VALUES_ATTRIBUTE_KEY, RequestAttributes.SCOPE_REQUEST); if (memoizedValues != null) { return memoizedValues; } Map valuesToBind = getValuesToBind(targetType, request); - request.setAttribute(BIND_VALUES_ATTRIBUTE_KEY, valuesToBind, NativeWebRequest.SCOPE_REQUEST); + request.setAttribute(BIND_VALUES_ATTRIBUTE_KEY, valuesToBind, RequestAttributes.SCOPE_REQUEST); return valuesToBind; } From 10a88caa454de7c85324e6afb6ccb44cf2bdebb4 Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Mon, 22 Jul 2024 22:04:19 -0400 Subject: [PATCH 29/33] Remove unused methods --- .../spring/web/reactive/bind/MockWebExchangeDataBinder.java | 5 ----- .../spring/web/servlet/mvc/bind/MockWebDataBinder.java | 5 ----- 2 files changed, 10 deletions(-) diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java index d483add..7d257ba 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java @@ -41,11 +41,6 @@ public MockWebExchangeDataBinder(@Nullable Object target) { validationHints = new ArrayList<>(); } - @Override - public Mono construct(ServerWebExchange exchange) { - return super.construct(exchange); // TODO - } - @Override public void bind(PropertyValues pvs) { this.pvs = pvs; diff --git a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinder.java b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinder.java index aa7021c..edb3569 100644 --- a/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinder.java +++ b/spring-webmvc-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/servlet/mvc/bind/MockWebDataBinder.java @@ -45,11 +45,6 @@ public MockWebDataBinder(@Nullable Object target, String objectName) { validationHints = new ArrayList<>(); } - @Override - public void construct(ValueResolver valueResolver) { - super.construct(valueResolver); // TODO - } - @Override public void bind(PropertyValues pvs) { this.pvs = pvs; From e4b42134133b2180b21f019740503b1af8ca57ca Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Mon, 22 Jul 2024 22:12:19 -0400 Subject: [PATCH 30/33] Remove unused imports --- .../spring/web/reactive/bind/MockWebExchangeDataBinder.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java index 7d257ba..e706ec2 100644 --- a/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java +++ b/spring-webflux-annotated-data-binder/src/test/java/com/mattbertolini/spring/web/reactive/bind/MockWebExchangeDataBinder.java @@ -20,8 +20,6 @@ import org.springframework.lang.Nullable; import org.springframework.validation.BindingResult; import org.springframework.web.bind.support.WebExchangeDataBinder; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Mono; import java.util.ArrayList; import java.util.Arrays; From a483875cbd7ed03e4e671fea61523000f27034f6 Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Tue, 23 Jul 2024 08:05:46 -0400 Subject: [PATCH 31/33] Add test for MapValueResolver --- .../bind/support/MapValueResolverTest.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/support/MapValueResolverTest.java diff --git a/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/support/MapValueResolverTest.java b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/support/MapValueResolverTest.java new file mode 100644 index 0000000..61811c1 --- /dev/null +++ b/spring-annotated-data-binder-core/src/test/java/com/mattbertolini/spring/web/bind/support/MapValueResolverTest.java @@ -0,0 +1,32 @@ +package com.mattbertolini.spring.web.bind.support; + +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class MapValueResolverTest { + @Test + void resolveValueReturnsMapValueFromKey() { + Map map = Map.of("key1", "value1", "key2", "value2"); + MapValueResolver valueResolver = new MapValueResolver(map); + assertThat(valueResolver.resolveValue("key1", String.class)).isEqualTo("value1"); + } + + @Test + void getNamesReturnsMapKeys() { + Map map = Map.of("key1", "value1", "key2", "value2"); + MapValueResolver valueResolver = new MapValueResolver(map); + assertThat(valueResolver.getNames()).contains("key1", "key2"); + } + + @Test + void valuesAccessorIsUnmodifiable() { + Map map = Map.of("key1", "value1", "key2", "value2"); + MapValueResolver valueResolver = new MapValueResolver(map); + assertThat(valueResolver.values()) + .isUnmodifiable() + .isEqualTo(map); + } +} From be435d1b2b15b93f60554cdf23d4d35921c3e5da Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Tue, 23 Jul 2024 19:20:33 -0400 Subject: [PATCH 32/33] Update version --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2deccc7..761eb9e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { allprojects { group = "com.mattbertolini" - version = "0.7.0-SNAPSHOT" + version = "1.0.0-SNAPSHOT" } val rootJacocoDir = "reports/jacoco/testCodeCoverageReport" From 6895cec6416693f9d913ac67368be4268a4d66cc Mon Sep 17 00:00:00 2001 From: Matt Bertolini Date: Tue, 23 Jul 2024 20:13:24 -0400 Subject: [PATCH 33/33] Update publish script --- publish.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 publish.sh diff --git a/publish.sh b/publish.sh old mode 100644 new mode 100755 index c116e88..dedec2d --- a/publish.sh +++ b/publish.sh @@ -1,4 +1,4 @@ #!/usr/bin/env bash # Disabling parallel builds as it doesn't work when publishing to Maven Central -./gradlew --no-parallel build publish \ No newline at end of file +./gradlew --no-parallel --no-configuration-cache build publish \ No newline at end of file