Compare commits

...

67 Commits

Author SHA1 Message Date
Spring Builds e0f56e7d80 Release v5.3.20 2022-05-11 06:55:19 +00:00
Juergen Hoeller 83186b689f Refine CachedIntrospectionResults property introspection
Closes gh-28445
2022-05-11 08:32:06 +02:00
rstoyanchev dc2947c52d Ignore invalid connect frame
Closes gh-28443
2022-05-11 07:22:22 +01:00
Arjen Poutsma e4ec376075 Disabling Undertow server in CoroutinesIntegrationTests 2022-05-10 15:55:00 +02:00
Juergen Hoeller c81e11d537 Polishing 2022-05-10 13:07:00 +02:00
Juergen Hoeller de6180b093 Upgrade to Reactor 2020.0.19
Includes Netty 4.1.77 and RxJava 3.1.4.

Closes gh-28437
2022-05-10 13:05:56 +02:00
Sam Brannen 1c10cdd1e8 Update copyright dates
See gh-28433
2022-05-10 11:33:32 +02:00
evgeny.bovykin 941b92cbed Make inner classes static when feasible
A static nested class does not keep an implicit reference to its
enclosing instance.

This prevents a common cause of memory leaks and uses less memory per
instance of the class.

Closes gh-28433
2022-05-10 11:32:37 +02:00
Sam Brannen e26d8839b3 Stop referring to features as Java 6/7 features where unnecessary 2022-05-09 19:09:06 +02:00
Sam Brannen a1c7380398 Add test for value attribute in @ModelAttribute in WebFlux
This complements the previous commit which tested only the `name`
attribute.

See gh-28423
2022-05-09 15:22:22 +02:00
Sam Brannen 7dd622bdb2 Support name attribute in @ModelAttribute in WebFlux
Prior to this commit, the `name` attribute in @ModelAttribute was not
supported when using WebFlux. This is because MethodParameter was used
instead of SynthesizingMethodParameter when retrieving the
@ModelAttribute annotation. In other words, @AliasFor was not honored
because the annotation was not synthesized. Consequently, only the
`value` attribute was supported in WebFlux when specifying a custom name
via @ModelAttribute.

This commit fixes this by using SynthesizingMethodParameter to retrieve
the @ModelAttribute annotation.

Closes gh-28423
2022-05-07 17:16:26 +02:00
Sam Brannen 64c96c579d Polish contribution
See gh-28422
2022-05-07 16:18:47 +02:00
Carlos Bouzón García 39e3876301 Fix BindingResult error when ModelAttribute has custom name in WebFlux
Closes gh-28422
2022-05-07 16:18:04 +02:00
Juergen Hoeller f771603789 Polishing 2022-05-05 18:04:54 +02:00
Juergen Hoeller e441832e99 Accept WritableResource as required dependency type as well
Closes gh-15284
2022-05-05 18:04:13 +02:00
Juergen Hoeller f963fc5f98 Check that nullable annotations are from org.springframework.lang
Closes gh-28410
2022-05-05 18:02:27 +02:00
Juergen Hoeller b55eee1b0d Upgrade to ASM 9.3
Closes gh-28390
2022-05-05 18:01:53 +02:00
Juergen Hoeller 28742171fd Upgrade to Netty 4.1.76, Mockito 4.5.1, HtmlUnit 2.61 2022-05-05 18:01:31 +02:00
Lee, Kyutae 7a75b94556 Fix typo in reference docs regarding RequestMappingHandlerAdapter
Closes gh-28370
2022-05-05 16:31:40 +02:00
Sam Brannen ab71ff93bb Polish package-info for org.springframework.util
See gh-28411
2022-05-05 16:22:52 +02:00
neals cf30327740 Remove Log4J configurer from package-info.java in spring-core
Closes gh-28411
2022-05-05 16:16:49 +02:00
neals e22a038725 Remove Log4J initialization from package-info.java
Closes gh-28420
2022-05-05 16:10:59 +02:00
Brian Clozel 7aedb9ee33 Build CI image using oci-build-task resource 2022-05-03 15:50:29 +02:00
Brian Clozel 5b1719cd77 Upgrade CI to concourse-release-scripts 0.3.4 2022-05-03 15:09:23 +02:00
Brian Clozel c056b81753 Upgrade JDK and Ubuntu versions in CI image 2022-05-03 15:08:14 +02:00
Brian Clozel dbdd67ec62 Remove outdated docker-image resource from CI
The docker-image resource is now replaced by the registry-image
resource.
2022-05-03 15:05:58 +02:00
Sam Brannen 4fcfa5b991 Update and simplify ArrayConstructorTests 2022-05-03 14:27:48 +02:00
Sam Brannen ed06a6de26 Convert SimpleFormController example to @Controller in reference manual
This change is necessary since the SimpleFormController class no longer
exists.
2022-05-03 12:03:04 +02:00
Sam Brannen afa799b4f0 Suppress warning in test 2022-05-03 11:46:47 +02:00
rstoyanchev f0d149b330 Polishing contribution
Closes gh-27830
2022-04-28 11:26:50 +01:00
binchoo caaf83b8e6 Add tests for binding to a Part field
See gh-27830
2022-04-28 10:45:09 +01:00
rstoyanchev b30f4d7bb7 Exposes all root causes to ExceptionHandler methods
Closes gh-28155
2022-04-28 04:04:57 +01:00
Arjen Poutsma b6b03f38d7 Updated .sdkmanrc 2022-04-26 11:54:02 +02:00
Stephane Nicoll 46b0d7dc03 Merge pull request #28369 from izeye
* pr/28369:
  Add Javadoc since for GraphQL constants

Closes gh-28369
2022-04-24 10:05:28 +02:00
izeye fcf64798b5 Add Javadoc since for GraphQL constants
See gh-28369
2022-04-24 09:58:50 +02:00
Stephane Nicoll 94ed07d9cb Merge pull request #28372 from zhangmingqi09
* pr/28372:
  Update copyright year of changed file
  Fix github issue reference in RequestMappingHandlerMapping

Closes gh-28372
2022-04-24 09:57:02 +02:00
Stephane Nicoll b81c62d064 Update copyright year of changed file
See gh-28372
2022-04-24 09:56:39 +02:00
zhangmingqi09 ca1a11acac Fix github issue reference in RequestMappingHandlerMapping
See gh-28372
2022-04-24 09:55:20 +02:00
Stephane Nicoll c261d40610 Merge pull request #28340 from koenpunt
* pr/28340:
  Update copyright year of changed file
  Fix method reference in Kotlin documentation

Closes gh-28340
2022-04-14 14:11:28 +02:00
Stephane Nicoll 3017955eff Update copyright year of changed file
See gh-28340
2022-04-14 14:09:54 +02:00
Koen Punt 22c82ff206 Fix method reference in Kotlin documentation
See gh-28340
2022-04-14 14:09:02 +02:00
Jerome Prinet f54952481b Update Gradle Enterprise plugin to 3.9
Closes gh-28338
2022-04-14 11:04:22 +02:00
Spring Builds 9e733b09e2 Next development version (v5.3.20-SNAPSHOT) 2022-04-13 09:24:33 +00:00
Sam Brannen a7cf19cec5 Improve documentation and matching algorithm in data binders 2022-04-13 09:55:40 +02:00
Juergen Hoeller 0cf7f7bd89 Polishing 2022-04-13 00:24:23 +02:00
Juergen Hoeller 949c3d450c Align plain accessor check 2022-04-13 00:24:06 +02:00
Arjen Poutsma 3b4ae7b028 TomcatHttpHandlerAdapter continues after 0 bytes
This commit makes sure that TomcatServerHttpRequest::readFromInputStream
follows the same contract as the method it overrides, and returns
AbstractListenerReadPublisher.EMPTY_BUFFER when 0 bytes are read.

See gh-28241
2022-04-12 16:52:13 +02:00
Stephane Nicoll 8b39698553 Upgrade to Reactor 2020.0.18
Closes gh-28329
2022-04-12 15:53:10 +02:00
Sam Brannen 6fad00ed22 Ensure dynamic proxy with AOP introduction includes lambda interfaces
Closes gh-28209
2022-04-09 09:57:43 +02:00
Sam Brannen 5f6d8df34b Introduce isLambdaClass() as a public utility in ClassUtils
This commit extracts isLambda() from AopProxyUtils and makes it
publicly available as ClassUtils.isLambdaClass().

This is a prerequisite for gh-28209.
2022-04-09 09:57:43 +02:00
Juergen Hoeller 35de7e19ee Introduce initializer callback for Bean Validation Configuration
Closes gh-27956
2022-04-08 14:39:36 +02:00
Juergen Hoeller 10e979e58b Polishing 2022-04-08 14:37:41 +02:00
Juergen Hoeller 4143b445d6 Polishing 2022-04-08 13:04:14 +02:00
Juergen Hoeller eefdd2c768 Avoid return value reference in potentially cached MethodParameter instance
Closes gh-28232
2022-04-08 13:03:13 +02:00
Juergen Hoeller 9f91168396 Restore ability to configure setClassLoader methods
Closes gh-28269
2022-04-08 13:02:53 +02:00
Juergen Hoeller 7aed6279a2 Consistent fallback in case of fast-class generation failure
Closes gh-28138
2022-04-08 13:02:36 +02:00
Juergen Hoeller c3fe112fd7 Consistent use of getLocalAddr() without DNS lookups in request adapters
Closes gh-28280
2022-04-08 13:00:50 +02:00
Juergen Hoeller 90103b0ae9 Consistent support for direct column matches in DataClassRowMapper
Closes gh-28243
2022-04-08 13:00:31 +02:00
Juergen Hoeller 0cf15c0fdd Upgrade to Tomcat 9.0.62, Jetty 9.4.46, Jetty Reactive HttpClient 1.1.11, Undertow 2.2.17, R2DBC Arabba-SR13, RSocket 1.1.2, OpenPDF 1.3.27, HtmlUnit 2.60, Checkstyle 10.1 2022-04-08 13:00:20 +02:00
Stephane Nicoll 4e9af3e277 Upgrade Java 18 in CI image 2022-04-07 08:52:43 +02:00
Stephane Nicoll 01fd489b47 Upgrade Ubuntu version in CI images 2022-04-07 08:51:48 +02:00
Brian Clozel 270b167e08 Upgrade registry-image-resource in CI pipeline to 1.5.0
See spring-projects/spring-boot#30408
2022-04-04 19:01:03 +02:00
GatinMI b158110801 Fix debug log for no matching acceptableTypes 2022-04-04 18:29:01 +02:00
Brian Clozel 17f7a24118 Add application/graphql+json mime and media types
Closes gh-28271
2022-04-01 19:24:55 +02:00
rstoyanchev d518a7d8c8 AbstractListenerReadPublisher continues after 0 bytes
If we read 0 bytes, e.g. chunked encoding markup read but not the
actual data within it, don't stop reading since the server may or
may not consider it necessary to call onDataAvailable again.
Instead, we keep on reading, and although isReady likely returns
false on the next iteration, it eliminates ambiguity and ensures
the server will call onDataAvailable when more data arrives.

Closes gh-28241
2022-04-01 17:38:03 +01:00
rstoyanchev 24cd3c1f4c Revert "Disable flaky integration tests for now"
This reverts commit 1627f57f1f in
preparation for fixing the root cause
2022-04-01 16:07:05 +01:00
Spring Builds 2a8c369cff Next development version (v5.3.19-SNAPSHOT) 2022-03-31 09:04:49 +00:00
118 changed files with 1602 additions and 940 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=8.0.322-librca
java=8.0.332-librca
+15 -15
View File
@@ -28,11 +28,11 @@ configure(allprojects) { project ->
dependencyManagement {
imports {
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.6"
mavenBom "io.netty:netty-bom:4.1.75.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.17"
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR12"
mavenBom "io.rsocket:rsocket-bom:1.1.1"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.45.v20220203"
mavenBom "io.netty:netty-bom:4.1.77.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.19"
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR13"
mavenBom "io.rsocket:rsocket-bom:1.1.2"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.46.v20220331"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.5.32"
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.2"
mavenBom "org.jetbrains.kotlinx:kotlinx-serialization-bom:1.2.2"
@@ -67,7 +67,7 @@ configure(allprojects) { project ->
dependency "io.reactivex:rxjava:1.3.8"
dependency "io.reactivex:rxjava-reactive-streams:1.2.1"
dependency "io.reactivex.rxjava2:rxjava:2.2.21"
dependency "io.reactivex.rxjava3:rxjava:3.1.3"
dependency "io.reactivex.rxjava3:rxjava:3.1.4"
dependency "io.smallrye.reactive:mutiny:1.4.0"
dependency "io.projectreactor.tools:blockhound:1.0.6.RELEASE"
@@ -96,7 +96,7 @@ configure(allprojects) { project ->
dependency "com.h2database:h2:2.1.210"
dependency "com.github.ben-manes.caffeine:caffeine:2.9.3"
dependency "com.github.librepdf:openpdf:1.3.26"
dependency "com.github.librepdf:openpdf:1.3.27"
dependency "com.rometools:rome:1.18.0"
dependency "commons-io:commons-io:2.5"
dependency "io.vavr:vavr:0.10.4"
@@ -128,18 +128,18 @@ configure(allprojects) { project ->
dependency "org.webjars:webjars-locator-core:0.48"
dependency "org.webjars:underscorejs:1.8.3"
dependencySet(group: 'org.apache.tomcat', version: '9.0.60') {
dependencySet(group: 'org.apache.tomcat', version: '9.0.62') {
entry 'tomcat-util'
entry('tomcat-websocket') {
exclude group: "org.apache.tomcat", name: "tomcat-servlet-api"
exclude group: "org.apache.tomcat", name: "tomcat-websocket-api"
}
}
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.60') {
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.62') {
entry 'tomcat-embed-core'
entry 'tomcat-embed-websocket'
}
dependencySet(group: 'io.undertow', version: '2.2.16.Final') {
dependencySet(group: 'io.undertow', version: '2.2.17.Final') {
entry 'undertow-core'
entry('undertow-servlet') {
exclude group: "org.jboss.spec.javax.servlet", name: "jboss-servlet-api_4.0_spec"
@@ -150,7 +150,7 @@ configure(allprojects) { project ->
}
}
dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.10"
dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.11"
dependency 'org.apache.httpcomponents.client5:httpclient5:5.1.3'
dependency 'org.apache.httpcomponents.core5:httpcore5-reactive:5.1.3'
dependency("org.apache.httpcomponents:httpclient:4.5.13") {
@@ -198,7 +198,7 @@ configure(allprojects) { project ->
exclude group: "org.hamcrest", name: "hamcrest-core"
}
}
dependencySet(group: 'org.mockito', version: '4.4.0') {
dependencySet(group: 'org.mockito', version: '4.5.1') {
entry('mockito-core') {
exclude group: "org.hamcrest", name: "hamcrest-core"
}
@@ -206,10 +206,10 @@ configure(allprojects) { project ->
}
dependency "io.mockk:mockk:1.12.1"
dependency("net.sourceforge.htmlunit:htmlunit:2.59.0") {
dependency("net.sourceforge.htmlunit:htmlunit:2.61.0") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:htmlunit-driver:2.59.0") {
dependency("org.seleniumhq.selenium:htmlunit-driver:2.61.0") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:selenium-java:3.141.59") {
@@ -340,7 +340,7 @@ configure([rootProject] + javaProjects) { project ->
}
checkstyle {
toolVersion = "9.3"
toolVersion = "10.1"
configDirectory.set(rootProject.file("src/checkstyle"))
}
+1 -1
View File
@@ -1,4 +1,4 @@
FROM ubuntu:focal-20220302
FROM ubuntu:focal-20220415
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
+4 -4
View File
@@ -3,16 +3,16 @@ set -e
case "$1" in
java8)
echo "https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u322-b06/OpenJDK8U-jdk_x64_linux_hotspot_8u322b06.tar.gz"
echo "https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u332-b09/OpenJDK8U-jdk_x64_linux_hotspot_8u332b09.tar.gz"
;;
java11)
echo "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.14.1%2B1/OpenJDK11U-jdk_x64_linux_hotspot_11.0.14.1_1.tar.gz"
echo "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.15%2B10/OpenJDK11U-jdk_x64_linux_hotspot_11.0.15_10.tar.gz"
;;
java17)
echo "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.2%2B8/OpenJDK17U-jdk_x64_linux_hotspot_17.0.2_8.tar.gz"
echo "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.3%2B7/OpenJDK17U-jdk_x64_linux_hotspot_17.0.3_7.tar.gz"
;;
java18)
echo "https://github.com/adoptium/temurin18-binaries/releases/download/jdk18-2022-02-12-08-06-beta/OpenJDK18-jdk_x64_linux_hotspot_2022-02-12-08-06.tar.gz"
echo "https://github.com/adoptium/temurin18-binaries/releases/download/jdk-18.0.1%2B10/OpenJDK18U-jdk_x64_linux_hotspot_18.0.1_10.tar.gz"
;;
*)
echo $"Unknown java version"
-2
View File
@@ -14,8 +14,6 @@ rm -rf /var/lib/apt/lists/*
curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.4/concourse-java.sh > /opt/concourse-java.sh
curl --output /opt/concourse-release-scripts.jar https://repo.spring.io/release/io/spring/concourse/releasescripts/concourse-release-scripts/0.3.2/concourse-release-scripts-0.3.2.jar
###########################################################
# JAVA
###########################################################
+4
View File
@@ -1,5 +1,6 @@
github-repo: "https://github.com/spring-projects/spring-framework.git"
github-repo-name: "spring-projects/spring-framework"
sonatype-staging-profile: "org.springframework"
docker-hub-organization: "springci"
artifactory-server: "https://repo.spring.io"
branch: "5.3.x"
@@ -7,4 +8,7 @@ milestone: "5.3.x"
build-name: "spring-framework"
pipeline-name: "spring-framework"
concourse-url: "https://ci.spring.io"
registry-mirror-host: docker.repo.spring.io
registry-mirror-username: ((artifactory-username))
registry-mirror-password: ((artifactory-password))
task-timeout: 1h00m
+23 -10
View File
@@ -12,7 +12,7 @@ anchors:
SONATYPE_USERNAME: ((sonatype-username))
SONATYPE_PASSWORD: ((sonatype-password))
SONATYPE_URL: ((sonatype-url))
SONATYPE_STAGING_PROFILE_ID: ((sonatype-staging-profile-id))
SONATYPE_STAGING_PROFILE: ((sonatype-staging-profile))
artifactory-task-params: &artifactory-task-params
ARTIFACTORY_SERVER: ((artifactory-server))
ARTIFACTORY_USERNAME: ((artifactory-username))
@@ -24,6 +24,10 @@ anchors:
username: ((docker-hub-username))
password: ((docker-hub-password))
tag: ((milestone))
registry-mirror-vars: &registry-mirror-vars
registry-mirror-host: ((registry-mirror-host))
registry-mirror-username: ((registry-mirror-username))
registry-mirror-password: ((registry-mirror-password))
slack-fail-params: &slack-fail-params
text: >
:concourse-failed: <https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}|${BUILD_PIPELINE_NAME} ${BUILD_JOB_NAME} failed!>
@@ -41,6 +45,11 @@ anchors:
GITHUB_TOKEN: ((github-ci-release-token))
resource_types:
- name: registry-image
type: registry-image
source:
repository: concourse/registry-image-resource
tag: 1.5.0
- name: artifactory-resource
type: registry-image
source:
@@ -87,7 +96,7 @@ resources:
branch: ((branch))
paths: ["ci/images/*"]
- name: ci-image
type: docker-image
type: registry-image
icon: docker
source:
<<: *docker-resource-source
@@ -157,13 +166,20 @@ resources:
jobs:
- name: build-ci-images
plan:
- get: ci-images-git-repo
trigger: true
- in_parallel:
- get: git-repo
- get: ci-images-git-repo
trigger: true
- task: build-ci-image
privileged: true
file: git-repo/ci/tasks/build-ci-image.yml
output_mapping:
image: ci-image
vars:
ci-image-name: ci-image
<<: *registry-mirror-vars
- put: ci-image
params:
build: ci-images-git-repo/ci/images
dockerfile: ci-images-git-repo/ci/images/ci-image/Dockerfile
image: ci-image/image.tar
- name: build
serial: true
public: true
@@ -340,7 +356,6 @@ jobs:
download_artifacts: false
save_build_info: true
- task: promote
image: ci-image
file: git-repo/ci/tasks/promote-version.yml
params:
RELEASE_TYPE: M
@@ -385,7 +400,6 @@ jobs:
download_artifacts: false
save_build_info: true
- task: promote
image: ci-image
file: git-repo/ci/tasks/promote-version.yml
params:
RELEASE_TYPE: RC
@@ -430,7 +444,6 @@ jobs:
download_artifacts: true
save_build_info: true
- task: promote
image: ci-image
file: git-repo/ci/tasks/promote-version.yml
params:
RELEASE_TYPE: RELEASE
+2 -2
View File
@@ -6,11 +6,11 @@ CONFIG_DIR=git-repo/ci/config
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json
java -jar /opt/concourse-release-scripts.jar \
java -jar /concourse-release-scripts.jar \
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
publishToCentral $RELEASE_TYPE $BUILD_INFO_LOCATION artifactory-repo || { exit 1; }
java -jar /opt/concourse-release-scripts.jar \
java -jar /concourse-release-scripts.jar \
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
promote $RELEASE_TYPE $BUILD_INFO_LOCATION || { exit 1; }
-8
View File
@@ -1,8 +0,0 @@
#!/bin/bash
export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
java -jar /opt/concourse-release-scripts.jar syncToCentral "RELEASE" $BUILD_INFO_LOCATION || { exit 1; }
echo "Sync complete"
echo $version > version/version
+31
View File
@@ -0,0 +1,31 @@
---
platform: linux
image_resource:
type: registry-image
source:
repository: concourse/oci-build-task
tag: 0.9.1
registry_mirror:
host: ((registry-mirror-host))
username: ((registry-mirror-username))
password: ((registry-mirror-password))
inputs:
- name: ci-images-git-repo
outputs:
- name: image
caches:
- path: ci-image-cache
params:
CONTEXT: ci-images-git-repo/ci/images
DOCKERFILE: ci-images-git-repo/ci/images/ci-image/Dockerfile
DOCKER_HUB_AUTH: ((docker-hub-auth))
run:
path: /bin/sh
args:
- "-c"
- |
mkdir -p /root/.docker
cat > /root/.docker/config.json <<EOF
{ "auths": { "https://index.docker.io/v1/": { "auth": "$DOCKER_HUB_AUTH" }}}
EOF
build
+6 -1
View File
@@ -1,5 +1,10 @@
---
platform: linux
image_resource:
type: registry-image
source:
repository: springio/concourse-release-scripts
tag: '0.3.4'
inputs:
- name: git-repo
- name: artifactory-repo
@@ -13,6 +18,6 @@ params:
SONATYPE_USER:
SONATYPE_PASSWORD:
SONATYPE_URL:
SONATYPE_STAGING_PROFILE_ID:
SONATYPE_STAGING_PROFILE:
run:
path: git-repo/ci/scripts/promote-version.sh
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.3.18-SNAPSHOT
version=5.3.20
org.gradle.jvmargs=-Xmx1536M
org.gradle.caching=true
org.gradle.parallel=true
+1 -1
View File
@@ -7,7 +7,7 @@ pluginManagement {
}
plugins {
id "com.gradle.enterprise" version "3.8.1"
id "com.gradle.enterprise" version "3.9"
id "io.spring.ge.conventions" version "0.0.9"
}
@@ -134,7 +134,7 @@ public abstract class AopProxyUtils {
if (targetClass.isInterface()) {
advised.setInterfaces(targetClass);
}
else if (Proxy.isProxyClass(targetClass) || isLambda(targetClass)) {
else if (Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
advised.setInterfaces(targetClass.getInterfaces());
}
specifiedInterfaces = advised.getProxiedInterfaces();
@@ -245,18 +245,4 @@ public abstract class AopProxyUtils {
return arguments;
}
/**
* Determine if the supplied {@link Class} is a JVM-generated implementation
* class for a lambda expression or method reference.
* <p>This method makes a best-effort attempt at determining this, based on
* checks that work on modern, main stream JVMs.
* @param clazz the class to check
* @return {@code true} if the class is a lambda implementation class
* @since 5.3.16
*/
static boolean isLambda(Class<?> clazz) {
return (clazz.isSynthetic() && (clazz.getSuperclass() == Object.class) &&
(clazz.getInterfaces().length > 0) && clazz.getName().contains("$$Lambda"));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -375,6 +375,22 @@ class CglibAopProxy implements AopProxy, Serializable {
return false;
}
/**
* Invoke the given method with a CGLIB MethodProxy if possible, falling back
* to a plain reflection invocation in case of a fast-class generation failure.
*/
@Nullable
private static Object invokeMethod(@Nullable Object target, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
try {
return methodProxy.invoke(target, args);
}
catch (CodeGenerationException ex) {
CglibMethodInvocation.logFastClassGenerationFailure(method);
return AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
}
/**
* Process a return value. Wraps a return of {@code this} if necessary to be the
* {@code proxy} and also verifies that {@code null} is not returned as a primitive.
@@ -425,7 +441,7 @@ class CglibAopProxy implements AopProxy, Serializable {
@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object retVal = methodProxy.invoke(this.target, args);
Object retVal = invokeMethod(this.target, method, args, methodProxy);
return processReturnType(proxy, this.target, method, retVal);
}
}
@@ -450,7 +466,7 @@ class CglibAopProxy implements AopProxy, Serializable {
Object oldProxy = null;
try {
oldProxy = AopContext.setCurrentProxy(proxy);
Object retVal = methodProxy.invoke(this.target, args);
Object retVal = invokeMethod(this.target, method, args, methodProxy);
return processReturnType(proxy, this.target, method, retVal);
}
finally {
@@ -478,7 +494,7 @@ class CglibAopProxy implements AopProxy, Serializable {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object target = this.targetSource.getTarget();
try {
Object retVal = methodProxy.invoke(target, args);
Object retVal = invokeMethod(target, method, args, methodProxy);
return processReturnType(proxy, target, method, retVal);
}
finally {
@@ -508,7 +524,7 @@ class CglibAopProxy implements AopProxy, Serializable {
Object target = this.targetSource.getTarget();
try {
oldProxy = AopContext.setCurrentProxy(proxy);
Object retVal = methodProxy.invoke(target, args);
Object retVal = invokeMethod(target, method, args, methodProxy);
return processReturnType(proxy, target, method, retVal);
}
finally {
@@ -685,13 +701,7 @@ class CglibAopProxy implements AopProxy, Serializable {
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
try {
retVal = methodProxy.invoke(target, argsToUse);
}
catch (CodeGenerationException ex) {
CglibMethodInvocation.logFastClassGenerationFailure(method);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
retVal = invokeMethod(target, method, argsToUse, methodProxy);
}
else {
// We need to create a method invocation...
@@ -21,6 +21,7 @@ import java.lang.reflect.Proxy;
import org.springframework.aop.SpringProxy;
import org.springframework.core.NativeDetector;
import org.springframework.util.ClassUtils;
/**
* Default {@link AopProxyFactory} implementation, creating either a CGLIB proxy
@@ -60,7 +61,7 @@ public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || AopProxyUtils.isLambda(targetClass)) {
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,6 +50,7 @@ import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostP
import org.springframework.core.SmartClassLoader;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
@@ -85,6 +86,7 @@ import org.springframework.util.StringUtils;
* @author Juergen Hoeller
* @author Rod Johnson
* @author Rob Harrop
* @author Sam Brannen
* @since 13.10.2003
* @see #setInterceptorNames
* @see #getAdvicesAndAdvisorsForBean
@@ -442,8 +444,8 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
proxyFactory.copyFrom(this);
if (proxyFactory.isProxyTargetClass()) {
// Explicit handling of JDK proxy targets (for introduction advice scenarios)
if (Proxy.isProxyClass(beanClass)) {
// Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)
if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
// Must allow for introductions; can't just set interfaces to the proxy's interfaces only.
for (Class<?> ifc : beanClass.getInterfaces()) {
proxyFactory.addInterface(ifc);
@@ -19,7 +19,6 @@ package org.springframework.aop.framework;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
import org.junit.jupiter.api.Test;
@@ -134,61 +133,4 @@ public class AopProxyUtilsTests {
AopProxyUtils.proxiedUserInterfaces(proxy));
}
@Test
void isLambda() {
assertIsLambda(AopProxyUtilsTests.staticLambdaExpression);
assertIsLambda(AopProxyUtilsTests::staticStringFactory);
assertIsLambda(this.instanceLambdaExpression);
assertIsLambda(this::instanceStringFactory);
}
@Test
void isNotLambda() {
assertIsNotLambda(new EnigmaSupplier());
assertIsNotLambda(new Supplier<String>() {
@Override
public String get() {
return "anonymous inner class";
}
});
assertIsNotLambda(new Fake$$LambdaSupplier());
}
private static void assertIsLambda(Supplier<String> supplier) {
assertThat(AopProxyUtils.isLambda(supplier.getClass())).isTrue();
}
private static void assertIsNotLambda(Supplier<String> supplier) {
assertThat(AopProxyUtils.isLambda(supplier.getClass())).isFalse();
}
private static final Supplier<String> staticLambdaExpression = () -> "static lambda expression";
private final Supplier<String> instanceLambdaExpression = () -> "instance lambda expressions";
private static String staticStringFactory() {
return "static string factory";
}
private String instanceStringFactory() {
return "instance string factory";
}
private static class EnigmaSupplier implements Supplier<String> {
@Override
public String get() {
return "enigma";
}
}
private static class Fake$$LambdaSupplier implements Supplier<String> {
@Override
public String get() {
return "fake lambda";
}
}
}
@@ -22,6 +22,7 @@ import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.security.ProtectionDomain;
import java.util.Collections;
import java.util.HashSet;
@@ -287,13 +288,17 @@ public final class CachedIntrospectionResults {
// This call is slow so we do it once.
PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
if (Class.class == beanClass && (!"name".equals(pd.getName()) && !pd.getName().endsWith("Name"))) {
if (Class.class == beanClass && !("name".equals(pd.getName()) ||
(pd.getName().endsWith("Name") && String.class == pd.getPropertyType()))) {
// Only allow all name variants of Class properties
continue;
}
if (pd.getPropertyType() != null && (ClassLoader.class.isAssignableFrom(pd.getPropertyType())
|| ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) {
// Ignore ClassLoader and ProtectionDomain types - nobody needs to bind to those
if (URL.class == beanClass && "content".equals(pd.getName())) {
// Only allow URL attribute introspection, not content resolution
continue;
}
if (pd.getWriteMethod() == null && isInvalidReadOnlyPropertyType(pd.getPropertyType())) {
// Ignore read-only properties such as ClassLoader - no need to bind to those
continue;
}
if (logger.isTraceEnabled()) {
@@ -342,9 +347,8 @@ public final class CachedIntrospectionResults {
// GenericTypeAwarePropertyDescriptor leniently resolves a set* write method
// against a declared read method, so we prefer read method descriptors here.
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
if (pd.getPropertyType() != null && (ClassLoader.class.isAssignableFrom(pd.getPropertyType())
|| ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) {
// Ignore ClassLoader and ProtectionDomain types - nobody needs to bind to those
if (pd.getWriteMethod() == null && isInvalidReadOnlyPropertyType(pd.getPropertyType())) {
// Ignore read-only properties such as ClassLoader - no need to bind to those
continue;
}
this.propertyDescriptors.put(pd.getName(), pd);
@@ -364,7 +368,7 @@ public final class CachedIntrospectionResults {
for (Method method : beanClass.getMethods()) {
if (!this.propertyDescriptors.containsKey(method.getName()) &&
!readMethodNames.contains((method.getName())) && isPlainAccessor(method)) {
!readMethodNames.contains(method.getName()) && isPlainAccessor(method)) {
this.propertyDescriptors.put(method.getName(),
new GenericTypeAwarePropertyDescriptor(beanClass, method.getName(), method, null, null));
readMethodNames.add(method.getName());
@@ -373,8 +377,10 @@ public final class CachedIntrospectionResults {
}
private boolean isPlainAccessor(Method method) {
if (method.getParameterCount() > 0 || method.getReturnType() == void.class ||
method.getDeclaringClass() == Object.class || Modifier.isStatic(method.getModifiers())) {
if (Modifier.isStatic(method.getModifiers()) ||
method.getDeclaringClass() == Object.class || method.getDeclaringClass() == Class.class ||
method.getParameterCount() > 0 || method.getReturnType() == void.class ||
isInvalidReadOnlyPropertyType(method.getReturnType())) {
return false;
}
try {
@@ -387,6 +393,12 @@ public final class CachedIntrospectionResults {
}
}
private boolean isInvalidReadOnlyPropertyType(@Nullable Class<?> returnType) {
return (returnType != null && (AutoCloseable.class.isAssignableFrom(returnType) ||
ClassLoader.class.isAssignableFrom(returnType) ||
ProtectionDomain.class.isAssignableFrom(returnType)));
}
BeanInfo getBeanInfo() {
return this.beanInfo;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,8 +23,9 @@ import org.springframework.lang.Nullable;
/**
* Common interface for classes that can access named properties
* (such as bean properties of an object or fields in an object)
* Serves as base interface for {@link BeanWrapper}.
* (such as bean properties of an object or fields in an object).
*
* <p>Serves as base interface for {@link BeanWrapper}.
*
* @author Juergen Hoeller
* @since 1.1
@@ -1,5 +1,5 @@
/**
* Support package for the Java 6 ServiceLoader facility.
* Support package for the Java {@link java.util.ServiceLoader} facility.
*/
@NonNullApi
@NonNullFields
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -985,7 +985,7 @@ class ConstructorResolver {
/**
* Delegate for checking Java 6's {@link ConstructorProperties} annotation.
* Delegate for checking Java's {@link ConstructorProperties} annotation.
*/
private static class ConstructorPropertiesChecker {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,6 +43,7 @@ import org.springframework.core.io.ContextResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceEditor;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.WritableResource;
import org.springframework.core.io.support.ResourceArrayPropertyEditor;
import org.springframework.core.io.support.ResourcePatternResolver;
@@ -102,6 +103,7 @@ public class ResourceEditorRegistrar implements PropertyEditorRegistrar {
ResourceEditor baseEditor = new ResourceEditor(this.resourceLoader, this.propertyResolver);
doRegisterEditor(registry, Resource.class, baseEditor);
doRegisterEditor(registry, ContextResource.class, baseEditor);
doRegisterEditor(registry, WritableResource.class, baseEditor);
doRegisterEditor(registry, InputStream.class, new InputStreamEditor(baseEditor));
doRegisterEditor(registry, InputSource.class, new InputSourceEditor(baseEditor));
doRegisterEditor(registry, File.class, new FileEditor(baseEditor));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,7 +39,7 @@ inline fun <reified T : Any> ListableBeanFactory.getBeansOfType(includeNonSingle
/**
* Extension for [ListableBeanFactory.getBeanNamesForAnnotation] providing a
* `getBeansOfType<Foo>()` variant.
* `getBeanNamesForAnnotation<Foo>()` variant.
*
* @author Sebastien Deleuze
* @since 5.0
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,9 @@ import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.core.OverridingClassLoader;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.UrlResource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -105,7 +108,7 @@ class BeanWrapperTests extends AbstractPropertyAccessorTests {
.satisfies(ex -> assertThat(ex.getPossibleMatches()).containsExactly("age"));
}
@Test // Can't be shared; there is no such thing as a read-only field
@Test // Can't be shared; there is no such thing as a read-only field
void setReadOnlyMapProperty() {
TypedReadOnlyMap map = new TypedReadOnlyMap(Collections.singletonMap("key", new TestBean()));
TypedReadOnlyMapClient target = new TypedReadOnlyMapClient();
@@ -151,18 +154,58 @@ class BeanWrapperTests extends AbstractPropertyAccessorTests {
}
@Test
void propertyDescriptors() {
void propertyDescriptors() throws Exception {
TestBean target = new TestBean();
target.setSpouse(new TestBean());
BeanWrapper accessor = createAccessor(target);
accessor.setPropertyValue("name", "a");
accessor.setPropertyValue("spouse.name", "b");
assertThat(target.getName()).isEqualTo("a");
assertThat(target.getSpouse().getName()).isEqualTo("b");
assertThat(accessor.getPropertyValue("name")).isEqualTo("a");
assertThat(accessor.getPropertyValue("spouse.name")).isEqualTo("b");
assertThat(accessor.getPropertyDescriptor("name").getPropertyType()).isEqualTo(String.class);
assertThat(accessor.getPropertyDescriptor("spouse.name").getPropertyType()).isEqualTo(String.class);
assertThat(accessor.isReadableProperty("class.package")).isFalse();
assertThat(accessor.isReadableProperty("class.module")).isFalse();
assertThat(accessor.isReadableProperty("class.classLoader")).isFalse();
assertThat(accessor.isReadableProperty("class.name")).isTrue();
assertThat(accessor.isReadableProperty("class.simpleName")).isTrue();
assertThat(accessor.getPropertyValue("class.name")).isEqualTo(TestBean.class.getName());
assertThat(accessor.getPropertyValue("class.simpleName")).isEqualTo(TestBean.class.getSimpleName());
assertThat(accessor.getPropertyDescriptor("class.name").getPropertyType()).isEqualTo(String.class);
assertThat(accessor.getPropertyDescriptor("class.simpleName").getPropertyType()).isEqualTo(String.class);
accessor = createAccessor(new DefaultResourceLoader());
assertThat(accessor.isReadableProperty("class.package")).isFalse();
assertThat(accessor.isReadableProperty("class.module")).isFalse();
assertThat(accessor.isReadableProperty("class.classLoader")).isFalse();
assertThat(accessor.isReadableProperty("class.name")).isTrue();
assertThat(accessor.isReadableProperty("class.simpleName")).isTrue();
assertThat(accessor.isReadableProperty("classLoader")).isTrue();
assertThat(accessor.isWritableProperty("classLoader")).isTrue();
OverridingClassLoader ocl = new OverridingClassLoader(getClass().getClassLoader());
accessor.setPropertyValue("classLoader", ocl);
assertThat(accessor.getPropertyValue("classLoader")).isSameAs(ocl);
accessor = createAccessor(new UrlResource("https://spring.io"));
assertThat(accessor.isReadableProperty("class.package")).isFalse();
assertThat(accessor.isReadableProperty("class.module")).isFalse();
assertThat(accessor.isReadableProperty("class.classLoader")).isFalse();
assertThat(accessor.isReadableProperty("class.name")).isTrue();
assertThat(accessor.isReadableProperty("class.simpleName")).isTrue();
assertThat(accessor.isReadableProperty("URL.protocol")).isTrue();
assertThat(accessor.isReadableProperty("URL.host")).isTrue();
assertThat(accessor.isReadableProperty("URL.port")).isTrue();
assertThat(accessor.isReadableProperty("URL.file")).isTrue();
assertThat(accessor.isReadableProperty("URL.content")).isFalse();
assertThat(accessor.isReadableProperty("inputStream")).isFalse();
assertThat(accessor.isReadableProperty("filename")).isTrue();
assertThat(accessor.isReadableProperty("description")).isTrue();
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import javax.lang.model.element.Element;
/**
* Provide the list of stereotypes that match an {@link Element}.
*
* <p>If an element has one or more stereotypes, it is referenced in the index
* of candidate components and each stereotype can be queried individually.
*
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ import java.util.Set;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.ConstraintValidatorFactory;
import javax.validation.ConstraintViolation;
import javax.validation.Payload;
import javax.validation.Valid;
@@ -43,6 +44,7 @@ import org.hibernate.validator.HibernateValidatorFactory;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.convert.support.DefaultConversionService;
@@ -52,18 +54,18 @@ import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Juergen Hoeller
*/
@SuppressWarnings("resource")
public class ValidatorFactoryTests {
class ValidatorFactoryTests {
@Test
@SuppressWarnings("cast")
public void testSimpleValidation() {
void simpleValidation() {
@SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -78,15 +80,15 @@ public class ValidatorFactoryTests {
Validator nativeValidator = validator.unwrap(Validator.class);
assertThat(nativeValidator.getClass().getName().startsWith("org.hibernate")).isTrue();
assertThat(validator.unwrap(ValidatorFactory.class) instanceof HibernateValidatorFactory).isTrue();
assertThat(validator.unwrap(HibernateValidatorFactory.class) instanceof HibernateValidatorFactory).isTrue();
assertThat(validator.unwrap(ValidatorFactory.class)).isInstanceOf(HibernateValidatorFactory.class);
assertThat(validator.unwrap(HibernateValidatorFactory.class)).isInstanceOf(HibernateValidatorFactory.class);
validator.destroy();
}
@Test
@SuppressWarnings("cast")
public void testSimpleValidationWithCustomProvider() {
void simpleValidationWithCustomProvider() {
@SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setProviderClass(HibernateValidator.class);
validator.afterPropertiesSet();
@@ -102,14 +104,15 @@ public class ValidatorFactoryTests {
Validator nativeValidator = validator.unwrap(Validator.class);
assertThat(nativeValidator.getClass().getName().startsWith("org.hibernate")).isTrue();
assertThat(validator.unwrap(ValidatorFactory.class) instanceof HibernateValidatorFactory).isTrue();
assertThat(validator.unwrap(HibernateValidatorFactory.class) instanceof HibernateValidatorFactory).isTrue();
assertThat(validator.unwrap(ValidatorFactory.class)).isInstanceOf(HibernateValidatorFactory.class);
assertThat(validator.unwrap(HibernateValidatorFactory.class)).isInstanceOf(HibernateValidatorFactory.class);
validator.destroy();
}
@Test
public void testSimpleValidationWithClassLevel() {
void simpleValidationWithClassLevel() {
@SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -122,10 +125,13 @@ public class ValidatorFactoryTests {
ConstraintViolation<?> cv = iterator.next();
assertThat(cv.getPropertyPath().toString()).isEqualTo("");
assertThat(cv.getConstraintDescriptor().getAnnotation() instanceof NameAddressValid).isTrue();
validator.destroy();
}
@Test
public void testSpringValidationFieldType() {
void springValidationFieldType() {
@SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -135,11 +141,16 @@ public class ValidatorFactoryTests {
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
assertThat(errors.getErrorCount()).isEqualTo(1);
assertThat(errors.getFieldError("address").getRejectedValue()).isInstanceOf(ValidAddress.class);
assertThat(errors.getFieldError("address").getRejectedValue())
.as("Field/Value type mismatch")
.isInstanceOf(ValidAddress.class);
validator.destroy();
}
@Test
public void testSpringValidation() {
void springValidation() {
@SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -164,10 +175,13 @@ public class ValidatorFactoryTests {
assertThat(errorCodes.contains("NotNull.street")).isTrue();
assertThat(errorCodes.contains("NotNull.java.lang.String")).isTrue();
assertThat(errorCodes.contains("NotNull")).isTrue();
validator.destroy();
}
@Test
public void testSpringValidationWithClassLevel() {
void springValidationWithClassLevel() {
@SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -182,10 +196,12 @@ public class ValidatorFactoryTests {
assertThat(errorCodes.size()).isEqualTo(2);
assertThat(errorCodes.contains("NameAddressValid.person")).isTrue();
assertThat(errorCodes.contains("NameAddressValid")).isTrue();
validator.destroy();
}
@Test
public void testSpringValidationWithAutowiredValidator() {
void springValidationWithAutowiredValidator() {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(
LocalValidatorFactoryBean.class);
LocalValidatorFactoryBean validator = ctx.getBean(LocalValidatorFactoryBean.class);
@@ -202,11 +218,14 @@ public class ValidatorFactoryTests {
assertThat(errorCodes.size()).isEqualTo(2);
assertThat(errorCodes.contains("NameAddressValid.person")).isTrue();
assertThat(errorCodes.contains("NameAddressValid")).isTrue();
validator.destroy();
ctx.close();
}
@Test
public void testSpringValidationWithErrorInListElement() {
void springValidationWithErrorInListElement() {
@SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -221,10 +240,13 @@ public class ValidatorFactoryTests {
assertThat(fieldError.getField()).isEqualTo("address.street");
fieldError = result.getFieldError("addressList[0].street");
assertThat(fieldError.getField()).isEqualTo("addressList[0].street");
validator.destroy();
}
@Test
public void testSpringValidationWithErrorInSetElement() {
void springValidationWithErrorInSetElement() {
@SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -239,10 +261,13 @@ public class ValidatorFactoryTests {
assertThat(fieldError.getField()).isEqualTo("address.street");
fieldError = result.getFieldError("addressSet[].street");
assertThat(fieldError.getField()).isEqualTo("addressSet[].street");
validator.destroy();
}
@Test
public void testInnerBeanValidation() {
void innerBeanValidation() {
@SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -251,10 +276,13 @@ public class ValidatorFactoryTests {
validator.validate(mainBean, errors);
Object rejected = errors.getFieldValue("inner.value");
assertThat(rejected).isNull();
validator.destroy();
}
@Test
public void testValidationWithOptionalField() {
void validationWithOptionalField() {
@SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -263,10 +291,13 @@ public class ValidatorFactoryTests {
validator.validate(mainBean, errors);
Object rejected = errors.getFieldValue("inner.value");
assertThat(rejected).isNull();
validator.destroy();
}
@Test
public void testListValidation() {
void listValidation() {
@SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
@@ -282,6 +313,34 @@ public class ValidatorFactoryTests {
assertThat(fieldError).isNotNull();
assertThat(fieldError.getRejectedValue()).isEqualTo("X");
assertThat(errors.getFieldValue("list[1]")).isEqualTo("X");
validator.destroy();
}
@Test
void withConstraintValidatorFactory() {
ConstraintValidatorFactory cvf = new SpringConstraintValidatorFactory(new DefaultListableBeanFactory());
@SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setConstraintValidatorFactory(cvf);
validator.afterPropertiesSet();
assertThat(validator.getConstraintValidatorFactory()).isSameAs(cvf);
validator.destroy();
}
@Test
void withCustomInitializer() {
ConstraintValidatorFactory cvf = new SpringConstraintValidatorFactory(new DefaultListableBeanFactory());
@SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setConfigurationInitializer(configuration -> configuration.constraintValidatorFactory(cvf));
validator.afterPropertiesSet();
assertThat(validator.getConstraintValidatorFactory()).isSameAs(cvf);
validator.destroy();
}
@@ -380,8 +439,8 @@ public class ValidatorFactoryTests {
}
boolean valid = (value.name == null || !value.address.street.contains(value.name));
if (!valid && "Phil".equals(value.name)) {
context.buildConstraintViolationWithTemplate(
context.getDefaultConstraintMessageTemplate()).addPropertyNode("address").addConstraintViolation().disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode("address").addConstraintViolation().disableDefaultConstraintViolation();
}
return valid;
}
@@ -417,6 +476,7 @@ public class ValidatorFactoryTests {
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@@ -425,8 +485,8 @@ public class ValidatorFactoryTests {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Constraint(validatedBy=InnerValidator.class)
public static @interface InnerValid {
@Constraint(validatedBy = InnerValidator.class)
public @interface InnerValid {
String message() default "NOT VALID";
@@ -446,7 +506,8 @@ public class ValidatorFactoryTests {
public boolean isValid(InnerBean bean, ConstraintValidatorContext context) {
context.disableDefaultConstraintViolation();
if (bean.getValue() == null) {
context.buildConstraintViolationWithTemplate("NULL").addPropertyNode("value").addConstraintViolation();
context.buildConstraintViolationWithTemplate("NULL")
.addPropertyNode("value").addConstraintViolation();
return false;
}
return true;
@@ -494,7 +555,8 @@ public class ValidatorFactoryTests {
boolean valid = true;
for (int i = 0; i < list.size(); i++) {
if ("X".equals(list.get(i))) {
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).addBeanNode().inIterable().atIndex(i).addConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addBeanNode().inIterable().atIndex(i).addConstraintViolation();
valid = false;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -762,7 +762,7 @@ public class MBeanExporter extends MBeanRegistrationSupport implements MBeanExpo
* <p>The default implementation delegates to {@link JmxUtils#isMBean},
* which checks for {@link javax.management.DynamicMBean} classes as well
* as classes with corresponding "*MBean" interface (Standard MBeans)
* or corresponding "*MXBean" interface (Java 6 MXBeans).
* or corresponding "*MXBean" interface (Java MXBeans).
* @param beanClass the bean class to analyze
* @return whether the class qualifies as an MBean
* @see org.springframework.jmx.support.JmxUtils#isMBean(Class)
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -255,7 +255,7 @@ public abstract class JmxUtils {
* Determine whether the given bean class qualifies as an MBean as-is.
* <p>This implementation checks for {@link javax.management.DynamicMBean}
* classes as well as classes with corresponding "*MBean" interface
* (Standard MBeans) or corresponding "*MXBean" interface (Java 6 MXBeans).
* (Standard MBeans) or corresponding "*MXBean" interface (Java MXBeans).
* @param clazz the bean class to analyze
* @return whether the class qualifies as an MBean
* @see org.springframework.jmx.export.MBeanExporter#isMBean(Class)
@@ -289,7 +289,7 @@ public abstract class JmxUtils {
}
/**
* Return the Java 6 MXBean interface exists for the given class, if any
* Return the Java MXBean interface for the given class, if any
* (that is, an interface whose name ends with "MXBean" and/or
* carries an appropriate MXBean annotation).
* @param clazz the class to check
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@ import org.springframework.util.StringUtils;
/**
* {@link org.springframework.scripting.ScriptFactory} implementation based
* on the JSR-223 script engine abstraction (as included in Java 6+).
* on the JSR-223 script engine abstraction (as included in Java).
* Supports JavaScript, Groovy, JRuby, and other JSR-223 compliant engines.
*
* <p>Typically used in combination with a
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,18 +51,20 @@ import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;
/**
* Binder that allows for setting property values onto a target object,
* including support for validation and binding result analysis.
* The binding process can be customized through specifying allowed fields,
* Binder that allows for setting property values on a target object, including
* support for validation and binding result analysis.
*
* <p>The binding process can be customized by specifying allowed field patterns,
* required fields, custom editors, etc.
*
* <p>Note that there are potential security implications in failing to set an array
* of allowed fields. In the case of HTTP form POST data for example, malicious clients
* can attempt to subvert an application by supplying values for fields or properties
* that do not exist on the form. In some cases this could lead to illegal data being
* set on command objects <i>or their nested objects</i>. For this reason, it is
* <b>highly recommended to specify the {@link #setAllowedFields allowedFields} property</b>
* on the DataBinder.
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
* parts of the object graph that are not meant to be accessed or modified by
* external clients. Therefore the design and use of data binding should be considered
* carefully with regard to security. For more details, please refer to the dedicated
* sections on data binding for
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
* in the reference manual.
*
* <p>The binding results can be examined via the {@link BindingResult} interface,
* extending the {@link Errors} interface: see the {@link #getBindingResult()} method.
@@ -96,6 +98,7 @@ import org.springframework.util.StringUtils;
* @author Rob Harrop
* @author Stephane Nicoll
* @author Kazuki Shimizu
* @author Sam Brannen
* @see #setAllowedFields
* @see #setRequiredFields
* @see #registerCustomEditor
@@ -418,15 +421,21 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
}
/**
* Register fields that should be allowed for binding. Default is all fields.
* Restrict this for example to avoid unwanted modifications by malicious
* Register field patterns that should be allowed for binding.
* <p>Default is all fields.
* <p>Restrict this for example to avoid unwanted modifications by malicious
* users when binding HTTP request parameters.
* <p>Supports "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an
* arbitrary number of pattern parts), as well as direct equality. More
* sophisticated matching can be implemented by overriding the
* {@code isAllowed} method.
* <p>Alternatively, specify a list of <i>disallowed</i> fields.
* @param allowedFields array of field names
* <p>Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
* well as direct equality.
* <p>The default implementation of this method stores allowed field patterns
* in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical}
* form. Subclasses which override this method must therefore take this into
* account.
* <p>More sophisticated matching can be implemented by overriding the
* {@link #isAllowed} method.
* <p>Alternatively, specify a list of <i>disallowed</i> field patterns.
* @param allowedFields array of allowed field patterns
* @see #setDisallowedFields
* @see #isAllowed(String)
*/
@@ -435,8 +444,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
}
/**
* Return the fields that should be allowed for binding.
* @return array of field names
* Return the field patterns that should be allowed for binding.
* @return array of allowed field patterns
* @see #setAllowedFields(String...)
*/
@Nullable
public String[] getAllowedFields() {
@@ -444,25 +454,44 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
}
/**
* Register fields that should <i>not</i> be allowed for binding. Default
* is none. Mark fields as disallowed for example to avoid unwanted
* Register field patterns that should <i>not</i> be allowed for binding.
* <p>Default is none.
* <p>Mark fields as disallowed, for example to avoid unwanted
* modifications by malicious users when binding HTTP request parameters.
* <p>Supports "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an
* arbitrary number of pattern parts), as well as direct equality.
* More sophisticated matching can be implemented by overriding the
* {@code isAllowed} method.
* <p>Alternatively, specify a list of <i>allowed</i> fields.
* @param disallowedFields array of field names
* <p>Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
* well as direct equality.
* <p>The default implementation of this method stores disallowed field patterns
* in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical}
* form. As of Spring Framework 5.2.21, the default implementation also transforms
* disallowed field patterns to {@linkplain String#toLowerCase() lowercase} to
* support case-insensitive pattern matching in {@link #isAllowed}. Subclasses
* which override this method must therefore take both of these transformations
* into account.
* <p>More sophisticated matching can be implemented by overriding the
* {@link #isAllowed} method.
* <p>Alternatively, specify a list of <i>allowed</i> field patterns.
* @param disallowedFields array of disallowed field patterns
* @see #setAllowedFields
* @see #isAllowed(String)
*/
public void setDisallowedFields(@Nullable String... disallowedFields) {
this.disallowedFields = PropertyAccessorUtils.canonicalPropertyNames(disallowedFields);
if (disallowedFields == null) {
this.disallowedFields = null;
}
else {
String[] fieldPatterns = new String[disallowedFields.length];
for (int i = 0; i < fieldPatterns.length; i++) {
fieldPatterns[i] = PropertyAccessorUtils.canonicalPropertyName(disallowedFields[i]).toLowerCase();
}
this.disallowedFields = fieldPatterns;
}
}
/**
* Return the fields that should <i>not</i> be allowed for binding.
* @return array of field names
* Return the field patterns that should <i>not</i> be allowed for binding.
* @return array of disallowed field patterns
* @see #setDisallowedFields(String...)
*/
@Nullable
public String[] getDisallowedFields() {
@@ -774,16 +803,20 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
}
/**
* Return if the given field is allowed for binding.
* Invoked for each passed-in property value.
* <p>The default implementation checks for "xxx*", "*xxx", "*xxx*" and "xxx*yyy"
* matches (with an arbitrary number of pattern parts), as well as direct equality,
* in the specified lists of allowed fields and disallowed fields. A field matching
* a disallowed pattern will not be accepted even if it also happens to match a
* pattern in the allowed list.
* <p>Can be overridden in subclasses.
* Determine if the given field is allowed for binding.
* <p>Invoked for each passed-in property value.
* <p>Checks for {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
* well as direct equality, in the configured lists of allowed field patterns
* and disallowed field patterns.
* <p>Matching against allowed field patterns is case-sensitive; whereas,
* matching against disallowed field patterns is case-insensitive.
* <p>A field matching a disallowed pattern will not be accepted even if it
* also happens to match a pattern in the allowed list.
* <p>Can be overridden in subclasses, but care must be taken to honor the
* aforementioned contract.
* @param field the field to check
* @return if the field is allowed
* @return {@code true} if the field is allowed
* @see #setAllowedFields
* @see #setDisallowedFields
* @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)
@@ -792,7 +825,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
String[] allowed = getAllowedFields();
String[] disallowed = getDisallowedFields();
return ((ObjectUtils.isEmpty(allowed) || PatternMatchUtils.simpleMatch(allowed, field)) &&
(ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field)));
(ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field.toLowerCase())));
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.function.Consumer;
import javax.validation.Configuration;
import javax.validation.ConstraintValidatorFactory;
@@ -113,6 +114,9 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
private final Map<String, String> validationPropertyMap = new HashMap<>();
@Nullable
private Consumer<Configuration<?>> configurationInitializer;
@Nullable
private ApplicationContext applicationContext;
@@ -234,6 +238,18 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
return this.validationPropertyMap;
}
/**
* Specify a callback for customizing the Bean Validation {@code Configuration} instance,
* as an alternative to overriding the {@link #postProcessConfiguration(Configuration)}
* method in custom {@code LocalValidatorFactoryBean} subclasses.
* <p>This enables convenient customizations for application purposes. Infrastructure
* extensions may keep overriding the {@link #postProcessConfiguration} template method.
* @since 5.3.19
*/
public void setConfigurationInitializer(Consumer<Configuration<?>> configurationInitializer) {
this.configurationInitializer = configurationInitializer;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
@@ -312,6 +328,9 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
this.validationPropertyMap.forEach(configuration::addProperty);
// Allow for custom post-processing before we actually build the ValidatorFactory.
if (this.configurationInitializer != null) {
this.configurationInitializer.accept(configuration);
}
postProcessConfiguration(configuration);
try {
@@ -21,6 +21,8 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.function.Supplier;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
@@ -31,11 +33,17 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.IntroductionAdvisor;
import org.springframework.aop.IntroductionInterceptor;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.aop.aspectj.annotation.AspectMetadata;
import org.springframework.aop.config.AopConfigUtils;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyConfig;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.beans.PropertyValue;
@@ -52,6 +60,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.DecoratingProxy;
import org.springframework.core.NestedRuntimeException;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
@@ -304,10 +313,26 @@ public class AspectJAutoProxyCreatorTests {
@ValueSource(classes = {ProxyTargetClassFalseConfig.class, ProxyTargetClassTrueConfig.class})
void lambdaIsAlwaysProxiedWithJdkProxy(Class<?> configClass) {
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(configClass)) {
Supplier<?> supplier = context.getBean(Supplier.class);
@SuppressWarnings("unchecked")
Supplier<String> supplier = context.getBean(Supplier.class);
assertThat(AopUtils.isAopProxy(supplier)).as("AOP proxy").isTrue();
assertThat(AopUtils.isJdkDynamicProxy(supplier)).as("JDK Dynamic proxy").isTrue();
assertThat(supplier.get()).asString().isEqualTo("advised: lambda");
assertThat(supplier.getClass().getInterfaces())
.containsExactlyInAnyOrder(Supplier.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
assertThat(supplier.get()).isEqualTo("advised: lambda");
}
}
@ParameterizedTest(name = "[{index}] {0}")
@ValueSource(classes = {MixinProxyTargetClassFalseConfig.class, MixinProxyTargetClassTrueConfig.class})
void lambdaIsAlwaysProxiedWithJdkProxyWithIntroductions(Class<?> configClass) {
try (ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(configClass)) {
MessageGenerator messageGenerator = context.getBean(MessageGenerator.class);
assertThat(AopUtils.isAopProxy(messageGenerator)).as("AOP proxy").isTrue();
assertThat(AopUtils.isJdkDynamicProxy(messageGenerator)).as("JDK Dynamic proxy").isTrue();
assertThat(messageGenerator.getClass().getInterfaces())
.containsExactlyInAnyOrder(MessageGenerator.class, Mixin.class, SpringProxy.class, Advised.class, DecoratingProxy.class);
assertThat(messageGenerator.generateMessage()).isEqualTo("mixin: lambda");
}
}
@@ -616,3 +641,79 @@ class ProxyTargetClassFalseConfig extends AbstractProxyTargetClassConfig {
@EnableAspectJAutoProxy(proxyTargetClass = true)
class ProxyTargetClassTrueConfig extends AbstractProxyTargetClassConfig {
}
@FunctionalInterface
interface MessageGenerator {
String generateMessage();
}
interface Mixin {
}
class MixinIntroductionInterceptor implements IntroductionInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
return "mixin: " + invocation.proceed();
}
@Override
public boolean implementsInterface(Class<?> intf) {
return Mixin.class.isAssignableFrom(intf);
}
}
@SuppressWarnings("serial")
class MixinAdvisor extends AbstractPointcutAdvisor implements IntroductionAdvisor {
@Override
public org.springframework.aop.Pointcut getPointcut() {
return org.springframework.aop.Pointcut.TRUE;
}
@Override
public Advice getAdvice() {
return new MixinIntroductionInterceptor();
}
@Override
public Class<?>[] getInterfaces() {
return new Class[] { Mixin.class };
}
@Override
public ClassFilter getClassFilter() {
return MessageGenerator.class::isAssignableFrom;
}
@Override
public void validateInterfaces() {
/* no-op */
}
}
abstract class AbstractMixinConfig {
@Bean
MessageGenerator messageGenerator() {
return () -> "lambda";
}
@Bean
MixinAdvisor mixinAdvisor() {
return new MixinAdvisor();
}
}
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = false)
class MixinProxyTargetClassFalseConfig extends AbstractMixinConfig {
}
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
class MixinProxyTargetClassTrueConfig extends AbstractMixinConfig {
}
@@ -64,23 +64,26 @@ import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.lang.Nullable;
import org.springframework.tests.sample.beans.BeanWithObjectProperty;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.entry;
/**
* Unit tests for {@link DataBinder}.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Rob Harrop
* @author Kazuki Shimizu
* @author Sam Brannen
*/
class DataBinderTests {
@Test
void testBindingNoErrors() throws BindException {
void bindingNoErrors() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
assertThat(binder.isIgnoreUnknownFields()).isTrue();
@@ -110,12 +113,11 @@ class DataBinderTests {
assertThat(ex).isEqualTo(binder.getBindingResult());
other.reject("xxx");
boolean condition = !other.equals(binder.getBindingResult());
assertThat(condition).isTrue();
assertThat(other).isNotEqualTo(binder.getBindingResult());
}
@Test
void testBindingWithDefaultConversionNoErrors() throws BindException {
void bindingWithDefaultConversionNoErrors() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
assertThat(binder.isIgnoreUnknownFields()).isTrue();
@@ -131,7 +133,7 @@ class DataBinderTests {
}
@Test
void testNestedBindingWithDefaultConversionNoErrors() throws BindException {
void nestedBindingWithDefaultConversionNoErrors() throws BindException {
TestBean rod = new TestBean(new TestBean());
DataBinder binder = new DataBinder(rod, "person");
assertThat(binder.isIgnoreUnknownFields()).isTrue();
@@ -147,7 +149,7 @@ class DataBinderTests {
}
@Test
void testBindingNoErrorsNotIgnoreUnknown() {
void bindingNoErrorsNotIgnoreUnknown() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
binder.setIgnoreUnknownFields(false);
@@ -160,7 +162,7 @@ class DataBinderTests {
}
@Test
void testBindingNoErrorsWithInvalidField() {
void bindingNoErrorsWithInvalidField() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -171,7 +173,7 @@ class DataBinderTests {
}
@Test
void testBindingNoErrorsWithIgnoreInvalid() {
void bindingNoErrorsWithIgnoreInvalid() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
binder.setIgnoreInvalidFields(true);
@@ -180,10 +182,14 @@ class DataBinderTests {
pvs.add("spouse.age", 32);
binder.bind(pvs);
binder.close();
assertThat(rod.getName()).isEqualTo("Rod");
assertThat(rod.getSpouse()).isNull();
}
@Test
void testBindingWithErrors() {
void bindingWithErrors() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -245,7 +251,7 @@ class DataBinderTests {
}
@Test
void testBindingWithSystemFieldError() {
void bindingWithSystemFieldError() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -257,7 +263,7 @@ class DataBinderTests {
}
@Test
void testBindingWithErrorsAndCustomEditors() {
void bindingWithErrorsAndCustomEditors() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
binder.registerCustomEditor(String.class, "touchy", new PropertyEditorSupport() {
@@ -325,7 +331,7 @@ class DataBinderTests {
}
@Test
void testBindingWithCustomEditorOnObjectField() {
void bindingWithCustomEditorOnObjectField() {
BeanWithObjectProperty tb = new BeanWithObjectProperty();
DataBinder binder = new DataBinder(tb);
binder.registerCustomEditor(Integer.class, "object", new CustomNumberEditor(Integer.class, true));
@@ -336,7 +342,7 @@ class DataBinderTests {
}
@Test
void testBindingWithFormatter() {
void bindingWithFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -368,7 +374,7 @@ class DataBinderTests {
}
@Test
void testBindingErrorWithFormatter() {
void bindingErrorWithFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -391,7 +397,7 @@ class DataBinderTests {
}
@Test
void testBindingErrorWithParseExceptionFromFormatter() {
void bindingErrorWithParseExceptionFromFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -419,7 +425,7 @@ class DataBinderTests {
}
@Test
void testBindingErrorWithRuntimeExceptionFromFormatter() {
void bindingErrorWithRuntimeExceptionFromFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -447,7 +453,7 @@ class DataBinderTests {
}
@Test
void testBindingWithFormatterAgainstList() {
void bindingWithFormatterAgainstList() {
BeanWithIntegerList tb = new BeanWithIntegerList();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -469,7 +475,7 @@ class DataBinderTests {
}
@Test
void testBindingErrorWithFormatterAgainstList() {
void bindingErrorWithFormatterAgainstList() {
BeanWithIntegerList tb = new BeanWithIntegerList();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -492,7 +498,7 @@ class DataBinderTests {
}
@Test
void testBindingWithFormatterAgainstFields() {
void bindingWithFormatterAgainstFields() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
@@ -525,7 +531,7 @@ class DataBinderTests {
}
@Test
void testBindingErrorWithFormatterAgainstFields() {
void bindingErrorWithFormatterAgainstFields() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
binder.initDirectFieldAccess();
@@ -549,7 +555,7 @@ class DataBinderTests {
}
@Test
void testBindingWithCustomFormatter() {
void bindingWithCustomFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
binder.addCustomFormatter(new NumberStyleFormatter(), Float.class);
@@ -578,7 +584,7 @@ class DataBinderTests {
}
@Test
void testBindingErrorWithCustomFormatter() {
void bindingErrorWithCustomFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
binder.addCustomFormatter(new NumberStyleFormatter());
@@ -599,7 +605,7 @@ class DataBinderTests {
}
@Test
void testBindingErrorWithParseExceptionFromCustomFormatter() {
void bindingErrorWithParseExceptionFromCustomFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
@@ -624,7 +630,7 @@ class DataBinderTests {
}
@Test
void testBindingErrorWithRuntimeExceptionFromCustomFormatter() {
void bindingErrorWithRuntimeExceptionFromCustomFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
@@ -649,7 +655,7 @@ class DataBinderTests {
}
@Test
void testConversionWithInappropriateStringEditor() {
void conversionWithInappropriateStringEditor() {
DataBinder dataBinder = new DataBinder(null);
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
dataBinder.setConversionService(conversionService);
@@ -662,7 +668,7 @@ class DataBinderTests {
}
@Test
void testBindingWithAllowedFields() throws BindException {
void bindingWithAllowedFields() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod);
binder.setAllowedFields("name", "myparam");
@@ -672,30 +678,32 @@ class DataBinderTests {
binder.bind(pvs);
binder.close();
assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
assertThat(rod.getAge() == 0).as("did not change age").isTrue();
assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
assertThat(rod.getAge()).as("did not change age").isZero();
}
@Test
void testBindingWithDisallowedFields() throws BindException {
void bindingWithDisallowedFields() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod);
binder.setDisallowedFields("age");
binder.setDisallowedFields(" ", "\t", "favouriteColour", null, "age");
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("name", "Rod");
pvs.add("age", "32x");
pvs.add("favouriteColour", "BLUE");
binder.bind(pvs);
binder.close();
assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
assertThat(rod.getAge() == 0).as("did not change age").isTrue();
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
assertThat(disallowedFields.length).isEqualTo(1);
assertThat(disallowedFields[0]).isEqualTo("age");
assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
assertThat(rod.getAge()).as("did not change age").isZero();
assertThat(rod.getFavouriteColour()).as("did not change favourite colour").isNull();
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactlyInAnyOrder("age", "favouriteColour");
}
@Test
void testBindingWithAllowedAndDisallowedFields() throws BindException {
void bindingWithAllowedAndDisallowedFields() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod);
binder.setAllowedFields("name", "myparam");
@@ -706,34 +714,32 @@ class DataBinderTests {
binder.bind(pvs);
binder.close();
assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
assertThat(rod.getAge() == 0).as("did not change age").isTrue();
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
assertThat(disallowedFields).hasSize(1);
assertThat(disallowedFields[0]).isEqualTo("age");
assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
assertThat(rod.getAge()).as("did not change age").isZero();
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("age");
}
@Test
void testBindingWithOverlappingAllowedAndDisallowedFields() throws BindException {
void bindingWithOverlappingAllowedAndDisallowedFields() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod);
binder.setAllowedFields("name", "age");
binder.setDisallowedFields("age");
binder.setDisallowedFields("AGE");
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("name", "Rod");
pvs.add("age", "32x");
binder.bind(pvs);
binder.close();
assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
assertThat(rod.getAge() == 0).as("did not change age").isTrue();
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
assertThat(disallowedFields).hasSize(1);
assertThat(disallowedFields[0]).isEqualTo("age");
assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
assertThat(rod.getAge()).as("did not change age").isZero();
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("age");
}
@Test
void testBindingWithAllowedFieldsUsingAsterisks() throws BindException {
void bindingWithAllowedFieldsUsingAsterisks() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
binder.setAllowedFields("nam*", "*ouchy");
@@ -760,11 +766,11 @@ class DataBinderTests {
}
@Test
void testBindingWithAllowedAndDisallowedMapFields() throws BindException {
void bindingWithAllowedAndDisallowedMapFields() throws BindException {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod);
binder.setAllowedFields("someMap[key1]", "someMap[key2]");
binder.setDisallowedFields("someMap['key3']", "someMap[key4]");
binder.setDisallowedFields("someMap['KEY3']", "SomeMap[key4]");
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("someMap[key1]", "value1");
@@ -774,21 +780,18 @@ class DataBinderTests {
binder.bind(pvs);
binder.close();
assertThat(rod.getSomeMap().get("key1")).isEqualTo("value1");
assertThat(rod.getSomeMap().get("key2")).isEqualTo("value2");
assertThat(rod.getSomeMap().get("key3")).isNull();
assertThat(rod.getSomeMap().get("key4")).isNull();
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
assertThat(disallowedFields).hasSize(2);
assertThat(ObjectUtils.containsElement(disallowedFields, "someMap[key3]")).isTrue();
assertThat(ObjectUtils.containsElement(disallowedFields, "someMap[key4]")).isTrue();
@SuppressWarnings("unchecked")
Map<String, String> someMap = (Map<String, String>) rod.getSomeMap();
assertThat(someMap).containsOnly(entry("key1", "value1"), entry("key2", "value2"));
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("someMap[key3]", "someMap[key4]");
}
/**
* Tests for required field, both null, non-existing and empty strings.
*/
@Test
void testBindingWithRequiredFields() {
void bindingWithRequiredFields() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
@@ -819,7 +822,7 @@ class DataBinderTests {
}
@Test
void testBindingWithRequiredMapFields() {
void bindingWithRequiredMapFields() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
@@ -839,7 +842,7 @@ class DataBinderTests {
}
@Test
void testBindingWithNestedObjectCreation() {
void bindingWithNestedObjectCreation() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "person");
@@ -860,7 +863,7 @@ class DataBinderTests {
}
@Test
void testCustomEditorWithOldValueAccess() {
void customEditorWithOldValueAccess() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
@@ -885,7 +888,7 @@ class DataBinderTests {
}
@Test
void testCustomEditorForSingleProperty() {
void customEditorForSingleProperty() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
DataBinder binder = new DataBinder(tb, "tb");
@@ -925,7 +928,7 @@ class DataBinderTests {
}
@Test
void testCustomEditorForPrimitiveProperty() {
void customEditorForPrimitiveProperty() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
@@ -949,7 +952,7 @@ class DataBinderTests {
}
@Test
void testCustomEditorForAllStringProperties() {
void customEditorForAllStringProperties() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
@@ -981,7 +984,7 @@ class DataBinderTests {
}
@Test
void testCustomFormatterForSingleProperty() {
void customFormatterForSingleProperty() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
DataBinder binder = new DataBinder(tb, "tb");
@@ -1021,7 +1024,7 @@ class DataBinderTests {
}
@Test
void testCustomFormatterForPrimitiveProperty() {
void customFormatterForPrimitiveProperty() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
@@ -1045,7 +1048,7 @@ class DataBinderTests {
}
@Test
void testCustomFormatterForAllStringProperties() {
void customFormatterForAllStringProperties() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
@@ -1077,7 +1080,7 @@ class DataBinderTests {
}
@Test
void testJavaBeanPropertyConventions() {
void javaBeanPropertyConventions() {
Book book = new Book();
DataBinder binder = new DataBinder(book);
@@ -1101,7 +1104,7 @@ class DataBinderTests {
}
@Test
void testOptionalProperty() {
void optionalProperty() {
OptionalHolder bean = new OptionalHolder();
DataBinder binder = new DataBinder(bean);
binder.setConversionService(new DefaultConversionService());
@@ -1122,7 +1125,7 @@ class DataBinderTests {
}
@Test
void testValidatorNoErrors() throws Exception {
void validatorNoErrors() throws Exception {
TestBean tb = new TestBean();
tb.setAge(33);
tb.setName("Rod");
@@ -1175,15 +1178,13 @@ class DataBinderTests {
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
assertThat(errors.getErrorCount()).isEqualTo(1);
boolean condition1 = !errors.hasGlobalErrors();
assertThat(condition1).isTrue();
assertThat(errors.hasGlobalErrors()).isFalse();
assertThat(errors.getFieldErrorCount("age")).isEqualTo(1);
boolean condition = !errors.hasFieldErrors("name");
assertThat(condition).isTrue();
assertThat(errors.hasFieldErrors("name")).isFalse();
}
@Test
void testValidatorWithErrors() {
void validatorWithErrors() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
@@ -1252,7 +1253,7 @@ class DataBinderTests {
}
@Test
void testValidatorWithErrorsAndCodesPrefix() {
void validatorWithErrorsAndCodesPrefix() {
TestBean tb = new TestBean();
tb.setSpouse(new TestBean());
@@ -1324,7 +1325,7 @@ class DataBinderTests {
}
@Test
void testValidatorWithNestedObjectNull() {
void validatorWithNestedObjectNull() {
TestBean tb = new TestBean();
Errors errors = new BeanPropertyBindingResult(tb, "tb");
Validator testValidator = new TestBeanValidator();
@@ -1343,7 +1344,7 @@ class DataBinderTests {
}
@Test
void testNestedValidatorWithoutNestedPath() {
void nestedValidatorWithoutNestedPath() {
TestBean tb = new TestBean();
tb.setName("XXX");
Errors errors = new BeanPropertyBindingResult(tb, "tb");
@@ -1357,7 +1358,8 @@ class DataBinderTests {
}
@Test
void testBindingStringArrayToIntegerSet() {
@SuppressWarnings("unchecked")
void bindingStringArrayToIntegerSet() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(Set.class, new CustomCollectionEditor(TreeSet.class) {
@@ -1371,12 +1373,8 @@ class DataBinderTests {
binder.bind(pvs);
assertThat(binder.getBindingResult().getFieldValue("set")).isEqualTo(tb.getSet());
boolean condition = tb.getSet() instanceof TreeSet;
assertThat(condition).isTrue();
assertThat(tb.getSet().size()).isEqualTo(3);
assertThat(tb.getSet().contains(10)).isTrue();
assertThat(tb.getSet().contains(20)).isTrue();
assertThat(tb.getSet().contains(30)).isTrue();
assertThat(tb.getSet()).isInstanceOf(TreeSet.class);
assertThat((Set<Integer>) tb.getSet()).containsExactly(10, 20, 30);
pvs = new MutablePropertyValues();
pvs.add("set", null);
@@ -1386,7 +1384,7 @@ class DataBinderTests {
}
@Test
void testBindingNullToEmptyCollection() {
void bindingNullToEmptyCollection() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(Set.class, new CustomCollectionEditor(TreeSet.class, true));
@@ -1394,13 +1392,12 @@ class DataBinderTests {
pvs.add("set", null);
binder.bind(pvs);
boolean condition = tb.getSet() instanceof TreeSet;
assertThat(condition).isTrue();
assertThat(tb.getSet().isEmpty()).isTrue();
assertThat(tb.getSet()).isInstanceOf(TreeSet.class);
assertThat(tb.getSet()).isEmpty();
}
@Test
void testBindingToIndexedField() {
void bindingToIndexedField() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(String.class, "array.name", new PropertyEditorSupport() {
@@ -1439,7 +1436,7 @@ class DataBinderTests {
}
@Test
void testBindingToNestedIndexedField() {
void bindingToNestedIndexedField() {
IndexedTestBean tb = new IndexedTestBean();
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
@@ -1470,7 +1467,7 @@ class DataBinderTests {
}
@Test
void testEditorForNestedIndexedField() {
void editorForNestedIndexedField() {
IndexedTestBean tb = new IndexedTestBean();
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
@@ -1496,7 +1493,7 @@ class DataBinderTests {
}
@Test
void testSpecificEditorForNestedIndexedField() {
void specificEditorForNestedIndexedField() {
IndexedTestBean tb = new IndexedTestBean();
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
@@ -1522,7 +1519,7 @@ class DataBinderTests {
}
@Test
void testInnerSpecificEditorForNestedIndexedField() {
void innerSpecificEditorForNestedIndexedField() {
IndexedTestBean tb = new IndexedTestBean();
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
@@ -1548,7 +1545,7 @@ class DataBinderTests {
}
@Test
void testDirectBindingToIndexedField() {
void directBindingToIndexedField() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(TestBean.class, "array", new PropertyEditorSupport() {
@@ -1601,7 +1598,7 @@ class DataBinderTests {
}
@Test
void testDirectBindingToEmptyIndexedFieldWithRegisteredSpecificEditor() {
void directBindingToEmptyIndexedFieldWithRegisteredSpecificEditor() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(TestBean.class, "map[key0]", new PropertyEditorSupport() {
@@ -1632,7 +1629,7 @@ class DataBinderTests {
}
@Test
void testDirectBindingToEmptyIndexedFieldWithRegisteredGenericEditor() {
void directBindingToEmptyIndexedFieldWithRegisteredGenericEditor() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(TestBean.class, "map", new PropertyEditorSupport() {
@@ -1663,7 +1660,7 @@ class DataBinderTests {
}
@Test
void testCustomEditorWithSubclass() {
void customEditorWithSubclass() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(TestBean.class, new PropertyEditorSupport() {
@@ -1697,7 +1694,7 @@ class DataBinderTests {
}
@Test
void testBindToStringArrayWithArrayEditor() {
void bindToStringArrayWithArrayEditor() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(String[].class, "stringArray", new PropertyEditorSupport() {
@@ -1709,15 +1706,12 @@ class DataBinderTests {
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("stringArray", "a1-b2");
binder.bind(pvs);
boolean condition = !binder.getBindingResult().hasErrors();
assertThat(condition).isTrue();
assertThat(tb.getStringArray().length).isEqualTo(2);
assertThat(tb.getStringArray()[0]).isEqualTo("a1");
assertThat(tb.getStringArray()[1]).isEqualTo("b2");
assertThat(binder.getBindingResult().hasErrors()).isFalse();
assertThat(tb.getStringArray()).containsExactly("a1", "b2");
}
@Test
void testBindToStringArrayWithComponentEditor() {
void bindToStringArrayWithComponentEditor() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb, "tb");
binder.registerCustomEditor(String.class, "stringArray", new PropertyEditorSupport() {
@@ -1729,15 +1723,14 @@ class DataBinderTests {
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.add("stringArray", new String[] {"a1", "b2"});
binder.bind(pvs);
boolean condition = !binder.getBindingResult().hasErrors();
assertThat(condition).isTrue();
assertThat(binder.getBindingResult().hasErrors()).isFalse();
assertThat(tb.getStringArray().length).isEqualTo(2);
assertThat(tb.getStringArray()[0]).isEqualTo("Xa1");
assertThat(tb.getStringArray()[1]).isEqualTo("Xb2");
}
@Test
void testBindingErrors() {
void bindingErrors() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -1764,7 +1757,7 @@ class DataBinderTests {
}
@Test
void testAddAllErrors() {
void addAllErrors() {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod, "person");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -1784,7 +1777,7 @@ class DataBinderTests {
@Test
@SuppressWarnings("unchecked")
void testBindingWithResortedList() {
void bindingWithResortedList() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");
MutablePropertyValues pvs = new MutablePropertyValues();
@@ -1802,7 +1795,7 @@ class DataBinderTests {
}
@Test
void testRejectWithoutDefaultMessage() {
void rejectWithoutDefaultMessage() {
TestBean tb = new TestBean();
tb.setName("myName");
tb.setAge(99);
@@ -1820,7 +1813,7 @@ class DataBinderTests {
}
@Test
void testBindExceptionSerializable() throws Exception {
void bindExceptionSerializable() throws Exception {
SerializablePerson tb = new SerializablePerson();
tb.setName("myName");
tb.setAge(99);
@@ -1849,27 +1842,27 @@ class DataBinderTests {
}
@Test
void testTrackDisallowedFields() {
void trackDisallowedFields() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.setAllowedFields("name", "age");
String name = "Rob Harrop";
String beanName = "foobar";
int age = 42;
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("name", name);
mpvs.add("beanName", beanName);
mpvs.add("age", age);
mpvs.add("beanName", "foobar");
binder.bind(mpvs);
assertThat(testBean.getName()).isEqualTo(name);
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
assertThat(disallowedFields).hasSize(1);
assertThat(disallowedFields[0]).isEqualTo("beanName");
assertThat(testBean.getAge()).isEqualTo(age);
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("beanName");
}
@Test
void testAutoGrowWithinDefaultLimit() {
void autoGrowWithinDefaultLimit() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
@@ -1881,7 +1874,7 @@ class DataBinderTests {
}
@Test
void testAutoGrowBeyondDefaultLimit() {
void autoGrowBeyondDefaultLimit() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
@@ -1894,7 +1887,7 @@ class DataBinderTests {
}
@Test
void testAutoGrowWithinCustomLimit() {
void autoGrowWithinCustomLimit() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.setAutoGrowCollectionLimit(10);
@@ -1907,7 +1900,7 @@ class DataBinderTests {
}
@Test
void testAutoGrowBeyondCustomLimit() {
void autoGrowBeyondCustomLimit() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.setAutoGrowCollectionLimit(10);
@@ -1921,7 +1914,7 @@ class DataBinderTests {
}
@Test
void testNestedGrowingList() {
void nestedGrowingList() {
Form form = new Form();
DataBinder binder = new DataBinder(form, "form");
MutablePropertyValues mpv = new MutablePropertyValues();
@@ -1937,7 +1930,7 @@ class DataBinderTests {
}
@Test
void testFieldErrorAccessVariations() {
void fieldErrorAccessVariations() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
assertThat(binder.getBindingResult().getGlobalError()).isNull();
@@ -1958,7 +1951,7 @@ class DataBinderTests {
}
@Test // SPR-14888
void testSetAutoGrowCollectionLimit() {
void setAutoGrowCollectionLimit() {
BeanWithIntegerList tb = new BeanWithIntegerList();
DataBinder binder = new DataBinder(tb);
binder.setAutoGrowCollectionLimit(257);
@@ -1972,7 +1965,7 @@ class DataBinderTests {
}
@Test // SPR-14888
void testSetAutoGrowCollectionLimitAfterInitialization() {
void setAutoGrowCollectionLimitAfterInitialization() {
DataBinder binder = new DataBinder(new BeanWithIntegerList());
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
assertThatIllegalStateException().isThrownBy(() ->
@@ -1981,7 +1974,7 @@ class DataBinderTests {
}
@Test // SPR-15009
void testSetCustomMessageCodesResolverBeforeInitializeBindingResultForBeanPropertyAccess() {
void setCustomMessageCodesResolverBeforeInitializeBindingResultForBeanPropertyAccess() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
DefaultMessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver();
@@ -1998,7 +1991,7 @@ class DataBinderTests {
}
@Test // SPR-15009
void testSetCustomMessageCodesResolverBeforeInitializeBindingResultForDirectFieldAccess() {
void setCustomMessageCodesResolverBeforeInitializeBindingResultForDirectFieldAccess() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
DefaultMessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver();
@@ -2013,7 +2006,7 @@ class DataBinderTests {
}
@Test // SPR-15009
void testSetCustomMessageCodesResolverAfterInitializeBindingResult() {
void setCustomMessageCodesResolverAfterInitializeBindingResult() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.initBeanPropertyAccess();
@@ -2028,7 +2021,7 @@ class DataBinderTests {
}
@Test // SPR-15009
void testSetMessageCodesResolverIsNullAfterInitializeBindingResult() {
void setMessageCodesResolverIsNullAfterInitializeBindingResult() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.initBeanPropertyAccess();
@@ -2042,8 +2035,7 @@ class DataBinderTests {
}
@Test // SPR-15009
void testCallSetMessageCodesResolverTwice() {
void callSetMessageCodesResolverTwice() {
TestBean testBean = new TestBean();
DataBinder binder = new DataBinder(testBean, "testBean");
binder.setMessageCodesResolver(new DefaultMessageCodesResolver());
@@ -31,6 +31,7 @@ import java.util.Set;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.ConstraintValidatorFactory;
import javax.validation.ConstraintViolation;
import javax.validation.Payload;
import javax.validation.Valid;
@@ -43,6 +44,7 @@ import org.hibernate.validator.HibernateValidatorFactory;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.convert.support.DefaultConversionService;
@@ -313,6 +315,32 @@ class ValidatorFactoryTests {
validator.destroy();
}
@Test
void withConstraintValidatorFactory() {
ConstraintValidatorFactory cvf = new SpringConstraintValidatorFactory(new DefaultListableBeanFactory());
@SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setConstraintValidatorFactory(cvf);
validator.afterPropertiesSet();
assertThat(validator.getConstraintValidatorFactory()).isSameAs(cvf);
validator.destroy();
}
@Test
void withCustomInitializer() {
ConstraintValidatorFactory cvf = new SpringConstraintValidatorFactory(new DefaultListableBeanFactory());
@SuppressWarnings("resource")
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setConfigurationInitializer(configuration -> configuration.constraintValidatorFactory(cvf));
validator.afterPropertiesSet();
assertThat(validator.getConstraintValidatorFactory()).isSameAs(cvf);
validator.destroy();
}
@NameAddressValid
public static class ValidPerson {
@@ -409,8 +437,8 @@ class ValidatorFactoryTests {
}
boolean valid = (value.name == null || !value.address.street.contains(value.name));
if (!valid && "Phil".equals(value.name)) {
context.buildConstraintViolationWithTemplate(
context.getDefaultConstraintMessageTemplate()).addPropertyNode("address").addConstraintViolation().disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode("address").addConstraintViolation().disableDefaultConstraintViolation();
}
return valid;
}
@@ -446,6 +474,7 @@ class ValidatorFactoryTests {
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@@ -454,7 +483,7 @@ class ValidatorFactoryTests {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Constraint(validatedBy=InnerValidator.class)
@Constraint(validatedBy = InnerValidator.class)
public @interface InnerValid {
String message() default "NOT VALID";
@@ -475,7 +504,8 @@ class ValidatorFactoryTests {
public boolean isValid(InnerBean bean, ConstraintValidatorContext context) {
context.disableDefaultConstraintViolation();
if (bean.getValue() == null) {
context.buildConstraintViolationWithTemplate("NULL").addPropertyNode("value").addConstraintViolation();
context.buildConstraintViolationWithTemplate("NULL")
.addPropertyNode("value").addConstraintViolation();
return false;
}
return true;
@@ -523,7 +553,8 @@ class ValidatorFactoryTests {
boolean valid = true;
for (int i = 0; i < list.size(); i++) {
if ("X".equals(list.get(i))) {
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).addBeanNode().inIterable().atIndex(i).addConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addBeanNode().inIterable().atIndex(i).addConstraintViolation();
valid = false;
}
}
@@ -5,19 +5,19 @@
<bean id="publisher" class="org.springframework.jmx.export.NotificationPublisherTests$MyNotificationPublisher"/>
<bean id="publisherMBean" class="org.springframework.jmx.export.NotificationPublisherTests$MyNotificationPublisherMBean"/>
<bean id="publisherMBean" class="org.springframework.jmx.export.NotificationPublisherTests$MyNotificationPublisherMBean"/>
<bean id="publisherStandardMBean" class="org.springframework.jmx.export.NotificationPublisherTests$MyNotificationPublisherStandardMBean"/>
<bean id="publisherStandardMBean" class="org.springframework.jmx.export.NotificationPublisherTests$MyNotificationPublisherStandardMBean"/>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="spring:type=Publisher" value-ref="publisher"/>
<entry key="spring:type=PublisherMBean" value-ref="publisherMBean"/>
<entry key="spring:type=PublisherStandardMBean" value-ref="publisherStandardMBean"/>
<entry key="spring:type=PublisherMBean" value-ref="publisherMBean"/>
<entry key="spring:type=PublisherStandardMBean" value-ref="publisherStandardMBean"/>
</map>
</property>
<property name="server" ref="server"/>
<property name="server" ref="server"/>
</bean>
</beans>
@@ -55,7 +55,7 @@ public abstract class AnnotationVisitor {
* @param api the ASM API version implemented by this visitor. Must be one of the {@code
* ASM}<i>x</i> values in {@link Opcodes}.
*/
public AnnotationVisitor(final int api) {
protected AnnotationVisitor(final int api) {
this(api, null);
}
@@ -67,7 +67,7 @@ public abstract class AnnotationVisitor {
* @param annotationVisitor the annotation visitor to which this visitor must delegate method
* calls. May be {@literal null}.
*/
public AnnotationVisitor(final int api, final AnnotationVisitor annotationVisitor) {
protected AnnotationVisitor(final int api, final AnnotationVisitor annotationVisitor) {
if (api != Opcodes.ASM9
&& api != Opcodes.ASM8
&& api != Opcodes.ASM7
@@ -65,6 +65,15 @@ public class ByteVector {
this.length = data.length;
}
/**
* Returns the actual number of bytes in this vector.
*
* @return the actual number of bytes in this vector.
*/
public int size() {
return length;
}
/**
* Puts a byte into this byte vector. The byte vector is automatically enlarged if necessary.
*
@@ -54,7 +54,7 @@ public abstract class ClassVisitor {
* @param api the ASM API version implemented by this visitor. Must be one of the {@code
* ASM}<i>x</i> values in {@link Opcodes}.
*/
public ClassVisitor(final int api) {
protected ClassVisitor(final int api) {
this(api, null);
}
@@ -66,7 +66,7 @@ public abstract class ClassVisitor {
* @param classVisitor the class visitor to which this visitor must delegate method calls. May be
* null.
*/
public ClassVisitor(final int api, final ClassVisitor classVisitor) {
protected ClassVisitor(final int api, final ClassVisitor classVisitor) {
if (api != Opcodes.ASM9
&& api != Opcodes.ASM8
&& api != Opcodes.ASM7
@@ -51,7 +51,7 @@ public abstract class FieldVisitor {
* @param api the ASM API version implemented by this visitor. Must be one of the {@code
* ASM}<i>x</i> values in {@link Opcodes}.
*/
public FieldVisitor(final int api) {
protected FieldVisitor(final int api) {
this(api, null);
}
@@ -63,7 +63,7 @@ public abstract class FieldVisitor {
* @param fieldVisitor the field visitor to which this visitor must delegate method calls. May be
* null.
*/
public FieldVisitor(final int api, final FieldVisitor fieldVisitor) {
protected FieldVisitor(final int api, final FieldVisitor fieldVisitor) {
if (api != Opcodes.ASM9
&& api != Opcodes.ASM8
&& api != Opcodes.ASM7
@@ -67,7 +67,7 @@ public abstract class MethodVisitor {
* @param api the ASM API version implemented by this visitor. Must be one of the {@code
* ASM}<i>x</i> values in {@link Opcodes}.
*/
public MethodVisitor(final int api) {
protected MethodVisitor(final int api) {
this(api, null);
}
@@ -79,7 +79,7 @@ public abstract class MethodVisitor {
* @param methodVisitor the method visitor to which this visitor must delegate method calls. May
* be null.
*/
public MethodVisitor(final int api, final MethodVisitor methodVisitor) {
protected MethodVisitor(final int api, final MethodVisitor methodVisitor) {
if (api != Opcodes.ASM9
&& api != Opcodes.ASM8
&& api != Opcodes.ASM7
@@ -349,12 +349,12 @@ public abstract class MethodVisitor {
*
* @param opcode the opcode of the local variable instruction to be visited. This opcode is either
* ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET.
* @param var the operand of the instruction to be visited. This operand is the index of a local
* variable.
* @param varIndex the operand of the instruction to be visited. This operand is the index of a
* local variable.
*/
public void visitVarInsn(final int opcode, final int var) {
public void visitVarInsn(final int opcode, final int varIndex) {
if (mv != null) {
mv.visitVarInsn(opcode, var);
mv.visitVarInsn(opcode, varIndex);
}
}
@@ -466,7 +466,8 @@ final class MethodWriter extends MethodVisitor {
/**
* Indicates what must be computed. Must be one of {@link #COMPUTE_ALL_FRAMES}, {@link
* #COMPUTE_INSERTED_FRAMES}, {@link #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_NOTHING}.
* #COMPUTE_INSERTED_FRAMES}, {@link COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link
* #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_NOTHING}.
*/
private final int compute;
@@ -904,26 +905,26 @@ final class MethodWriter extends MethodVisitor {
}
@Override
public void visitVarInsn(final int opcode, final int var) {
public void visitVarInsn(final int opcode, final int varIndex) {
lastBytecodeOffset = code.length;
// Add the instruction to the bytecode of the method.
if (var < 4 && opcode != Opcodes.RET) {
if (varIndex < 4 && opcode != Opcodes.RET) {
int optimizedOpcode;
if (opcode < Opcodes.ISTORE) {
optimizedOpcode = Constants.ILOAD_0 + ((opcode - Opcodes.ILOAD) << 2) + var;
optimizedOpcode = Constants.ILOAD_0 + ((opcode - Opcodes.ILOAD) << 2) + varIndex;
} else {
optimizedOpcode = Constants.ISTORE_0 + ((opcode - Opcodes.ISTORE) << 2) + var;
optimizedOpcode = Constants.ISTORE_0 + ((opcode - Opcodes.ISTORE) << 2) + varIndex;
}
code.putByte(optimizedOpcode);
} else if (var >= 256) {
code.putByte(Constants.WIDE).put12(opcode, var);
} else if (varIndex >= 256) {
code.putByte(Constants.WIDE).put12(opcode, varIndex);
} else {
code.put11(opcode, var);
code.put11(opcode, varIndex);
}
// If needed, update the maximum stack size and number of locals, and stack map frames.
if (currentBasicBlock != null) {
if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) {
currentBasicBlock.frame.execute(opcode, var, null, null);
currentBasicBlock.frame.execute(opcode, varIndex, null, null);
} else {
if (opcode == Opcodes.RET) {
// No stack size delta.
@@ -945,9 +946,9 @@ final class MethodWriter extends MethodVisitor {
|| opcode == Opcodes.DLOAD
|| opcode == Opcodes.LSTORE
|| opcode == Opcodes.DSTORE) {
currentMaxLocals = var + 2;
currentMaxLocals = varIndex + 2;
} else {
currentMaxLocals = var + 1;
currentMaxLocals = varIndex + 1;
}
if (currentMaxLocals > maxLocals) {
maxLocals = currentMaxLocals;
@@ -1307,21 +1308,21 @@ final class MethodWriter extends MethodVisitor {
}
@Override
public void visitIincInsn(final int var, final int increment) {
public void visitIincInsn(final int varIndex, final int increment) {
lastBytecodeOffset = code.length;
// Add the instruction to the bytecode of the method.
if ((var > 255) || (increment > 127) || (increment < -128)) {
code.putByte(Constants.WIDE).put12(Opcodes.IINC, var).putShort(increment);
if ((varIndex > 255) || (increment > 127) || (increment < -128)) {
code.putByte(Constants.WIDE).put12(Opcodes.IINC, varIndex).putShort(increment);
} else {
code.putByte(Opcodes.IINC).put11(var, increment);
code.putByte(Opcodes.IINC).put11(varIndex, increment);
}
// If needed, update the maximum stack size and number of locals, and stack map frames.
if (currentBasicBlock != null
&& (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES)) {
currentBasicBlock.frame.execute(Opcodes.IINC, var, null, null);
currentBasicBlock.frame.execute(Opcodes.IINC, varIndex, null, null);
}
if (compute != COMPUTE_NOTHING) {
int currentMaxLocals = var + 1;
int currentMaxLocals = varIndex + 1;
if (currentMaxLocals > maxLocals) {
maxLocals = currentMaxLocals;
}
@@ -53,7 +53,7 @@ public abstract class ModuleVisitor {
* @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM6}
* or {@link Opcodes#ASM7}.
*/
public ModuleVisitor(final int api) {
protected ModuleVisitor(final int api) {
this(api, null);
}
@@ -65,7 +65,7 @@ public abstract class ModuleVisitor {
* @param moduleVisitor the module visitor to which this visitor must delegate method calls. May
* be null.
*/
public ModuleVisitor(final int api, final ModuleVisitor moduleVisitor) {
protected ModuleVisitor(final int api, final ModuleVisitor moduleVisitor) {
if (api != Opcodes.ASM9
&& api != Opcodes.ASM8
&& api != Opcodes.ASM7
@@ -53,7 +53,7 @@ public abstract class RecordComponentVisitor {
* @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM8}
* or {@link Opcodes#ASM9}.
*/
public RecordComponentVisitor(final int api) {
protected RecordComponentVisitor(final int api) {
this(api, null);
}
@@ -64,7 +64,7 @@ public abstract class RecordComponentVisitor {
* @param recordComponentVisitor the record component visitor to which this visitor must delegate
* method calls. May be null.
*/
public RecordComponentVisitor(
protected RecordComponentVisitor(
final int api, final RecordComponentVisitor recordComponentVisitor) {
if (api != Opcodes.ASM9
&& api != Opcodes.ASM8
@@ -440,7 +440,7 @@ public final class Type {
case '(':
return new Type(METHOD, descriptorBuffer, descriptorBegin, descriptorEnd);
default:
throw new IllegalArgumentException();
throw new IllegalArgumentException("Invalid descriptor: " + descriptorBuffer);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,7 +47,7 @@ import org.springframework.util.StringUtils;
*/
public final class Property {
private static Map<Property, Annotation[]> annotationCache = new ConcurrentReferenceHashMap<>();
private static final Map<Property, Annotation[]> annotationCache = new ConcurrentReferenceHashMap<>();
private final Class<?> objectType;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,7 +26,7 @@ import org.springframework.util.StringUtils;
* Converts from a String to a {@link java.util.Locale}.
*
* <p>Accepts the classic {@link Locale} String format ({@link Locale#toString()})
* as well as BCP 47 language tags ({@link Locale#forLanguageTag} on Java 7+).
* as well as BCP 47 language tags ({@link Locale#forLanguageTag}.
*
* @author Keith Donald
* @author Juergen Hoeller
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -99,14 +99,14 @@ public interface Resource extends InputStreamSource {
/**
* Return a URL handle for this resource.
* @throws IOException if the resource cannot be resolved as URL,
* i.e. if the resource is not available as descriptor
* i.e. if the resource is not available as a descriptor
*/
URL getURL() throws IOException;
/**
* Return a URI handle for this resource.
* @throws IOException if the resource cannot be resolved as URI,
* i.e. if the resource is not available as descriptor
* i.e. if the resource is not available as a descriptor
* @since 2.5
*/
URI getURI() throws IOException;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1061,7 +1061,7 @@ public abstract class DataBufferUtils {
@Override
public Context currentContext() {
return this.sink.currentContext();
return Context.of(this.sink.contextView());
}
}
@@ -1158,7 +1158,7 @@ public abstract class DataBufferUtils {
@Override
public Context currentContext() {
return this.sink.currentContext();
return Context.of(this.sink.contextView());
}
}
@@ -842,6 +842,20 @@ public abstract class ClassUtils {
return (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers()));
}
/**
* Determine if the supplied {@link Class} is a JVM-generated implementation
* class for a lambda expression or method reference.
* <p>This method makes a best-effort attempt at determining this, based on
* checks that work on modern, mainstream JVMs.
* @param clazz the class to check
* @return {@code true} if the class is a lambda implementation class
* @since 5.3.19
*/
public static boolean isLambdaClass(Class<?> clazz) {
return (clazz.isSynthetic() && (clazz.getSuperclass() == Object.class) &&
(clazz.getInterfaces().length > 0) && clazz.getName().contains("$$Lambda"));
}
/**
* Check whether the given object is a CGLIB proxy.
* @param object the object to check
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,8 +35,8 @@ import java.util.Properties;
* <p>Loading from and storing to a stream delegates to {@code Properties.load}
* and {@code Properties.store}, respectively, to be fully compatible with
* the Unicode conversion as implemented by the JDK Properties class. As of JDK 6,
* {@code Properties.load/store} will also be used for readers/writers,
* effectively turning this class into a plain backwards compatibility adapter.
* {@code Properties.load/store} is also used for readers/writers, effectively
* turning this class into a plain backwards compatibility adapter.
*
* <p>The persistence code that works with Reader/Writer follows the JDK's parsing
* strategy but does not implement Unicode conversion, because the Reader/Writer
@@ -65,9 +65,22 @@ public abstract class MimeTypeUtils {
*/
public static final String ALL_VALUE = "*/*";
/**
* Public constant mime type for {@code application/graphql+json}.
* @since 5.3.19
* @see <a href="https://github.com/graphql/graphql-over-http">GraphQL over HTTP spec</a>
*/
public static final MimeType APPLICATION_GRAPHQL;
/**
* A String equivalent of {@link MimeTypeUtils#APPLICATION_GRAPHQL}.
* @since 5.3.19
*/
public static final String APPLICATION_GRAPHQL_VALUE = "application/graphql+json";
/**
* Public constant mime type for {@code application/json}.
* */
*/
public static final MimeType APPLICATION_JSON;
/**
@@ -165,6 +178,7 @@ public abstract class MimeTypeUtils {
static {
// Not using "parseMimeType" to avoid static init cost
ALL = new MimeType("*", "*");
APPLICATION_GRAPHQL = new MimeType("application", "graphql+json");
APPLICATION_JSON = new MimeType("application", "json");
APPLICATION_OCTET_STREAM = new MimeType("application", "octet-stream");
APPLICATION_XML = new MimeType("application", "xml");
@@ -804,11 +804,11 @@ public abstract class StringUtils {
/**
* Parse the given {@code String} value into a {@link Locale}, accepting
* the {@link Locale#toString} format as well as BCP 47 language tags.
* the {@link Locale#toString} format as well as BCP 47 language tags as
* specified by {@link Locale#forLanguageTag}.
* @param localeValue the locale value: following either {@code Locale's}
* {@code toString()} format ("en", "en_UK", etc), also accepting spaces as
* separators (as an alternative to underscores), or BCP 47 (e.g. "en-UK")
* as specified by {@link Locale#forLanguageTag} on Java 7+
* @return a corresponding {@code Locale} instance, or {@code null} if none
* @throws IllegalArgumentException in case of an invalid locale specification
* @since 5.0.4
@@ -1,6 +1,6 @@
/**
* Miscellaneous utility classes, such as String manipulation utilities,
* a Log4J configurer, and a state holder for paged lists of objects.
* Miscellaneous utility classes, such as utilities for working with strings,
* classes, collections, reflection, etc.
*/
@NonNullApi
@NonNullFields
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
@@ -408,6 +409,29 @@ class ClassUtilsTests {
assertThat(ClassUtils.isPrimitiveOrWrapper(type)).isTrue();
}
@Test
void isLambda() {
assertIsLambda(ClassUtilsTests.staticLambdaExpression);
assertIsLambda(ClassUtilsTests::staticStringFactory);
assertIsLambda(this.instanceLambdaExpression);
assertIsLambda(this::instanceStringFactory);
}
@Test
void isNotLambda() {
assertIsNotLambda(new EnigmaSupplier());
assertIsNotLambda(new Supplier<String>() {
@Override
public String get() {
return "anonymous inner class";
}
});
assertIsNotLambda(new Fake$$LambdaSupplier());
}
@Nested
class GetStaticMethodTests {
@@ -500,4 +524,38 @@ class ClassUtilsTests {
}
}
private static void assertIsLambda(Supplier<String> supplier) {
assertThat(ClassUtils.isLambdaClass(supplier.getClass())).isTrue();
}
private static void assertIsNotLambda(Supplier<String> supplier) {
assertThat(ClassUtils.isLambdaClass(supplier.getClass())).isFalse();
}
private static final Supplier<String> staticLambdaExpression = () -> "static lambda expression";
private final Supplier<String> instanceLambdaExpression = () -> "instance lambda expressions";
private static String staticStringFactory() {
return "static string factory";
}
private String instanceStringFactory() {
return "instance string factory";
}
private static class EnigmaSupplier implements Supplier<String> {
@Override
public String get() {
return "enigma";
}
}
private static class Fake$$LambdaSupplier implements Supplier<String> {
@Override
public String get() {
return "fake lambda";
}
}
}
@@ -288,8 +288,8 @@ public class ConstructorReference extends SpelNodeImpl {
else {
// There is an initializer
if (this.dimensions == null || this.dimensions.length > 1) {
// There is an initializer but this is a multi-dimensional array (e.g. new int[][]{{1,2},{3,4}}) - this
// is not currently supported
// There is an initializer but this is a multi-dimensional array (e.g. new int[][]{{1,2},{3,4}})
// - this is not currently supported
throw new SpelEvaluationException(getStartPosition(),
SpelMessage.MULTIDIM_ARRAY_INITIALIZER_NOT_SUPPORTED);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.ObjectUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -27,45 +28,33 @@ import static org.assertj.core.api.Assertions.assertThat;
* Test construction of arrays.
*
* @author Andy Clement
* @author Sam Brannen
*/
public class ArrayConstructorTests extends AbstractExpressionTests {
class ArrayConstructorTests extends AbstractExpressionTests {
@Test
public void simpleArrayWithInitializer() {
evaluateArrayBuildingExpression("new int[]{1,2,3}", "[1,2,3]");
evaluateArrayBuildingExpression("new int[]{}", "[]");
evaluate("new int[]{}.length", "0", Integer.class);
}
@Test
public void conversion() {
void conversion() {
evaluate("new String[]{1,2,3}[0]", "1", String.class);
evaluate("new int[]{'123'}[0]", 123, Integer.class);
}
@Test
public void multidimensionalArrays() {
evaluateAndCheckError("new int[][]{{1,2},{3,4}}", SpelMessage.MULTIDIM_ARRAY_INITIALIZER_NOT_SUPPORTED);
evaluateAndCheckError("new int[3][]", SpelMessage.MISSING_ARRAY_DIMENSION);
evaluateAndCheckError("new int[]", SpelMessage.MISSING_ARRAY_DIMENSION);
evaluateAndCheckError("new String[]", SpelMessage.MISSING_ARRAY_DIMENSION);
evaluateAndCheckError("new int[][1]", SpelMessage.MISSING_ARRAY_DIMENSION);
void primitiveTypeArrayConstructors() {
evaluateArrayBuildingExpression("new int[]{}", "{}");
evaluateArrayBuildingExpression("new int[]{1,2,3,4}", "{1, 2, 3, 4}");
evaluateArrayBuildingExpression("new boolean[]{true,false,true}", "{true, false, true}");
evaluateArrayBuildingExpression("new char[]{'a','b','c'}", "{'a', 'b', 'c'}");
evaluateArrayBuildingExpression("new long[]{1,2,3,4,5}", "{1, 2, 3, 4, 5}");
evaluateArrayBuildingExpression("new short[]{2,3,4,5,6}", "{2, 3, 4, 5, 6}");
evaluateArrayBuildingExpression("new double[]{1d,2d,3d,4d}", "{1.0, 2.0, 3.0, 4.0}");
evaluateArrayBuildingExpression("new float[]{1f,2f,3f,4f}", "{1.0, 2.0, 3.0, 4.0}");
evaluateArrayBuildingExpression("new byte[]{1,2,3,4}", "{1, 2, 3, 4}");
evaluate("new int[]{}.length", "0", Integer.class);
}
@Test
public void primitiveTypeArrayConstructors() {
evaluateArrayBuildingExpression("new int[]{1,2,3,4}", "[1,2,3,4]");
evaluateArrayBuildingExpression("new boolean[]{true,false,true}", "[true,false,true]");
evaluateArrayBuildingExpression("new char[]{'a','b','c'}", "[a,b,c]");
evaluateArrayBuildingExpression("new long[]{1,2,3,4,5}", "[1,2,3,4,5]");
evaluateArrayBuildingExpression("new short[]{2,3,4,5,6}", "[2,3,4,5,6]");
evaluateArrayBuildingExpression("new double[]{1d,2d,3d,4d}", "[1.0,2.0,3.0,4.0]");
evaluateArrayBuildingExpression("new float[]{1f,2f,3f,4f}", "[1.0,2.0,3.0,4.0]");
evaluateArrayBuildingExpression("new byte[]{1,2,3,4}", "[1,2,3,4]");
}
@Test
public void primitiveTypeArrayConstructorsElements() {
void primitiveTypeArrayConstructorsElements() {
evaluate("new int[]{1,2,3,4}[0]", 1, Integer.class);
evaluate("new boolean[]{true,false,true}[0]", true, Boolean.class);
evaluate("new char[]{'a','b','c'}[0]", 'a', Character.class);
@@ -78,15 +67,34 @@ public class ArrayConstructorTests extends AbstractExpressionTests {
}
@Test
public void errorCases() {
void errorCases() {
evaluateAndCheckError("new int[]", SpelMessage.MISSING_ARRAY_DIMENSION);
evaluateAndCheckError("new String[]", SpelMessage.MISSING_ARRAY_DIMENSION);
evaluateAndCheckError("new int[3][]", SpelMessage.MISSING_ARRAY_DIMENSION);
evaluateAndCheckError("new int[][1]", SpelMessage.MISSING_ARRAY_DIMENSION);
evaluateAndCheckError("new char[7]{'a','c','d','e'}", SpelMessage.INITIALIZER_LENGTH_INCORRECT);
evaluateAndCheckError("new char[3]{'a','c','d','e'}", SpelMessage.INITIALIZER_LENGTH_INCORRECT);
evaluateAndCheckError("new int[][]{{1,2},{3,4}}", SpelMessage.MULTIDIM_ARRAY_INITIALIZER_NOT_SUPPORTED);
evaluateAndCheckError("new char[2]{'hello','world'}", SpelMessage.TYPE_CONVERSION_ERROR);
// Could conceivably be a SpelMessage.INCORRECT_ELEMENT_TYPE_FOR_ARRAY, but it appears
// that SpelMessage.INCORRECT_ELEMENT_TYPE_FOR_ARRAY is not actually (no longer?) used
// in the code base.
evaluateAndCheckError("new Integer[3]{'3','ghi','5'}", SpelMessage.TYPE_CONVERSION_ERROR);
evaluateAndCheckError("new String('a','c','d')", SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM);
// Root cause: java.lang.OutOfMemoryError: Requested array size exceeds VM limit
evaluateAndCheckError("new java.util.ArrayList(T(java.lang.Integer).MAX_VALUE)", SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM);
int threshold = 256 * 1024; // ConstructorReference.MAX_ARRAY_ELEMENTS
evaluateAndCheckError("new int[T(java.lang.Integer).MAX_VALUE]", SpelMessage.MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED, 0, threshold);
evaluateAndCheckError("new int[1024 * 1024][1024 * 1024]", SpelMessage.MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED, 0, threshold);
}
@Test
public void typeArrayConstructors() {
void typeArrayConstructors() {
evaluate("new String[]{'a','b','c','d'}[1]", "b", String.class);
evaluateAndCheckError("new String[]{'a','b','c','d'}.size()", SpelMessage.METHOD_NOT_FOUND, 30, "size()",
"java.lang.String[]");
@@ -94,113 +102,25 @@ public class ArrayConstructorTests extends AbstractExpressionTests {
}
@Test
public void basicArray() {
void basicArray() {
evaluate("new String[3]", "java.lang.String[3]{null,null,null}", String[].class);
}
@Test
public void multiDimensionalArray() {
void multiDimensionalArrays() {
evaluate("new String[2][2]", "[Ljava.lang.String;[2]{[2]{null,null},[2]{null,null}}", String[][].class);
evaluate("new String[3][2][1]",
"[[Ljava.lang.String;[3]{[2]{[1]{null},[1]{null}},[2]{[1]{null},[1]{null}},[2]{[1]{null},[1]{null}}}",
String[][][].class);
}
@Test
public void constructorInvocation03() {
evaluateAndCheckError("new String[]", SpelMessage.MISSING_ARRAY_DIMENSION);
}
public void constructorInvocation04() {
evaluateAndCheckError("new Integer[3]{'3','ghi','5'}", SpelMessage.INCORRECT_ELEMENT_TYPE_FOR_ARRAY, 4);
}
private String evaluateArrayBuildingExpression(String expression, String expectedToString) {
private void evaluateArrayBuildingExpression(String expression, String expectedToString) {
SpelExpressionParser parser = new SpelExpressionParser();
Expression e = parser.parseExpression(expression);
Object o = e.getValue();
assertThat(o).isNotNull();
assertThat(o.getClass().isArray()).isTrue();
StringBuilder s = new StringBuilder();
s.append('[');
if (o instanceof int[]) {
int[] array = (int[]) o;
for (int i = 0; i < array.length; i++) {
if (i > 0) {
s.append(',');
}
s.append(array[i]);
}
}
else if (o instanceof boolean[]) {
boolean[] array = (boolean[]) o;
for (int i = 0; i < array.length; i++) {
if (i > 0) {
s.append(',');
}
s.append(array[i]);
}
}
else if (o instanceof char[]) {
char[] array = (char[]) o;
for (int i = 0; i < array.length; i++) {
if (i > 0) {
s.append(',');
}
s.append(array[i]);
}
}
else if (o instanceof long[]) {
long[] array = (long[]) o;
for (int i = 0; i < array.length; i++) {
if (i > 0) {
s.append(',');
}
s.append(array[i]);
}
}
else if (o instanceof short[]) {
short[] array = (short[]) o;
for (int i = 0; i < array.length; i++) {
if (i > 0) {
s.append(',');
}
s.append(array[i]);
}
}
else if (o instanceof double[]) {
double[] array = (double[]) o;
for (int i = 0; i < array.length; i++) {
if (i > 0) {
s.append(',');
}
s.append(array[i]);
}
}
else if (o instanceof float[]) {
float[] array = (float[]) o;
for (int i = 0; i < array.length; i++) {
if (i > 0) {
s.append(',');
}
s.append(array[i]);
}
}
else if (o instanceof byte[]) {
byte[] array = (byte[]) o;
for (int i = 0; i < array.length; i++) {
if (i > 0) {
s.append(',');
}
s.append(array[i]);
}
}
else {
throw new IllegalStateException("Not supported " + o.getClass());
}
s.append(']');
assertThat(s.toString()).isEqualTo(expectedToString);
return s.toString();
Object array = e.getValue();
assertThat(array).isNotNull();
assertThat(array.getClass().isArray()).isTrue();
assertThat(ObjectUtils.nullSafeToString(array)).isEqualTo(expectedToString);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -98,9 +98,18 @@ public class DataClassRowMapper<T> extends BeanPropertyRowMapper<T> {
if (this.constructorParameterNames != null && this.constructorParameterTypes != null) {
args = new Object[this.constructorParameterNames.length];
for (int i = 0; i < args.length; i++) {
String name = underscoreName(this.constructorParameterNames[i]);
String name = this.constructorParameterNames[i];
int index;
try {
// Try direct name match first
index = rs.findColumn(lowerCaseName(name));
}
catch (SQLException ex) {
// Try underscored name match instead
index = rs.findColumn(underscoreName(name));
}
TypeDescriptor td = this.constructorParameterTypes[i];
Object value = getColumnValue(rs, rs.findColumn(name), td.getType());
Object value = getColumnValue(rs, index, td.getType());
args[i] = tc.convertIfNecessary(value, td.getType(), td);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,9 +50,9 @@ import org.springframework.util.function.SupplierUtils;
* by default. This factory loads a "sql-error-codes.xml" file from the class path,
* defining error code mappings for database names from database meta-data.
* <li>Fallback to a fallback translator. {@link SQLStateSQLExceptionTranslator} is the
* default fallback translator, analyzing the exception's SQL state only. On Java 6
* which introduces its own {@code SQLException} subclass hierarchy, we will
* use {@link SQLExceptionSubclassTranslator} by default, which in turns falls back
* default fallback translator, analyzing the exception's SQL state only. Since Java 6
* which introduces its own {@code SQLException} subclass hierarchy, we use
* {@link SQLExceptionSubclassTranslator} by default, which in turns falls back
* to Spring's own SQL state translation when not encountering specific subclasses.
* </ul>
*
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -53,7 +53,7 @@ import org.springframework.lang.Nullable;
* @author Juergen Hoeller
* @since 2.5
* @see java.sql.SQLTransientException
* @see java.sql.SQLTransientException
* @see java.sql.SQLNonTransientException
* @see java.sql.SQLRecoverableException
*/
public class SQLExceptionSubclassTranslator extends AbstractFallbackSQLExceptionTranslator {
@@ -20,6 +20,7 @@ import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Statement;
import java.sql.Timestamp;
@@ -63,7 +64,7 @@ public abstract class AbstractRowMapperTests {
protected void verifyPerson(ConcretePerson person) {
assertThat(person.getName()).isEqualTo("Bubba");
assertThat(person.getAge()).isEqualTo(22L);
assertThat(person.getBirth_date()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
assertThat(person.getBirthDate()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
assertThat(person.getBalance()).isEqualTo(new BigDecimal("1234.56"));
verifyPersonViaBeanWrapper(person);
}
@@ -94,7 +95,14 @@ public abstract class AbstractRowMapperTests {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(person);
assertThat(bw.getPropertyValue("name")).isEqualTo("Bubba");
assertThat(bw.getPropertyValue("age")).isEqualTo(22L);
assertThat((Date) bw.getPropertyValue("birth_date")).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
Date birthDate;
if (bw.isReadableProperty("birth_date")) {
birthDate = (Date) bw.getPropertyValue("birth_date");
}
else {
birthDate = (Date) bw.getPropertyValue("birthDate");
}
assertThat(birthDate).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
assertThat(bw.getPropertyValue("balance")).isEqualTo(new BigDecimal("1234.56"));
}
@@ -107,7 +115,7 @@ public abstract class AbstractRowMapperTests {
}
protected enum MockType {ONE, TWO, THREE}
protected enum MockType {ONE, TWO, THREE, FOUR}
protected static class Mock {
@@ -152,13 +160,19 @@ public abstract class AbstractRowMapperTests {
given(resultSetMetaData.getColumnLabel(1)).willReturn(
type == MockType.THREE ? "Last Name" : "name");
given(resultSetMetaData.getColumnLabel(2)).willReturn("age");
given(resultSetMetaData.getColumnLabel(3)).willReturn("birth_date");
given(resultSetMetaData.getColumnLabel(3)).willReturn(type == MockType.FOUR ? "birthdate" :"birth_date");
given(resultSetMetaData.getColumnLabel(4)).willReturn("balance");
given(resultSetMetaData.getColumnLabel(5)).willReturn("e_mail");
given(resultSet.findColumn("name")).willReturn(1);
given(resultSet.findColumn("age")).willReturn(2);
given(resultSet.findColumn("birth_date")).willReturn(3);
if (type == MockType.FOUR) {
given(resultSet.findColumn("birthdate")).willReturn(3);
}
else {
given(resultSet.findColumn("birthdate")).willThrow(new SQLException());
given(resultSet.findColumn("birth_date")).willReturn(3);
}
given(resultSet.findColumn("balance")).willReturn(4);
given(resultSet.findColumn("e_mail")).willReturn(5);
@@ -140,6 +140,17 @@ class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
mock.verifyClosed();
}
@Test
void queryWithDirectNameMatchOnBirthDate() throws Exception {
Mock mock = new Mock(MockType.FOUR);
List<ConcretePerson> result = mock.getJdbcTemplate().query(
"select name, age, birthdate, balance from people",
new BeanPropertyRowMapper<>(ConcretePerson.class));
assertThat(result).hasSize(1);
verifyPerson(result.get(0));
mock.verifyClosed();
}
@Test
void queryWithUnderscoreInColumnNameAndPersonWithMultipleAdjacentUppercaseLettersInPropertyName() throws Exception {
Mock mock = new Mock();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -57,7 +57,7 @@ public class DataClassRowMapperTests extends AbstractRowMapperTests {
ConstructorPersonWithGenerics person = result.get(0);
assertThat(person.name()).isEqualTo("Bubba");
assertThat(person.age()).isEqualTo(22L);
assertThat(person.birth_date()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
assertThat(person.balance()).isEqualTo(Collections.singletonList(new BigDecimal("1234.56")));
mock.verifyClosed();
@@ -65,15 +65,15 @@ public class DataClassRowMapperTests extends AbstractRowMapperTests {
@Test
public void testStaticQueryWithDataClassAndSetters() throws Exception {
Mock mock = new Mock();
Mock mock = new Mock(MockType.FOUR);
List<ConstructorPersonWithSetters> result = mock.getJdbcTemplate().query(
"select name, age, birth_date, balance from people",
"select name, age, birthdate, balance from people",
new DataClassRowMapper<>(ConstructorPersonWithSetters.class));
assertThat(result.size()).isEqualTo(1);
ConstructorPersonWithSetters person = result.get(0);
assertThat(person.name()).isEqualTo("BUBBA");
assertThat(person.age()).isEqualTo(22L);
assertThat(person.birth_date()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
assertThat(person.balance()).isEqualTo(new BigDecimal("1234.56"));
mock.verifyClosed();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ public abstract class AbstractPerson {
private long age;
private Date birth_date;
private Date birthDate;
public String getName() {
@@ -46,12 +46,12 @@ public abstract class AbstractPerson {
this.age = age;
}
public Date getBirth_date() {
return birth_date;
public Date getBirthDate() {
return birthDate;
}
public void setBirth_date(Date birth_date) {
this.birth_date = birth_date;
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ public class ConstructorPersonWithGenerics {
private final long age;
private final Date birth_date;
private final Date birthDate;
private final List<BigDecimal> balance;
@@ -37,7 +37,7 @@ public class ConstructorPersonWithGenerics {
public ConstructorPersonWithGenerics(String name, long age, Date birth_date, List<BigDecimal> balance) {
this.name = name;
this.age = age;
this.birth_date = birth_date;
this.birthDate = birth_date;
this.balance = balance;
}
@@ -50,8 +50,8 @@ public class ConstructorPersonWithGenerics {
return this.age;
}
public Date birth_date() {
return this.birth_date;
public Date birthDate() {
return this.birthDate;
}
public List<BigDecimal> balance() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,15 +28,15 @@ public class ConstructorPersonWithSetters {
private long age;
private Date birth_date;
private Date birthDate;
private BigDecimal balance;
public ConstructorPersonWithSetters(String name, long age, Date birth_date, BigDecimal balance) {
public ConstructorPersonWithSetters(String name, long age, Date birthDate, BigDecimal balance) {
this.name = name.toUpperCase();
this.age = age;
this.birth_date = birth_date;
this.birthDate = birthDate;
this.balance = balance;
}
@@ -49,8 +49,8 @@ public class ConstructorPersonWithSetters {
this.age = age;
}
public void setBirth_date(Date birth_date) {
this.birth_date = birth_date;
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
public void setBalance(BigDecimal balance) {
@@ -65,8 +65,8 @@ public class ConstructorPersonWithSetters {
return this.age;
}
public Date birth_date() {
return this.birth_date;
public Date birthDate() {
return this.birthDate;
}
public BigDecimal balance() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -60,8 +60,8 @@ public class SpacePerson {
return balance;
}
public void setBalance(BigDecimal balanace) {
this.balance = balanace;
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ import java.sql.SQLTransactionRollbackException;
import java.sql.SQLTransientConnectionException;
/**
* Class to generate Java 6 SQLException subclasses for testing purposes.
* Class to generate {@link SQLException} subclasses for testing purposes.
*
* @author Thomas Risberg
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -420,21 +420,21 @@ public class HandlerMethod {
private class ReturnValueMethodParameter extends HandlerMethodParameter {
@Nullable
private final Object returnValue;
private final Class<?> returnValueType;
public ReturnValueMethodParameter(@Nullable Object returnValue) {
super(-1);
this.returnValue = returnValue;
this.returnValueType = (returnValue != null ? returnValue.getClass() : null);
}
protected ReturnValueMethodParameter(ReturnValueMethodParameter original) {
super(original);
this.returnValue = original.returnValue;
this.returnValueType = original.returnValueType;
}
@Override
public Class<?> getParameterType() {
return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType());
return (this.returnValueType != null ? this.returnValueType : super.getParameterType());
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -306,6 +306,12 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
else if (SimpMessageType.CONNECT.equals(messageType)) {
logMessage(message);
if (sessionId != null) {
if (this.sessions.get(sessionId) != null) {
if (logger.isWarnEnabled()) {
logger.warn("Ignoring CONNECT in session " + sessionId + ". Already connected.");
}
return;
}
long[] heartbeatIn = SimpMessageHeaderAccessor.getHeartbeat(headers);
long[] heartbeatOut = getHeartbeatValue();
Principal user = SimpMessageHeaderAccessor.getUser(headers);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -552,6 +552,12 @@ public class StompBrokerRelayMessageHandler extends AbstractBrokerMessageHandler
}
if (StompCommand.CONNECT.equals(command) || StompCommand.STOMP.equals(command)) {
if (this.connectionHandlers.get(sessionId) != null) {
if (logger.isWarnEnabled()) {
logger.warn("Ignoring CONNECT in session " + sessionId + ". Already connected.");
}
return;
}
if (logger.isDebugEnabled()) {
logger.debug(stompAccessor.getShortLogMessage(EMPTY_PAYLOAD));
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -260,6 +260,30 @@ class StompBrokerRelayMessageHandlerTests {
assertThat(captor.getValue()).isSameAs(message);
}
@Test
void alreadyConnected() {
this.brokerRelay.start();
Message<byte[]> connect = connectMessage("sess1", "joe");
this.brokerRelay.handleMessage(connect);
assertThat(this.tcpClient.getSentMessages().size()).isEqualTo(2);
StompHeaderAccessor headers1 = this.tcpClient.getSentHeaders(0);
assertThat(headers1.getCommand()).isEqualTo(StompCommand.CONNECT);
assertThat(headers1.getSessionId()).isEqualTo(StompBrokerRelayMessageHandler.SYSTEM_SESSION_ID);
StompHeaderAccessor headers2 = this.tcpClient.getSentHeaders(1);
assertThat(headers2.getCommand()).isEqualTo(StompCommand.CONNECT);
assertThat(headers2.getSessionId()).isEqualTo("sess1");
this.brokerRelay.handleMessage(connect);
assertThat(this.tcpClient.getSentMessages().size()).isEqualTo(2);
assertThat(this.outboundChannel.getMessages()).isEmpty();
}
private Message<byte[]> connectMessage(String sessionId, String user) {
StompHeaderAccessor headers = StompHeaderAccessor.create(StompCommand.CONNECT);
headers.setSessionId(sessionId);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,13 +32,17 @@ import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.mock.web.MockPart;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;
import org.springframework.ui.Model;
import org.springframework.util.StreamUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@@ -56,19 +60,24 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal
/**
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @author Jaebin Joo
*/
public class MultipartControllerTests {
@Test
public void multipartRequestWithSingleFile() throws Exception {
@ParameterizedTest
@ValueSource(strings = {"/multipartfile", "/part"})
public void multipartRequestWithSingleFileOrPart(String url) throws Exception {
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
MockMultipartFile filePart = new MockMultipartFile("file", "orig", null, fileContent);
byte[] json = "{\"name\":\"yeeeah\"}".getBytes(StandardCharsets.UTF_8);
MockMultipartFile jsonPart = new MockMultipartFile("json", "json", "application/json", json);
MockMultipartHttpServletRequestBuilder requestBuilder = (url.endsWith("file") ?
multipart(url).file(new MockMultipartFile("file", "orig", null, fileContent)) :
multipart(url).part(new MockPart("part", "orig", fileContent)));
standaloneSetup(new MultipartController()).build()
.perform(multipart("/multipartfile").file(filePart).file(jsonPart))
.perform(requestBuilder.file(jsonPart))
.andExpect(status().isFound())
.andExpect(model().attribute("fileContent", fileContent))
.andExpect(model().attribute("jsonContent", Collections.singletonMap("name", "yeeeah")));
@@ -225,19 +234,14 @@ public class MultipartControllerTests {
}
@Test
public void multipartRequestWithServletParts() throws Exception {
public void multipartRequestWithDataBindingToFile() throws Exception {
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
MockPart filePart = new MockPart("file", "orig", fileContent);
byte[] json = "{\"name\":\"yeeeah\"}".getBytes(StandardCharsets.UTF_8);
MockPart jsonPart = new MockPart("json", json);
jsonPart.getHeaders().setContentType(MediaType.APPLICATION_JSON);
standaloneSetup(new MultipartController()).build()
.perform(multipart("/multipartfile").part(filePart).part(jsonPart))
.perform(multipart("/multipartfilebinding").part(filePart))
.andExpect(status().isFound())
.andExpect(model().attribute("fileContent", fileContent))
.andExpect(model().attribute("jsonContent", Collections.singletonMap("name", "yeeeah")));
.andExpect(model().attribute("fileContent", fileContent));
}
@Test // SPR-13317
@@ -343,10 +347,13 @@ public class MultipartControllerTests {
}
@RequestMapping(value = "/part", method = RequestMethod.POST)
public String processPart(@RequestParam Part part,
public String processPart(@RequestPart Part part,
@RequestPart Map<String, String> json, Model model) throws IOException {
model.addAttribute("fileContent", part.getInputStream());
if (part != null) {
byte[] content = StreamUtils.copyToByteArray(part.getInputStream());
model.addAttribute("fileContent", content);
}
model.addAttribute("jsonContent", json);
return "redirect:/index";
@@ -357,6 +364,33 @@ public class MultipartControllerTests {
model.addAttribute("json", json);
return "redirect:/index";
}
@RequestMapping(value = "/multipartfilebinding", method = RequestMethod.POST)
public String processMultipartFileBean(
MultipartFileBean multipartFileBean, Model model, BindingResult bindingResult) throws IOException {
if (!bindingResult.hasErrors()) {
MultipartFile file = multipartFileBean.getFile();
if (file != null) {
model.addAttribute("fileContent", file.getBytes());
}
}
return "redirect:/index";
}
}
private static class MultipartFileBean {
private MultipartFile file;
public MultipartFile getFile() {
return file;
}
@SuppressWarnings("unused")
public void setFile(MultipartFile file) {
this.file = file;
}
}
@@ -95,6 +95,19 @@ public class MediaType extends MimeType implements Serializable {
*/
public static final String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded";
/**
* Public constant media type for {@code application/graphql+json}.
* @since 5.3.19
* @see <a href="https://github.com/graphql/graphql-over-http">GraphQL over HTTP spec</a>
*/
public static final MediaType APPLICATION_GRAPHQL;
/**
* A String equivalent of {@link MediaType#APPLICATION_GRAPHQL}.
* @since 5.3.19
*/
public static final String APPLICATION_GRAPHQL_VALUE = "application/graphql+json";
/**
* Public constant media type for {@code application/json}.
*/
@@ -396,6 +409,7 @@ public class MediaType extends MimeType implements Serializable {
APPLICATION_ATOM_XML = new MediaType("application", "atom+xml");
APPLICATION_CBOR = new MediaType("application", "cbor");
APPLICATION_FORM_URLENCODED = new MediaType("application", "x-www-form-urlencoded");
APPLICATION_GRAPHQL = new MediaType("application", "graphql+json");
APPLICATION_JSON = new MediaType("application", "json");
APPLICATION_JSON_UTF8 = new MediaType("application", "json", StandardCharsets.UTF_8);
APPLICATION_NDJSON = new MediaType("application", "x-ndjson");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -100,13 +100,12 @@ abstract class DefaultParts {
/**
* Abstract base class.
* Abstract base class for {@link Part} implementations.
*/
private static abstract class AbstractPart implements Part {
private final HttpHeaders headers;
protected AbstractPart(HttpHeaders headers) {
Assert.notNull(headers, "HttpHeaders is required");
this.headers = headers;
@@ -119,7 +118,6 @@ abstract class DefaultParts {
return name;
}
@Override
public HttpHeaders headers() {
return this.headers;
@@ -172,7 +170,6 @@ abstract class DefaultParts {
protected final Content content;
public DefaultPart(HttpHeaders headers, Content content) {
super(headers);
this.content = content;
@@ -198,7 +195,6 @@ abstract class DefaultParts {
return "DefaultPart";
}
}
}
@@ -213,7 +209,7 @@ abstract class DefaultParts {
@Override
public String filename() {
String filename = this.headers().getContentDisposition().getFilename();
String filename = headers().getContentDisposition().getFilename();
Assert.state(filename != null, "No filename found");
return filename;
}
@@ -235,7 +231,6 @@ abstract class DefaultParts {
return "DefaultFilePart{(" + filename + ")}";
}
}
}
@@ -249,9 +244,9 @@ abstract class DefaultParts {
Mono<Void> transferTo(Path dest);
Mono<Void> delete();
}
/**
* {@code Content} implementation based on a flux of data buffers.
*/
@@ -259,12 +254,10 @@ abstract class DefaultParts {
private final Flux<DataBuffer> content;
public FluxContent(Flux<DataBuffer> content) {
this.content = content;
}
@Override
public Flux<DataBuffer> content() {
return this.content;
@@ -279,7 +272,6 @@ abstract class DefaultParts {
public Mono<Void> delete() {
return Mono.empty();
}
}
@@ -292,13 +284,11 @@ abstract class DefaultParts {
private final Scheduler scheduler;
public FileContent(Path file, Scheduler scheduler) {
this.file = file;
this.scheduler = scheduler;
}
@Override
public Flux<DataBuffer> content() {
return DataBufferUtils.readByteChannel(
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -105,7 +105,7 @@ final class MultipartParser extends BaseSubscriber<DataBuffer> {
@Override
public Context currentContext() {
return this.sink.currentContext();
return Context.of(this.sink.contextView());
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -116,7 +116,7 @@ final class PartGenerator extends BaseSubscriber<MultipartParser.Token> {
@Override
public Context currentContext() {
return this.sink.currentContext();
return Context.of(this.sink.contextView());
}
@Override
@@ -197,7 +197,7 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
@Override
public InetSocketAddress getLocalAddress() {
return new InetSocketAddress(this.servletRequest.getLocalName(), this.servletRequest.getLocalPort());
return new InetSocketAddress(this.servletRequest.getLocalAddr(), this.servletRequest.getLocalPort());
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,6 +26,8 @@ import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.core.publisher.Operators;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.core.log.LogDelegateFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -56,6 +58,8 @@ public abstract class AbstractListenerReadPublisher<T> implements Publisher<T> {
*/
protected static Log rsReadLogger = LogDelegateFactory.getHiddenLog(AbstractListenerReadPublisher.class);
final static DataBuffer EMPTY_BUFFER = DefaultDataBufferFactory.sharedInstance.allocateBuffer(0);
private final AtomicReference<State> state = new AtomicReference<>(State.UNSUBSCRIBED);
@@ -180,7 +184,7 @@ public abstract class AbstractListenerReadPublisher<T> implements Publisher<T> {
/**
* Read and publish data one at a time until there is no more data, no more
* demand, or perhaps we completed in the mean time.
* demand, or perhaps we completed meanwhile.
* @return {@code true} if there is more demand; {@code false} if there is
* no more demand or we have completed.
*/
@@ -188,7 +192,12 @@ public abstract class AbstractListenerReadPublisher<T> implements Publisher<T> {
long r;
while ((r = this.demand) > 0 && (this.state.get() != State.COMPLETED)) {
T data = read();
if (data != null) {
if (data == EMPTY_BUFFER) {
if (rsReadLogger.isTraceEnabled()) {
rsReadLogger.trace(getLogPrefix() + "0 bytes read, trying again");
}
}
else if (data != null) {
if (r != Long.MAX_VALUE) {
DEMAND_FIELD_UPDATER.addAndGet(this, -1L);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -198,7 +198,7 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
@Nullable
protected SslInfo initSslInfo() {
X509Certificate[] certificates = getX509Certificates();
return certificates != null ? new DefaultSslInfo(getSslSessionId(), certificates) : null;
return (certificates != null ? new DefaultSslInfo(getSslSessionId(), certificates) : null);
}
@Nullable
@@ -208,8 +208,7 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
@Nullable
private X509Certificate[] getX509Certificates() {
String name = "javax.servlet.request.X509Certificate";
return (X509Certificate[]) this.request.getAttribute(name);
return (X509Certificate[]) this.request.getAttribute("javax.servlet.request.X509Certificate");
}
@Override
@@ -236,10 +235,10 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
/**
* Read from the request body InputStream and return a DataBuffer.
* Invoked only when {@link ServletInputStream#isReady()} returns "true".
* @return a DataBuffer with data read, or {@link #EOF_BUFFER} if the input
* stream returned -1, or null if 0 bytes were read.
* @return a DataBuffer with data read, or
* {@link AbstractListenerReadPublisher#EMPTY_BUFFER} if 0 bytes were read,
* or {@link #EOF_BUFFER} if the input stream returned -1.
*/
@Nullable
DataBuffer readFromInputStream() throws IOException {
int read = this.request.getInputStream().read(this.buffer);
logBytesRead(read);
@@ -254,7 +253,7 @@ class ServletServerHttpRequest extends AbstractServerHttpRequest {
return EOF_BUFFER;
}
return null;
return AbstractListenerReadPublisher.EMPTY_BUFFER;
}
protected final void logBytesRead(int read) {
@@ -153,7 +153,7 @@ public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter {
return EOF_BUFFER;
}
else {
return null;
return AbstractListenerReadPublisher.EMPTY_BUFFER;
}
}
finally {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -88,7 +88,7 @@ public class UndertowHttpHandlerAdapter implements io.undertow.server.HttpHandle
}
private class HandlerResultSubscriber implements Subscriber<Void> {
private static class HandlerResultSubscriber implements Subscriber<Void> {
private final HttpServerExchange exchange;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,6 +33,15 @@ import org.springframework.web.util.WebUtils;
* Special {@link org.springframework.validation.DataBinder} to perform data binding
* from servlet request parameters to JavaBeans, including support for multipart files.
*
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
* parts of the object graph that are not meant to be accessed or modified by
* external clients. Therefore the design and use of data binding should be considered
* carefully with regard to security. For more details, please refer to the dedicated
* sections on data binding for
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
* in the reference manual.
*
* <p>See the DataBinder/WebDataBinder superclasses for customization options,
* which include specifying allowed/required fields, and registering custom
* property editors.
@@ -95,11 +104,13 @@ public class ServletRequestDataBinder extends WebDataBinder {
* HTTP parameters: i.e. "uploadedFile" to an "uploadedFile" bean property,
* invoking a "setUploadedFile" setter method.
* <p>The type of the target property for a multipart file can be MultipartFile,
* byte[], or String. The latter two receive the contents of the uploaded file;
* all metadata like original file name, content type, etc are lost in those cases.
* byte[], or String. Servlet Part binding is also supported when the
* request has not been parsed to MultipartRequest via MultipartResolver.
* @param request the request with parameters to bind (can be multipart)
* @see org.springframework.web.multipart.MultipartHttpServletRequest
* @see org.springframework.web.multipart.MultipartRequest
* @see org.springframework.web.multipart.MultipartFile
* @see jakarta.servlet.http.Part
* @see #bind(org.springframework.beans.PropertyValues)
*/
public void bind(ServletRequest request) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,6 +34,15 @@ import org.springframework.web.multipart.MultipartFile;
* the Servlet API; serves as base class for more specific DataBinder variants,
* such as {@link org.springframework.web.bind.ServletRequestDataBinder}.
*
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
* parts of the object graph that are not meant to be accessed or modified by
* external clients. Therefore the design and use of data binding should be considered
* carefully with regard to security. For more details, please refer to the dedicated
* sections on data binding for
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
* in the reference manual.
*
* <p>Includes support for field markers which address a common problem with
* HTML checkboxes and select options: detecting that a field was part of
* the form, but did not generate a request parameter because it was empty.
@@ -100,6 +100,7 @@ import java.lang.annotation.Target;
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 3.0
* @see ControllerAdvice
* @see org.springframework.web.context.request.WebRequest
*/
@Target(ElementType.METHOD)
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,15 +23,24 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation that identifies methods which initialize the
* Annotation that identifies methods that initialize the
* {@link org.springframework.web.bind.WebDataBinder} which
* will be used for populating command and form object arguments
* of annotated handler methods.
*
* <p>Such init-binder methods support all arguments that {@link RequestMapping}
* supports, except for command/form objects and corresponding validation result
* objects. Init-binder methods must not have a return value; they are usually
* declared as {@code void}.
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
* parts of the object graph that are not meant to be accessed or modified by
* external clients. Therefore the design and use of data binding should be considered
* carefully with regard to security. For more details, please refer to the dedicated
* sections on data binding for
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
* in the reference manual.
*
* <p>{@code @InitBinder} methods support all arguments that
* {@link RequestMapping @RequestMapping} methods support, except for command/form
* objects and corresponding validation result objects. {@code @InitBinder} methods
* must not have a return value; they are usually declared as {@code void}.
*
* <p>Typical arguments are {@link org.springframework.web.bind.WebDataBinder}
* in combination with {@link org.springframework.web.context.request.WebRequest}
@@ -39,6 +48,7 @@ import java.lang.annotation.Target;
*
* @author Juergen Hoeller
* @since 2.5
* @see ControllerAdvice
* @see org.springframework.web.bind.WebDataBinder
* @see org.springframework.web.context.request.WebRequest
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,18 +31,27 @@ import org.springframework.ui.Model;
* for controller classes with {@link RequestMapping @RequestMapping}
* methods.
*
* <p>Can be used to expose command objects to a web view, using
* specific attribute names, through annotating corresponding
* parameters of an {@link RequestMapping @RequestMapping} method.
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
* parts of the object graph that are not meant to be accessed or modified by
* external clients. Therefore the design and use of data binding should be considered
* carefully with regard to security. For more details, please refer to the dedicated
* sections on data binding for
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
* in the reference manual.
*
* <p>Can also be used to expose reference data to a web view
* through annotating accessor methods in a controller class with
* <p>{@code @ModelAttribute} can be used to expose command objects to a web view,
* using specific attribute names, by annotating corresponding parameters of an
* {@link RequestMapping @RequestMapping} method.
*
* <p>{@code @ModelAttribute} can also be used to expose reference data to a web
* view by annotating accessor methods in a controller class with
* {@link RequestMapping @RequestMapping} methods. Such accessor
* methods are allowed to have any arguments that
* {@link RequestMapping @RequestMapping} methods support, returning
* the model attribute value to expose.
*
* <p>Note however that reference data and all other model content is
* <p>Note however that reference data and all other model content are
* not available to web views when request processing results in an
* {@code Exception} since the exception could be raised at any time
* making the content of the model unreliable. For this reason
@@ -52,6 +61,7 @@ import org.springframework.ui.Model;
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @since 2.5
* @see ControllerAdvice
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@@ -77,7 +87,7 @@ public @interface ModelAttribute {
String name() default "";
/**
* Allows declaring data binding disabled directly on an {@code @ModelAttribute}
* Allows data binding to be disabled directly on an {@code @ModelAttribute}
* method parameter or on the attribute returned from an {@code @ModelAttribute}
* method, both of which would prevent data binding for that attribute.
* <p>By default this is set to {@code true} in which case data binding applies.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,6 +36,15 @@ import org.springframework.web.server.ServerWebExchange;
* Specialized {@link org.springframework.validation.DataBinder} to perform data
* binding from URL query parameters or form data in the request data to Java objects.
*
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
* parts of the object graph that are not meant to be accessed or modified by
* external clients. Therefore the design and use of data binding should be considered
* carefully with regard to security. For more details, please refer to the dedicated
* sections on data binding for
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
* in the reference manual.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 5.0
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,6 +35,15 @@ import org.springframework.web.multipart.support.StandardServletPartUtils;
* Special {@link org.springframework.validation.DataBinder} to perform data binding
* from web request parameters to JavaBeans, including support for multipart files.
*
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
* parts of the object graph that are not meant to be accessed or modified by
* external clients. Therefore the design and use of data binding should be considered
* carefully with regard to security. For more details, please refer to the dedicated
* sections on data binding for
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
* in the reference manual.
*
* <p>See the DataBinder/WebDataBinder superclasses for customization options,
* which include specifying allowed/required fields, and registering custom
* property editors.
@@ -98,9 +107,9 @@ public class WebRequestDataBinder extends WebDataBinder {
* <p>Multipart files are bound via their parameter name, just like normal
* HTTP parameters: i.e. "uploadedFile" to an "uploadedFile" bean property,
* invoking a "setUploadedFile" setter method.
* <p>The type of the target property for a multipart file can be Part, MultipartFile,
* byte[], or String. The latter two receive the contents of the uploaded file;
* all metadata like original file name, content type, etc are lost in those cases.
* <p>The type of the target property for a multipart file can be MultipartFile,
* byte[], or String. Servlet Part binding is also supported when the
* request has not been parsed to MultipartRequest via MultipartResolver.
* @param request the request with parameters to bind (can be multipart)
* @see org.springframework.web.multipart.MultipartRequest
* @see org.springframework.web.multipart.MultipartFile
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -582,21 +582,21 @@ public class HandlerMethod {
private class ReturnValueMethodParameter extends HandlerMethodParameter {
@Nullable
private final Object returnValue;
private final Class<?> returnValueType;
public ReturnValueMethodParameter(@Nullable Object returnValue) {
super(-1);
this.returnValue = returnValue;
this.returnValueType = (returnValue != null ? returnValue.getClass() : null);
}
protected ReturnValueMethodParameter(ReturnValueMethodParameter original) {
super(original);
this.returnValue = original.returnValue;
this.returnValueType = original.returnValueType;
}
@Override
public Class<?> getParameterType() {
return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType());
return (this.returnValueType != null ? this.returnValueType : super.getParameterType());
}
@Override
@@ -1,6 +1,5 @@
/**
* Miscellaneous web utility classes, such as HTML escaping,
* Log4j initialization, and cookie handling.
* Miscellaneous web utility classes, such as HTML escaping and cookie handling.
*/
@NonNullApi
@NonNullFields
@@ -103,7 +103,7 @@ public class Jackson2JsonEncoderTests extends AbstractEncoderTests<Jackson2JsonE
);
}
@Test // SPR-15866
@Test // SPR-15866
public void canEncodeWithCustomMimeType() {
MimeType textJavascript = new MimeType("text", "javascript", StandardCharsets.UTF_8);
Jackson2JsonEncoder encoder = new Jackson2JsonEncoder(new ObjectMapper(), textJavascript);
@@ -231,9 +231,8 @@ public class Jackson2JsonEncoderTests extends AbstractEncoderTests<Jackson2JsonE
);
}
@Test // gh-28045
@Test // gh-28045
public void jacksonValueUnwrappedBeforeObjectMapperSelection() {
JacksonViewBean bean = new JacksonViewBean();
bean.setWithView1("with");
bean.setWithView2("with");
@@ -248,13 +247,15 @@ public class Jackson2JsonEncoderTests extends AbstractEncoderTests<Jackson2JsonE
ObjectMapper mapper = new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true);
this.encoder.registerObjectMappersForType(JacksonViewBean.class, map -> map.put(halMediaType, mapper));
String ls = System.lineSeparator(); // output below is different between Unix and Windows
testEncode(Mono.just(jacksonValue), type, halMediaType, Collections.emptyMap(), step -> step
.consumeNextWith(expectString("{\n \"withView1\" : \"with\"\n}").andThen(DataBufferUtils::release))
.consumeNextWith(expectString("{" + ls + " \"withView1\" : \"with\"" + ls + "}")
.andThen(DataBufferUtils::release))
.verifyComplete()
);
}
@Test // gh-22771
@Test // gh-22771
public void encodeWithFlushAfterWriteOff() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FLUSH_AFTER_WRITE_VALUE, false);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -116,7 +116,7 @@ public class InitBinderDataBinderFactoryTests {
WebDataBinder dataBinder = factory.createBinder(this.webRequest, null, "foo");
assertThat(dataBinder.getDisallowedFields()).isNotNull();
assertThat(dataBinder.getDisallowedFields()[0]).isEqualTo("requestParam-22");
assertThat(dataBinder.getDisallowedFields()[0]).isEqualToIgnoringCase("requestParam-22");
}
private WebDataBinderFactory createFactory(String methodName, Class<?>... parameterTypes)
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -269,7 +269,7 @@ public class ModelAttributeMethodProcessorTests {
assertThat(this.container.getModel().get("testBean")).isSameAs(testBean);
}
@Test // gh-25182
@Test // gh-25182
public void resolveConstructorListArgumentFromCommaSeparatedRequestParameter() throws Exception {
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
mockRequest.addParameter("listOfStrings", "1,2");
@@ -279,7 +279,6 @@ public class ModelAttributeMethodProcessorTests {
given(factory.createBinder(any(), any(), eq("testBeanWithConstructorArgs")))
.willAnswer(invocation -> {
WebRequestDataBinder binder = new WebRequestDataBinder(invocation.getArgument(1));
// Add conversion service which will convert "1,2" to a list
binder.setConversionService(new DefaultFormattingConversionService());
return binder;
@@ -309,7 +308,6 @@ public class ModelAttributeMethodProcessorTests {
private boolean validateInvoked;
public StubRequestDataBinder(Object target, String objectName) {
super(target, objectName);
}
@@ -345,7 +343,7 @@ public class ModelAttributeMethodProcessorTests {
}
@SessionAttributes(types=TestBean.class)
@SessionAttributes(types = TestBean.class)
private static class ModelAttributeHandler {
@SuppressWarnings("unused")
@@ -360,6 +358,7 @@ public class ModelAttributeMethodProcessorTests {
}
}
static class TestBeanWithConstructorArgs {
final List<String> listOfStrings;
@@ -367,15 +366,15 @@ public class ModelAttributeMethodProcessorTests {
public TestBeanWithConstructorArgs(List<String> listOfStrings) {
this.listOfStrings = listOfStrings;
}
}
@ModelAttribute("modelAttrName") @SuppressWarnings("unused")
@ModelAttribute("modelAttrName")
@SuppressWarnings("unused")
private String annotatedReturnValue() {
return null;
}
@SuppressWarnings("unused")
private TestBean notAnnotatedReturnValue() {
return null;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -236,7 +236,7 @@ public class VersionResourceResolver extends AbstractResourceResolver {
}
private class FileNameVersionedResource extends AbstractResource implements HttpResource {
private static class FileNameVersionedResource extends AbstractResource implements HttpResource {
private final Resource original;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import org.springframework.core.Conventions;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
@@ -33,10 +34,12 @@ import org.springframework.web.server.ServerWebExchange;
/**
* Resolve {@link Errors} or {@link BindingResult} method arguments.
* An {@code Errors} argument is expected to appear immediately after the
*
* <p>An {@code Errors} argument is expected to appear immediately after the
* model attribute in the method signature.
*
* @author Rossen Stoyanchev
* @author Sam Brannen
* @since 5.0
*/
public class ErrorsMethodArgumentResolver extends HandlerMethodArgumentResolverSupport {
@@ -78,7 +81,7 @@ public class ErrorsMethodArgumentResolver extends HandlerMethodArgumentResolverS
"Errors argument must be declared immediately after a model attribute argument");
int index = parameter.getParameterIndex() - 1;
MethodParameter attributeParam = MethodParameter.forExecutable(parameter.getExecutable(), index);
MethodParameter attributeParam = SynthesizingMethodParameter.forExecutable(parameter.getExecutable(), index);
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(attributeParam.getParameterType());
Assert.state(adapter == null, "An @ModelAttribute and an Errors/BindingResult argument " +
@@ -86,9 +89,9 @@ public class ErrorsMethodArgumentResolver extends HandlerMethodArgumentResolverS
"Either declare the @ModelAttribute without an async wrapper type or " +
"handle a WebExchangeBindException error signal through the async type.");
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
String name = (ann != null && StringUtils.hasText(ann.value()) ?
ann.value() : Conventions.getVariableNameForParameter(attributeParam));
ModelAttribute ann = attributeParam.getParameterAnnotation(ModelAttribute.class);
String name = (ann != null && StringUtils.hasText(ann.name()) ? ann.name() :
Conventions.getVariableNameForParameter(attributeParam));
Object errors = context.getModel().asMap().get(BindingResult.MODEL_KEY_PREFIX + name);
Assert.state(errors != null, () -> "An Errors/BindingResult argument is expected " +
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.web.reactive.result.method.annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
@@ -213,21 +214,30 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Application
InvocableHandlerMethod invocable = this.methodResolver.getExceptionHandlerMethod(exception, handlerMethod);
if (invocable != null) {
ArrayList<Throwable> exceptions = new ArrayList<>();
try {
if (logger.isDebugEnabled()) {
logger.debug(exchange.getLogPrefix() + "Using @ExceptionHandler " + invocable);
}
bindingContext.getModel().asMap().clear();
Throwable cause = exception.getCause();
if (cause != null) {
return invocable.invoke(exchange, bindingContext, exception, cause, handlerMethod);
}
else {
return invocable.invoke(exchange, bindingContext, exception, handlerMethod);
// Expose causes as provided arguments as well
Throwable exToExpose = exception;
while (exToExpose != null) {
exceptions.add(exToExpose);
Throwable cause = exToExpose.getCause();
exToExpose = (cause != exToExpose ? cause : null);
}
Object[] arguments = new Object[exceptions.size() + 1];
exceptions.toArray(arguments); // efficient arraycopy call in ArrayList
arguments[arguments.length - 1] = handlerMethod;
return invocable.invoke(exchange, bindingContext, arguments);
}
catch (Throwable invocationEx) {
if (logger.isWarnEnabled()) {
// Any other than the original exception (or a cause) is unintended here,
// probably an accident (e.g. failed assertion or the like).
if (!exceptions.contains(invocationEx) && logger.isWarnEnabled()) {
logger.warn(exchange.getLogPrefix() + "Failure in @ExceptionHandler " + invocable, invocationEx);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,7 +82,7 @@ public class UndertowRequestUpgradeStrategy implements RequestUpgradeStrategy {
}
private class DefaultCallback implements WebSocketConnectionCallback {
private static class DefaultCallback implements WebSocketConnectionCallback {
private final HandshakeInfo handshakeInfo;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,9 +82,10 @@ public class ControllerAdviceTests {
@Test
public void resolveExceptionWithAssertionErrorAsRootCause() throws Exception {
AssertionError cause = new AssertionError("argh");
FatalBeanException exception = new FatalBeanException("wrapped", cause);
testException(exception, cause.toString());
AssertionError rootCause = new AssertionError("argh");
FatalBeanException cause = new FatalBeanException("wrapped", rootCause);
Exception exception = new Exception(cause);
testException(exception, rootCause.toString());
}
private void testException(Throwable exception, String expected) throws Exception {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -70,7 +70,7 @@ class ErrorsMethodArgumentResolverTests {
}
@Test
void resolve() {
void resolveWithInferredModelAttributeName() {
BindingResult bindingResult = createBindingResult(new Foo(), "foo");
this.bindingContext.getModel().asMap().put(BindingResult.MODEL_KEY_PREFIX + "foo", bindingResult);
@@ -81,9 +81,32 @@ class ErrorsMethodArgumentResolverTests {
assertThat(actual).isSameAs(bindingResult);
}
private BindingResult createBindingResult(Foo target, String name) {
DataBinder binder = this.bindingContext.createDataBinder(this.exchange, target, name);
return binder.getBindingResult();
@Test
void resolveWithCustomModelAttributeNameConfiguredViaValueAttribute() {
BindingResult bindingResult = createBindingResult(new Foo(), "custom");
this.bindingContext.getModel().asMap().put(BindingResult.MODEL_KEY_PREFIX + "custom", bindingResult);
ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handleWithCustomModelAttributeNameViaValueAttribute").build();
MethodParameter parameter = testMethod.arg(Errors.class);
Object actual = this.resolver.resolveArgument(parameter, this.bindingContext, this.exchange)
.block(Duration.ofMillis(5000));
assertThat(actual).isSameAs(bindingResult);
}
@Test
void resolveWithCustomModelAttributeNameConfiguredViaNameAttribute() {
BindingResult bindingResult = createBindingResult(new Foo(), "custom");
this.bindingContext.getModel().asMap().put(BindingResult.MODEL_KEY_PREFIX + "custom", bindingResult);
ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handleWithCustomModelAttributeNameViaNameAttribute").build();
MethodParameter parameter = testMethod.arg(Errors.class);
Object actual = this.resolver.resolveArgument(parameter, this.bindingContext, this.exchange)
.block(Duration.ofMillis(5000));
assertThat(actual).isSameAs(bindingResult);
}
@Test
@@ -98,6 +121,20 @@ class ErrorsMethodArgumentResolverTests {
assertThat(actual).isSameAs(bindingResult);
}
@Test
void resolveWithMonoAndCustomModelAttributeNameConfiguredViaValueAttribute() {
BindingResult bindingResult = createBindingResult(new Foo(), "custom");
this.bindingContext.getModel().asMap().put(BindingResult.MODEL_KEY_PREFIX + "custom", Mono.just(bindingResult));
ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handleWithCustomModelAttributeNameViaValueAttribute").build();
MethodParameter parameter = testMethod.arg(Errors.class);
Object actual = this.resolver.resolveArgument(parameter, this.bindingContext, this.exchange)
.block(Duration.ofMillis(5000));
assertThat(actual).isSameAs(bindingResult);
}
@Test
void resolveWithMonoOnBindingResultAndModelAttribute() {
MethodParameter parameter = this.testMethod.arg(BindingResult.class);
@@ -118,6 +155,11 @@ class ErrorsMethodArgumentResolverTests {
"immediately after the @ModelAttribute argument");
}
private BindingResult createBindingResult(Foo target, String name) {
DataBinder binder = this.bindingContext.createDataBinder(this.exchange, target, name);
return binder.getBindingResult();
}
@SuppressWarnings("unused")
private static class Foo {
@@ -150,4 +192,16 @@ class ErrorsMethodArgumentResolverTests {
String string) {
}
@SuppressWarnings("unused")
void handleWithCustomModelAttributeNameViaValueAttribute(
@ModelAttribute("custom") Foo foo,
Errors errors) {
}
@SuppressWarnings("unused")
void handleWithCustomModelAttributeNameViaNameAttribute(
@ModelAttribute(name = "custom") Foo foo,
Errors errors) {
}
}

Some files were not shown because too many files have changed in this diff Show More