mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
152 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d9a9686b45 | |||
| efc335e198 | |||
| 1e57e572dd | |||
| 829000d511 | |||
| a315abfd32 | |||
| 36d133ce53 | |||
| 9417975f66 | |||
| ed9ec58caa | |||
| f48c3d9b67 | |||
| 6b705443a6 | |||
| c26087e848 | |||
| 7225238ad5 | |||
| ff43731347 | |||
| 29fb3f27be | |||
| 63db250ebe | |||
| b00c7075a5 | |||
| 3718c8d0e0 | |||
| ca8261cbf8 | |||
| 8de10e9199 | |||
| 7536b7f42a | |||
| bc71cc1a76 | |||
| 582559dd9e | |||
| d4c609f2f0 | |||
| 3524401bf1 | |||
| 86423c91f7 | |||
| 8807f94ad8 | |||
| da3ff29e88 | |||
| 9c6b1b645d | |||
| 0fc8bf654b | |||
| 729535d36c | |||
| 634c7710f9 | |||
| 2fbfd8a5db | |||
| 42c15be8d3 | |||
| 3baa44c5ef | |||
| 4b86a1a755 | |||
| df977a2fd2 | |||
| 99a1388bbd | |||
| 809813dd52 | |||
| d5e5dcb7e1 | |||
| defc2466b0 | |||
| a991c23db5 | |||
| b928ed8f37 | |||
| 5d70aaacef | |||
| 566ff54782 | |||
| 77b7e49fb2 | |||
| 1c6bab23ea | |||
| 3e502d4739 | |||
| 667256adf9 | |||
| 8791928f61 | |||
| c9147c4281 | |||
| d6f363c634 | |||
| 87c0e9b48a | |||
| 9a4164c4a8 | |||
| c36b80bd75 | |||
| bc90512309 | |||
| edd15646af | |||
| 53cafe728c | |||
| 58e9b187fe | |||
| 9b0c2cca95 | |||
| 26000ee29f | |||
| 0c2c66b38b | |||
| bd8e682c51 | |||
| f7c952cb3e | |||
| 6bc7e12bcd | |||
| ab94c7c016 | |||
| 8fb9796d1d | |||
| a264013d7a | |||
| cd80b6b4ac | |||
| 164b48e25f | |||
| eb7b206142 | |||
| 42061d27bd | |||
| 0d16c9100a | |||
| 7cdaaa22bd | |||
| f4c9f6b860 | |||
| 1721b0b8d7 | |||
| 836976d732 | |||
| 59818ceca9 | |||
| 08ce6baedf | |||
| c52526ad42 | |||
| 7a329eba5b | |||
| 91d542e406 | |||
| 6fde28be98 | |||
| 1e481cd14c | |||
| df4ba742cb | |||
| 996c86f448 | |||
| 0575e6637c | |||
| ad8775ef95 | |||
| 8589804012 | |||
| c649b74235 | |||
| bef5d59e43 | |||
| 99bd8115dd | |||
| 1a3ff83157 | |||
| c19b2851f1 | |||
| 3b4a3431d4 | |||
| c82a445094 | |||
| 95752ef1c9 | |||
| 0ff50d6d9e | |||
| 30cdeb23fe | |||
| ecf5113e0e | |||
| 80c4e6bb95 | |||
| 5e5d8e4a23 | |||
| d7e05aa48f | |||
| cf52246319 | |||
| c5284009a1 | |||
| 2d29fcd0bb | |||
| 5e56162394 | |||
| 45650f2b27 | |||
| 3e1b1638ef | |||
| e80a23d6ad | |||
| 8aeae49f40 | |||
| f22e2ac578 | |||
| 355aca7665 | |||
| ec5f99f981 | |||
| 03f1b65084 | |||
| 0de2833894 | |||
| cd9d562129 | |||
| 080ba5d448 | |||
| d96448fde9 | |||
| 84eed00260 | |||
| cee51b248a | |||
| 5a640bb635 | |||
| 5c1cbb769c | |||
| 4a7a2258f9 | |||
| bad8954e65 | |||
| e4566c11b8 | |||
| 9d8910d869 | |||
| aa7584d252 | |||
| e537844a09 | |||
| daa5465003 | |||
| 5a4a677fbd | |||
| 584dabbb39 | |||
| 4779323d33 | |||
| 67112b211a | |||
| 799885fd6d | |||
| 3c959d3515 | |||
| cfae40afb4 | |||
| 1b1ae33efe | |||
| d80b08084e | |||
| d1a7fed16c | |||
| 469eb8146e | |||
| 35225e5794 | |||
| a7413ea76b | |||
| c3c36ab498 | |||
| ea4fe7eaf7 | |||
| 3112dc5f81 | |||
| b07a6b3283 | |||
| 6b9d5698ce | |||
| 72a9d0ff4b | |||
| 872255a591 | |||
| 671270dae2 | |||
| 6ea39fd122 | |||
| dadef9f5bc |
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
!!! For Security Vulnerabilities, please go to https://pivotal.io/security !!!
|
||||
!!! For Security Vulnerabilities, please go to https://spring.io/security-policy !!!
|
||||
-->
|
||||
**Affects:** \<Spring Framework version>
|
||||
|
||||
@@ -14,4 +14,4 @@ Thanks for taking the time to create an issue. Please read the following:
|
||||
Issue or Pull Request? Create only one, not both. GitHub treats them as the same.
|
||||
If unsure, start with an issue, and if you submit a pull request later, the
|
||||
issue will be closed as superseded.
|
||||
-->
|
||||
-->
|
||||
|
||||
+28
-27
@@ -1,15 +1,16 @@
|
||||
plugins {
|
||||
id 'io.spring.dependency-management' version '1.0.9.RELEASE' apply false
|
||||
id 'io.spring.nohttp' version '0.0.5.RELEASE'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.4.21' apply false
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.4.30' apply false
|
||||
id 'org.jetbrains.dokka' version '0.10.1' apply false
|
||||
id 'org.asciidoctor.jvm.convert' version '3.1.0'
|
||||
id 'org.asciidoctor.jvm.pdf' version '3.1.0'
|
||||
id 'de.undercouch.download' version '4.1.1'
|
||||
id "io.freefair.aspectj" version '5.1.1' apply false
|
||||
id "com.github.ben-manes.versions" version '0.28.0'
|
||||
id "me.champeau.gradle.jmh" version "0.5.0" apply false
|
||||
id "org.jetbrains.kotlin.plugin.serialization" version "1.4.21" apply false
|
||||
id "com.github.johnrengelman.shadow" version "6.1.0" apply false
|
||||
id "me.champeau.gradle.jmh" version "0.5.2" apply false
|
||||
id "org.jetbrains.kotlin.plugin.serialization" version "1.4.30" apply false
|
||||
}
|
||||
|
||||
ext {
|
||||
@@ -25,15 +26,15 @@ configure(allprojects) { project ->
|
||||
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.0"
|
||||
mavenBom "io.netty:netty-bom:4.1.56.Final"
|
||||
mavenBom "io.projectreactor:reactor-bom:2020.0.3"
|
||||
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.1"
|
||||
mavenBom "io.netty:netty-bom:4.1.59.Final"
|
||||
mavenBom "io.projectreactor:reactor-bom:2020.0.4"
|
||||
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR8"
|
||||
mavenBom "io.rsocket:rsocket-bom:1.1.0"
|
||||
mavenBom "org.eclipse.jetty:jetty-bom:9.4.35.v20201120"
|
||||
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.4.21"
|
||||
mavenBom "org.eclipse.jetty:jetty-bom:9.4.36.v20210114"
|
||||
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.4.30"
|
||||
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.4.2"
|
||||
mavenBom "org.junit:junit-bom:5.7.0"
|
||||
mavenBom "org.junit:junit-bom:5.7.1"
|
||||
}
|
||||
dependencies {
|
||||
dependencySet(group: 'org.apache.logging.log4j', version: '2.14.0') {
|
||||
@@ -63,8 +64,8 @@ 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.20"
|
||||
dependency "io.reactivex.rxjava3:rxjava:3.0.9"
|
||||
dependency "io.reactivex.rxjava2:rxjava:2.2.21"
|
||||
dependency "io.reactivex.rxjava3:rxjava:3.0.10"
|
||||
dependency "io.projectreactor.tools:blockhound:1.0.4.RELEASE"
|
||||
|
||||
dependency "com.caucho:hessian:4.0.63"
|
||||
@@ -75,11 +76,11 @@ configure(allprojects) { project ->
|
||||
dependency "com.google.code.gson:gson:2.8.6"
|
||||
dependency "com.google.protobuf:protobuf-java-util:3.14.0"
|
||||
dependency "com.googlecode.protobuf-java-format:protobuf-java-format:1.4"
|
||||
dependency("com.thoughtworks.xstream:xstream:1.4.14") {
|
||||
dependency("com.thoughtworks.xstream:xstream:1.4.15") {
|
||||
exclude group: "xpp3", name: "xpp3_min"
|
||||
exclude group: "xmlpull", name: "xmlpull"
|
||||
}
|
||||
dependency "org.apache.johnzon:johnzon-jsonb:1.2.8"
|
||||
dependency "org.apache.johnzon:johnzon-jsonb:1.2.10"
|
||||
dependency("org.codehaus.jettison:jettison:1.3.8") {
|
||||
exclude group: "stax", name: "stax-api"
|
||||
}
|
||||
@@ -96,7 +97,7 @@ configure(allprojects) { project ->
|
||||
|
||||
dependency "com.h2database:h2:1.4.200"
|
||||
dependency "com.github.ben-manes.caffeine:caffeine:2.8.8"
|
||||
dependency "com.github.librepdf:openpdf:1.3.24"
|
||||
dependency "com.github.librepdf:openpdf:1.3.25"
|
||||
dependency "com.rometools:rome:1.15.0"
|
||||
dependency "commons-io:commons-io:2.5"
|
||||
dependency "io.vavr:vavr:0.10.3"
|
||||
@@ -123,23 +124,23 @@ configure(allprojects) { project ->
|
||||
dependency "net.sf.ehcache:ehcache:2.10.6"
|
||||
dependency "org.ehcache:jcache:1.0.1"
|
||||
dependency "org.ehcache:ehcache:3.4.0"
|
||||
dependency "org.hibernate:hibernate-core:5.4.27.Final"
|
||||
dependency "org.hibernate:hibernate-core:5.4.28.Final"
|
||||
dependency "org.hibernate:hibernate-validator:6.2.0.Final"
|
||||
dependency "org.webjars:webjars-locator-core:0.46"
|
||||
dependency "org.webjars:underscorejs:1.8.3"
|
||||
|
||||
dependencySet(group: 'org.apache.tomcat', version: '9.0.41') {
|
||||
dependencySet(group: 'org.apache.tomcat', version: '9.0.43') {
|
||||
entry 'tomcat-util'
|
||||
entry('tomcat-websocket') {
|
||||
exclude group: "org.apache.tomcat", name: "tomcat-websocket-api"
|
||||
exclude group: "org.apache.tomcat", name: "tomcat-servlet-api"
|
||||
}
|
||||
}
|
||||
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.41') {
|
||||
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.43') {
|
||||
entry 'tomcat-embed-core'
|
||||
entry 'tomcat-embed-websocket'
|
||||
}
|
||||
dependencySet(group: 'io.undertow', version: '2.2.3.Final') {
|
||||
dependencySet(group: 'io.undertow', version: '2.2.4.Final') {
|
||||
entry 'undertow-core'
|
||||
entry('undertow-websockets-jsr') {
|
||||
exclude group: "org.jboss.spec.javax.websocket", name: "jboss-websocket-api_1.1_spec"
|
||||
@@ -181,7 +182,7 @@ configure(allprojects) { project ->
|
||||
exclude group: "dom4j", name: "dom4j"
|
||||
}
|
||||
|
||||
dependency("junit:junit:4.13.1") {
|
||||
dependency("junit:junit:4.13.2") {
|
||||
exclude group: "org.hamcrest", name: "hamcrest-core"
|
||||
}
|
||||
dependency("de.bechte.junit:junit-hierarchicalcontextrunner:4.12.1") {
|
||||
@@ -190,14 +191,14 @@ configure(allprojects) { project ->
|
||||
dependency "org.testng:testng:7.3.0"
|
||||
dependency "org.hamcrest:hamcrest:2.1"
|
||||
dependency "org.awaitility:awaitility:3.1.6"
|
||||
dependency "org.assertj:assertj-core:3.18.1"
|
||||
dependency "org.assertj:assertj-core:3.19.0"
|
||||
dependencySet(group: 'org.xmlunit', version: '2.8.2') {
|
||||
entry 'xmlunit-assertj'
|
||||
entry('xmlunit-matchers') {
|
||||
exclude group: "org.hamcrest", name: "hamcrest-core"
|
||||
}
|
||||
}
|
||||
dependencySet(group: 'org.mockito', version: '3.7.0') {
|
||||
dependencySet(group: 'org.mockito', version: '3.7.7') {
|
||||
entry('mockito-core') {
|
||||
exclude group: "org.hamcrest", name: "hamcrest-core"
|
||||
}
|
||||
@@ -205,10 +206,10 @@ configure(allprojects) { project ->
|
||||
}
|
||||
dependency "io.mockk:mockk:1.10.2"
|
||||
|
||||
dependency("net.sourceforge.htmlunit:htmlunit:2.46.0") {
|
||||
dependency("net.sourceforge.htmlunit:htmlunit:2.47.1") {
|
||||
exclude group: "commons-logging", name: "commons-logging"
|
||||
}
|
||||
dependency("org.seleniumhq.selenium:htmlunit-driver:2.46.0") {
|
||||
dependency("org.seleniumhq.selenium:htmlunit-driver:2.47.1") {
|
||||
exclude group: "commons-logging", name: "commons-logging"
|
||||
}
|
||||
dependency("org.seleniumhq.selenium:selenium-java:3.141.59") {
|
||||
@@ -236,8 +237,8 @@ configure(allprojects) { project ->
|
||||
|
||||
dependency "com.ibm.websphere:uow:6.0.2.17"
|
||||
dependency "com.jamonapi:jamon:2.82"
|
||||
dependency "joda-time:joda-time:2.10.9"
|
||||
dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.7"
|
||||
dependency "joda-time:joda-time:2.10.10"
|
||||
dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.8"
|
||||
dependency "org.javamoney:moneta:1.3"
|
||||
|
||||
dependency "com.sun.activation:javax.activation:1.2.0"
|
||||
@@ -380,8 +381,8 @@ configure([rootProject] + javaProjects) { project ->
|
||||
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.10/",
|
||||
"https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.1/",
|
||||
"https://junit.org/junit5/docs/5.7.0/api/"
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
"https://junit.org/junit5/docs/5.7.1/api/"
|
||||
] as String[]
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ logging:
|
||||
io.spring.concourse: DEBUG
|
||||
distribute:
|
||||
optional-deployments:
|
||||
- '.*\\.zip'
|
||||
- '.*\.zip'
|
||||
spring:
|
||||
main:
|
||||
banner-mode: off
|
||||
banner-mode: off
|
||||
|
||||
@@ -3,13 +3,13 @@ set -e
|
||||
|
||||
case "$1" in
|
||||
java8)
|
||||
echo "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u275-b01/OpenJDK8U-jdk_x64_linux_hotspot_8u275b01.tar.gz"
|
||||
echo "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u282-b08/OpenJDK8U-jdk_x64_linux_hotspot_8u282b08.tar.gz"
|
||||
;;
|
||||
java11)
|
||||
echo "https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.9.1%2B1/OpenJDK11U-jdk_x64_linux_hotspot_11.0.9.1_1.tar.gz"
|
||||
echo "https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.10%2B9/OpenJDK11U-jdk_x64_linux_hotspot_11.0.10_9.tar.gz"
|
||||
;;
|
||||
java15)
|
||||
echo "https://github.com/AdoptOpenJDK/openjdk15-binaries/releases/download/jdk-15.0.1%2B9/OpenJDK15U-jdk_x64_linux_hotspot_15.0.1_9.tar.gz"
|
||||
echo "https://github.com/AdoptOpenJDK/openjdk15-binaries/releases/download/jdk-15.0.2%2B7/OpenJDK15U-jdk_x64_linux_hotspot_15.0.2_7.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo $"Unknown java version"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:focal-20201106
|
||||
FROM ubuntu:focal-20210119
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:focal-20201106
|
||||
FROM ubuntu:focal-20210119
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:focal-20201106
|
||||
FROM ubuntu:focal-20210119
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=5.3.3-SNAPSHOT
|
||||
version=5.3.4
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
|
||||
@@ -55,14 +55,6 @@ eclipse.classpath.file.whenMerged { classpath ->
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that test fixture dependencies are handled properly in Gradle 6.7.
|
||||
// Bug fixed in Gradle 6.8: https://github.com/gradle/gradle/issues/14932
|
||||
eclipse.classpath.file.whenMerged {
|
||||
entries.findAll { it instanceof ProjectDependency }.each {
|
||||
it.entryAttributes.remove('without_test_code')
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that JMH sources and resources are treated as test classpath entries
|
||||
// so that they can see test fixtures.
|
||||
// https://github.com/melix/jmh-gradle-plugin/issues/157
|
||||
|
||||
@@ -1,17 +1,37 @@
|
||||
apply plugin: 'org.springframework.build.compile'
|
||||
apply plugin: 'org.springframework.build.optional-dependencies'
|
||||
// Uncomment the following for Shadow support in the jmhJar block.
|
||||
// Currently commented out due to ZipException: archive is not a ZIP archive
|
||||
// apply plugin: 'com.github.johnrengelman.shadow'
|
||||
apply plugin: 'me.champeau.gradle.jmh'
|
||||
apply from: "$rootDir/gradle/publications.gradle"
|
||||
|
||||
dependencies {
|
||||
jmh 'org.openjdk.jmh:jmh-core:1.23'
|
||||
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.23'
|
||||
jmh 'org.openjdk.jmh:jmh-core:1.25'
|
||||
jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.25'
|
||||
jmh 'net.sf.jopt-simple:jopt-simple:4.6'
|
||||
}
|
||||
|
||||
jmh {
|
||||
duplicateClassesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
jmhJar {
|
||||
// Uncomment the following for Shadow's Transformer support.
|
||||
// mergeServiceFiles()
|
||||
// append('META-INF/spring.handlers')
|
||||
// append('META-INF/spring.schemas')
|
||||
// append('META-INF/spring.tooling')
|
||||
exclude 'LICENSE'
|
||||
exclude 'THIRD-PARTY'
|
||||
exclude 'META-INF/license.txt'
|
||||
exclude 'META-INF/notice.txt'
|
||||
exclude 'META-INF/DEPENDENCIES'
|
||||
exclude 'META-INF/LICENSE*'
|
||||
exclude 'META-INF/NOTICE'
|
||||
exclude 'META-INF/THIRD-PARTY'
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest.attributes["Implementation-Title"] = project.name
|
||||
manifest.attributes["Implementation-Version"] = project.version
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ pluginManagement {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "com.gradle.enterprise" version "3.2"
|
||||
id "io.spring.gradle-enterprise-conventions" version "0.0.2"
|
||||
id "com.gradle.enterprise" version "3.5.1"
|
||||
id "io.spring.ge.conventions" version "0.0.7"
|
||||
}
|
||||
|
||||
include "spring-aop"
|
||||
|
||||
+3
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,6 +20,7 @@ import java.io.Serializable;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import org.springframework.aop.SpringProxy;
|
||||
import org.springframework.core.NativeDetector;
|
||||
|
||||
/**
|
||||
* Default {@link AopProxyFactory} implementation, creating either a CGLIB proxy
|
||||
@@ -47,17 +48,10 @@ import org.springframework.aop.SpringProxy;
|
||||
@SuppressWarnings("serial")
|
||||
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
|
||||
|
||||
/**
|
||||
* Whether this environment lives within a native image.
|
||||
* Exposed as a private static field rather than in a {@code NativeImageDetector.inNativeImage()} static method due to https://github.com/oracle/graal/issues/2594.
|
||||
* @see <a href="https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java">ImageInfo.java</a>
|
||||
*/
|
||||
private static final boolean IN_NATIVE_IMAGE = (System.getProperty("org.graalvm.nativeimage.imagecode") != null);
|
||||
|
||||
|
||||
@Override
|
||||
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
|
||||
if (!IN_NATIVE_IMAGE &&
|
||||
if (!NativeDetector.inNativeImage() &&
|
||||
(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
|
||||
Class<?> targetClass = config.getTargetClass();
|
||||
if (targetClass == null) {
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -116,7 +116,7 @@ public abstract class AbstractPoolingTargetSource extends AbstractPrototypeBased
|
||||
|
||||
|
||||
/**
|
||||
* Return an IntroductionAdvisor that providing a mixin
|
||||
* Return an IntroductionAdvisor that provides a mixin
|
||||
* exposing statistics about the pool maintained by this object.
|
||||
*/
|
||||
public DefaultIntroductionAdvisor getPoolingConfigMixin() {
|
||||
|
||||
+4
-5
@@ -1,14 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">
|
||||
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<beans>
|
||||
|
||||
<bean id="bean1" class="org.springframework.beans.factory.ConcurrentBeanFactoryTests$ConcurrentBean"
|
||||
<bean id="bean1" class="org.springframework.beans.factory.ConcurrentBeanFactoryBenchmark$ConcurrentBean"
|
||||
scope="prototype">
|
||||
<property name="date" value="2004/08/08"/>
|
||||
</bean>
|
||||
|
||||
<bean id="bean2" class="org.springframework.beans.factory.ConcurrentBeanFactoryTests$ConcurrentBean"
|
||||
<bean id="bean2" class="org.springframework.beans.factory.ConcurrentBeanFactoryBenchmark$ConcurrentBean"
|
||||
scope="prototype">
|
||||
<property name="date" value="2000/02/02"/>
|
||||
</bean>
|
||||
|
||||
@@ -268,10 +268,7 @@ public abstract class BeanUtils {
|
||||
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
|
||||
Assert.notNull(clazz, "Class must not be null");
|
||||
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(clazz)) {
|
||||
Constructor<T> kotlinPrimaryConstructor = KotlinDelegate.findPrimaryConstructor(clazz);
|
||||
if (kotlinPrimaryConstructor != null) {
|
||||
return kotlinPrimaryConstructor;
|
||||
}
|
||||
return KotlinDelegate.findPrimaryConstructor(clazz);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -779,7 +776,14 @@ public abstract class BeanUtils {
|
||||
if (readMethod != null) {
|
||||
ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod);
|
||||
ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0);
|
||||
if (targetResolvableType.isAssignableFrom(sourceResolvableType)) {
|
||||
|
||||
// Ignore generic types in assignable check if either ResolvableType has unresolvable generics.
|
||||
boolean isAssignable =
|
||||
(sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ?
|
||||
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) :
|
||||
targetResolvableType.isAssignableFrom(sourceResolvableType));
|
||||
|
||||
if (isAssignable) {
|
||||
try {
|
||||
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
|
||||
readMethod.setAccessible(true);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -41,10 +41,10 @@ public class TypeMismatchException extends PropertyAccessException {
|
||||
private String propertyName;
|
||||
|
||||
@Nullable
|
||||
private transient Object value;
|
||||
private final transient Object value;
|
||||
|
||||
@Nullable
|
||||
private Class<?> requiredType;
|
||||
private final Class<?> requiredType;
|
||||
|
||||
|
||||
/**
|
||||
|
||||
+106
-82
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -628,45 +628,58 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
Field field = (Field) this.member;
|
||||
Object value;
|
||||
if (this.cached) {
|
||||
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
|
||||
try {
|
||||
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
// Unexpected removal of target bean for cached argument -> re-resolve
|
||||
value = resolveFieldValue(field, bean, beanName);
|
||||
}
|
||||
}
|
||||
else {
|
||||
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
|
||||
desc.setContainingClass(bean.getClass());
|
||||
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
|
||||
Assert.state(beanFactory != null, "No BeanFactory available");
|
||||
TypeConverter typeConverter = beanFactory.getTypeConverter();
|
||||
try {
|
||||
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
|
||||
}
|
||||
catch (BeansException ex) {
|
||||
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
|
||||
}
|
||||
synchronized (this) {
|
||||
if (!this.cached) {
|
||||
Object cachedFieldValue = null;
|
||||
if (value != null || this.required) {
|
||||
cachedFieldValue = desc;
|
||||
registerDependentBeans(beanName, autowiredBeanNames);
|
||||
if (autowiredBeanNames.size() == 1) {
|
||||
String autowiredBeanName = autowiredBeanNames.iterator().next();
|
||||
if (beanFactory.containsBean(autowiredBeanName) &&
|
||||
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
|
||||
cachedFieldValue = new ShortcutDependencyDescriptor(
|
||||
desc, autowiredBeanName, field.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
this.cachedFieldValue = cachedFieldValue;
|
||||
this.cached = true;
|
||||
}
|
||||
}
|
||||
value = resolveFieldValue(field, bean, beanName);
|
||||
}
|
||||
if (value != null) {
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
field.set(bean, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
|
||||
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
|
||||
desc.setContainingClass(bean.getClass());
|
||||
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
|
||||
Assert.state(beanFactory != null, "No BeanFactory available");
|
||||
TypeConverter typeConverter = beanFactory.getTypeConverter();
|
||||
Object value;
|
||||
try {
|
||||
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
|
||||
}
|
||||
catch (BeansException ex) {
|
||||
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
|
||||
}
|
||||
synchronized (this) {
|
||||
if (!this.cached) {
|
||||
Object cachedFieldValue = null;
|
||||
if (value != null || this.required) {
|
||||
cachedFieldValue = desc;
|
||||
registerDependentBeans(beanName, autowiredBeanNames);
|
||||
if (autowiredBeanNames.size() == 1) {
|
||||
String autowiredBeanName = autowiredBeanNames.iterator().next();
|
||||
if (beanFactory.containsBean(autowiredBeanName) &&
|
||||
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
|
||||
cachedFieldValue = new ShortcutDependencyDescriptor(
|
||||
desc, autowiredBeanName, field.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
this.cachedFieldValue = cachedFieldValue;
|
||||
this.cached = true;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -695,58 +708,16 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
Method method = (Method) this.member;
|
||||
Object[] arguments;
|
||||
if (this.cached) {
|
||||
// Shortcut for avoiding synchronization...
|
||||
arguments = resolveCachedArguments(beanName);
|
||||
try {
|
||||
arguments = resolveCachedArguments(beanName);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
// Unexpected removal of target bean for cached argument -> re-resolve
|
||||
arguments = resolveMethodArguments(method, bean, beanName);
|
||||
}
|
||||
}
|
||||
else {
|
||||
int argumentCount = method.getParameterCount();
|
||||
arguments = new Object[argumentCount];
|
||||
DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];
|
||||
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
|
||||
Assert.state(beanFactory != null, "No BeanFactory available");
|
||||
TypeConverter typeConverter = beanFactory.getTypeConverter();
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
MethodParameter methodParam = new MethodParameter(method, i);
|
||||
DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
|
||||
currDesc.setContainingClass(bean.getClass());
|
||||
descriptors[i] = currDesc;
|
||||
try {
|
||||
Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
|
||||
if (arg == null && !this.required) {
|
||||
arguments = null;
|
||||
break;
|
||||
}
|
||||
arguments[i] = arg;
|
||||
}
|
||||
catch (BeansException ex) {
|
||||
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
|
||||
}
|
||||
}
|
||||
synchronized (this) {
|
||||
if (!this.cached) {
|
||||
if (arguments != null) {
|
||||
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length);
|
||||
registerDependentBeans(beanName, autowiredBeans);
|
||||
if (autowiredBeans.size() == argumentCount) {
|
||||
Iterator<String> it = autowiredBeans.iterator();
|
||||
Class<?>[] paramTypes = method.getParameterTypes();
|
||||
for (int i = 0; i < paramTypes.length; i++) {
|
||||
String autowiredBeanName = it.next();
|
||||
if (beanFactory.containsBean(autowiredBeanName) &&
|
||||
beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
|
||||
cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
|
||||
descriptors[i], autowiredBeanName, paramTypes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.cachedMethodArguments = cachedMethodArguments;
|
||||
}
|
||||
else {
|
||||
this.cachedMethodArguments = null;
|
||||
}
|
||||
this.cached = true;
|
||||
}
|
||||
}
|
||||
arguments = resolveMethodArguments(method, bean, beanName);
|
||||
}
|
||||
if (arguments != null) {
|
||||
try {
|
||||
@@ -771,6 +742,59 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
}
|
||||
return arguments;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object[] resolveMethodArguments(Method method, Object bean, @Nullable String beanName) {
|
||||
int argumentCount = method.getParameterCount();
|
||||
Object[] arguments = new Object[argumentCount];
|
||||
DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];
|
||||
Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
|
||||
Assert.state(beanFactory != null, "No BeanFactory available");
|
||||
TypeConverter typeConverter = beanFactory.getTypeConverter();
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
MethodParameter methodParam = new MethodParameter(method, i);
|
||||
DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
|
||||
currDesc.setContainingClass(bean.getClass());
|
||||
descriptors[i] = currDesc;
|
||||
try {
|
||||
Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
|
||||
if (arg == null && !this.required) {
|
||||
arguments = null;
|
||||
break;
|
||||
}
|
||||
arguments[i] = arg;
|
||||
}
|
||||
catch (BeansException ex) {
|
||||
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
|
||||
}
|
||||
}
|
||||
synchronized (this) {
|
||||
if (!this.cached) {
|
||||
if (arguments != null) {
|
||||
DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length);
|
||||
registerDependentBeans(beanName, autowiredBeans);
|
||||
if (autowiredBeans.size() == argumentCount) {
|
||||
Iterator<String> it = autowiredBeans.iterator();
|
||||
Class<?>[] paramTypes = method.getParameterTypes();
|
||||
for (int i = 0; i < paramTypes.length; i++) {
|
||||
String autowiredBeanName = it.next();
|
||||
if (beanFactory.containsBean(autowiredBeanName) &&
|
||||
beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
|
||||
cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
|
||||
descriptors[i], autowiredBeanName, paramTypes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.cachedMethodArguments = cachedMethodArguments;
|
||||
}
|
||||
else {
|
||||
this.cachedMethodArguments = null;
|
||||
}
|
||||
this.cached = true;
|
||||
}
|
||||
}
|
||||
return arguments;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+9
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -52,6 +52,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Dave Syer
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @author Brian Clozel
|
||||
* @since 4.1
|
||||
*/
|
||||
public abstract class YamlProcessor {
|
||||
@@ -128,10 +129,11 @@ public abstract class YamlProcessor {
|
||||
|
||||
/**
|
||||
* Set the supported types that can be loaded from YAML documents.
|
||||
* <p>If no supported types are configured, all types encountered in YAML
|
||||
* documents will be supported. If an unsupported type is encountered, an
|
||||
* {@link IllegalStateException} will be thrown when the corresponding YAML
|
||||
* node is processed.
|
||||
* <p>If no supported types are configured, only Java standard classes
|
||||
* (as defined in {@link org.yaml.snakeyaml.constructor.SafeConstructor})
|
||||
* encountered in YAML documents will be supported.
|
||||
* If an unsupported type is encountered, an {@link IllegalStateException}
|
||||
* will be thrown when the corresponding YAML node is processed.
|
||||
* @param supportedTypes the supported types, or an empty array to clear the
|
||||
* supported types
|
||||
* @since 5.1.16
|
||||
@@ -182,12 +184,8 @@ public abstract class YamlProcessor {
|
||||
protected Yaml createYaml() {
|
||||
LoaderOptions loaderOptions = new LoaderOptions();
|
||||
loaderOptions.setAllowDuplicateKeys(false);
|
||||
|
||||
if (!this.supportedTypes.isEmpty()) {
|
||||
return new Yaml(new FilteringConstructor(loaderOptions), new Representer(),
|
||||
new DumperOptions(), loaderOptions);
|
||||
}
|
||||
return new Yaml(loaderOptions);
|
||||
return new Yaml(new FilteringConstructor(loaderOptions), new Representer(),
|
||||
new DumperOptions(), loaderOptions);
|
||||
}
|
||||
|
||||
private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
|
||||
|
||||
+8
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -73,6 +73,7 @@ import org.springframework.beans.factory.config.TypedStringValue;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.NamedThreadLocal;
|
||||
import org.springframework.core.NativeDetector;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.PriorityOrdered;
|
||||
import org.springframework.core.ResolvableType;
|
||||
@@ -123,14 +124,6 @@ import org.springframework.util.StringUtils;
|
||||
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
|
||||
implements AutowireCapableBeanFactory {
|
||||
|
||||
/**
|
||||
* Whether this environment lives within a native image.
|
||||
* Exposed as a private static field rather than in a {@code NativeImageDetector.inNativeImage()} static method due to https://github.com/oracle/graal/issues/2594.
|
||||
* @see <a href="https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java">ImageInfo.java</a>
|
||||
*/
|
||||
private static final boolean IN_NATIVE_IMAGE = (System.getProperty("org.graalvm.nativeimage.imagecode") != null);
|
||||
|
||||
|
||||
/** Strategy for creating bean instances. */
|
||||
private InstantiationStrategy instantiationStrategy;
|
||||
|
||||
@@ -184,7 +177,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
ignoreDependencyInterface(BeanNameAware.class);
|
||||
ignoreDependencyInterface(BeanFactoryAware.class);
|
||||
ignoreDependencyInterface(BeanClassLoaderAware.class);
|
||||
if (IN_NATIVE_IMAGE) {
|
||||
if (NativeDetector.inNativeImage()) {
|
||||
this.instantiationStrategy = new SimpleInstantiationStrategy();
|
||||
}
|
||||
else {
|
||||
@@ -1022,6 +1015,11 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
throw ex;
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
// Don't swallow a linkage error since it contains a full stacktrace on
|
||||
// first occurrence... and just a plain NoClassDefFoundError afterwards.
|
||||
if (ex.contains(LinkageError.class)) {
|
||||
throw ex;
|
||||
}
|
||||
// Instantiation failure, maybe too early...
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Bean creation exception on singleton FactoryBean type check: " + ex);
|
||||
|
||||
+23
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -1194,8 +1194,8 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
||||
this.primary == that.primary &&
|
||||
this.nonPublicAccessAllowed == that.nonPublicAccessAllowed &&
|
||||
this.lenientConstructorResolution == that.lenientConstructorResolution &&
|
||||
ObjectUtils.nullSafeEquals(this.constructorArgumentValues, that.constructorArgumentValues) &&
|
||||
ObjectUtils.nullSafeEquals(this.propertyValues, that.propertyValues) &&
|
||||
equalsConstructorArgumentValues(that) &&
|
||||
equalsPropertyValues(that) &&
|
||||
ObjectUtils.nullSafeEquals(this.methodOverrides, that.methodOverrides) &&
|
||||
ObjectUtils.nullSafeEquals(this.factoryBeanName, that.factoryBeanName) &&
|
||||
ObjectUtils.nullSafeEquals(this.factoryMethodName, that.factoryMethodName) &&
|
||||
@@ -1208,12 +1208,30 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess
|
||||
super.equals(other));
|
||||
}
|
||||
|
||||
private boolean equalsConstructorArgumentValues(AbstractBeanDefinition other) {
|
||||
if (!hasConstructorArgumentValues()) {
|
||||
return !other.hasConstructorArgumentValues();
|
||||
}
|
||||
return ObjectUtils.nullSafeEquals(this.constructorArgumentValues, other.constructorArgumentValues);
|
||||
}
|
||||
|
||||
private boolean equalsPropertyValues(AbstractBeanDefinition other) {
|
||||
if (!hasPropertyValues()) {
|
||||
return !other.hasPropertyValues();
|
||||
}
|
||||
return ObjectUtils.nullSafeEquals(this.propertyValues, other.propertyValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashCode = ObjectUtils.nullSafeHashCode(getBeanClassName());
|
||||
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.scope);
|
||||
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.constructorArgumentValues);
|
||||
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.propertyValues);
|
||||
if (hasConstructorArgumentValues()) {
|
||||
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.constructorArgumentValues);
|
||||
}
|
||||
if (hasPropertyValues()) {
|
||||
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.propertyValues);
|
||||
}
|
||||
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.factoryBeanName);
|
||||
hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.factoryMethodName);
|
||||
hashCode = 29 * hashCode + super.hashCode();
|
||||
|
||||
+75
-74
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -119,14 +119,16 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
|
||||
}
|
||||
}
|
||||
else {
|
||||
Class<?>[] paramTypes = destroyMethod.getParameterTypes();
|
||||
if (paramTypes.length > 1) {
|
||||
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
|
||||
beanName + "' has more than one parameter - not supported as destroy method");
|
||||
}
|
||||
else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) {
|
||||
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
|
||||
beanName + "' has a non-boolean parameter - not supported as destroy method");
|
||||
if (destroyMethod.getParameterCount() > 0) {
|
||||
Class<?>[] paramTypes = destroyMethod.getParameterTypes();
|
||||
if (paramTypes.length > 1) {
|
||||
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
|
||||
beanName + "' has more than one parameter - not supported as destroy method");
|
||||
}
|
||||
else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) {
|
||||
throw new BeanDefinitionValidationException("Method '" + destroyMethodName + "' of bean '" +
|
||||
beanName + "' has a non-boolean parameter - not supported as destroy method");
|
||||
}
|
||||
}
|
||||
destroyMethod = ClassUtils.getInterfaceMethodIfPossible(destroyMethod);
|
||||
}
|
||||
@@ -170,66 +172,6 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If the current value of the given beanDefinition's "destroyMethodName" property is
|
||||
* {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method.
|
||||
* Candidate methods are currently limited to public, no-arg methods named "close" or
|
||||
* "shutdown" (whether declared locally or inherited). The given BeanDefinition's
|
||||
* "destroyMethodName" is updated to be null if no such method is found, otherwise set
|
||||
* to the name of the inferred method. This constant serves as the default for the
|
||||
* {@code @Bean#destroyMethod} attribute and the value of the constant may also be
|
||||
* used in XML within the {@code <bean destroy-method="">} or {@code
|
||||
* <beans default-destroy-method="">} attributes.
|
||||
* <p>Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable}
|
||||
* interfaces, reflectively calling the "close" method on implementing beans as well.
|
||||
*/
|
||||
@Nullable
|
||||
private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
|
||||
String destroyMethodName = beanDefinition.getDestroyMethodName();
|
||||
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
|
||||
(destroyMethodName == null && bean instanceof AutoCloseable)) {
|
||||
// Only perform destroy method inference or Closeable detection
|
||||
// in case of the bean not explicitly implementing DisposableBean
|
||||
if (!(bean instanceof DisposableBean)) {
|
||||
try {
|
||||
return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
try {
|
||||
return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
|
||||
}
|
||||
catch (NoSuchMethodException ex2) {
|
||||
// no candidate destroy method found
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for all DestructionAwareBeanPostProcessors in the List.
|
||||
* @param processors the List to search
|
||||
* @return the filtered List of DestructionAwareBeanPostProcessors
|
||||
*/
|
||||
@Nullable
|
||||
private List<DestructionAwareBeanPostProcessor> filterPostProcessors(
|
||||
List<DestructionAwareBeanPostProcessor> processors, Object bean) {
|
||||
|
||||
List<DestructionAwareBeanPostProcessor> filteredPostProcessors = null;
|
||||
if (!CollectionUtils.isEmpty(processors)) {
|
||||
filteredPostProcessors = new ArrayList<>(processors.size());
|
||||
for (DestructionAwareBeanPostProcessor processor : processors) {
|
||||
if (processor.requiresDestruction(bean)) {
|
||||
filteredPostProcessors.add(processor);
|
||||
}
|
||||
}
|
||||
}
|
||||
return filteredPostProcessors;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
destroy();
|
||||
@@ -384,12 +326,50 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
|
||||
if (bean instanceof DisposableBean || bean instanceof AutoCloseable) {
|
||||
return true;
|
||||
}
|
||||
String destroyMethodName = beanDefinition.getDestroyMethodName();
|
||||
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) {
|
||||
return (ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME) ||
|
||||
ClassUtils.hasMethod(bean.getClass(), SHUTDOWN_METHOD_NAME));
|
||||
return inferDestroyMethodIfNecessary(bean, beanDefinition) != null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If the current value of the given beanDefinition's "destroyMethodName" property is
|
||||
* {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method.
|
||||
* Candidate methods are currently limited to public, no-arg methods named "close" or
|
||||
* "shutdown" (whether declared locally or inherited). The given BeanDefinition's
|
||||
* "destroyMethodName" is updated to be null if no such method is found, otherwise set
|
||||
* to the name of the inferred method. This constant serves as the default for the
|
||||
* {@code @Bean#destroyMethod} attribute and the value of the constant may also be
|
||||
* used in XML within the {@code <bean destroy-method="">} or {@code
|
||||
* <beans default-destroy-method="">} attributes.
|
||||
* <p>Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable}
|
||||
* interfaces, reflectively calling the "close" method on implementing beans as well.
|
||||
*/
|
||||
@Nullable
|
||||
private static String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
|
||||
String destroyMethodName = beanDefinition.resolvedDestroyMethodName;
|
||||
if (destroyMethodName == null) {
|
||||
destroyMethodName = beanDefinition.getDestroyMethodName();
|
||||
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
|
||||
(destroyMethodName == null && bean instanceof AutoCloseable)) {
|
||||
// Only perform destroy method inference or Closeable detection
|
||||
// in case of the bean not explicitly implementing DisposableBean
|
||||
destroyMethodName = null;
|
||||
if (!(bean instanceof DisposableBean)) {
|
||||
try {
|
||||
destroyMethodName = bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
try {
|
||||
destroyMethodName = bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
|
||||
}
|
||||
catch (NoSuchMethodException ex2) {
|
||||
// no candidate destroy method found
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
beanDefinition.resolvedDestroyMethodName = (destroyMethodName != null ? destroyMethodName : "");
|
||||
}
|
||||
return StringUtils.hasLength(destroyMethodName);
|
||||
return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -408,4 +388,25 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for all DestructionAwareBeanPostProcessors in the List.
|
||||
* @param processors the List to search
|
||||
* @return the filtered List of DestructionAwareBeanPostProcessors
|
||||
*/
|
||||
@Nullable
|
||||
private static List<DestructionAwareBeanPostProcessor> filterPostProcessors(
|
||||
List<DestructionAwareBeanPostProcessor> processors, Object bean) {
|
||||
|
||||
List<DestructionAwareBeanPostProcessor> filteredPostProcessors = null;
|
||||
if (!CollectionUtils.isEmpty(processors)) {
|
||||
filteredPostProcessors = new ArrayList<>(processors.size());
|
||||
for (DestructionAwareBeanPostProcessor processor : processors) {
|
||||
if (processor.requiresDestruction(bean)) {
|
||||
filteredPostProcessors.add(processor);
|
||||
}
|
||||
}
|
||||
}
|
||||
return filteredPostProcessors;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -86,6 +86,10 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
|
||||
@Nullable
|
||||
volatile Method factoryMethodToIntrospect;
|
||||
|
||||
/** Package-visible field for caching a resolved destroy method name (also for inferred). */
|
||||
@Nullable
|
||||
volatile String resolvedDestroyMethodName;
|
||||
|
||||
/** Common lock for the four constructor fields below. */
|
||||
final Object constructorArgumentLock = new Object();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,12 +19,16 @@ package org.springframework.beans;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -196,6 +200,29 @@ class BeanUtilsTests {
|
||||
assertThat(longListHolder.getList()).isEmpty();
|
||||
}
|
||||
|
||||
@Test // gh-26531
|
||||
void copyPropertiesIgnoresGenericsIfSourceOrTargetHasUnresolvableGenerics() throws Exception {
|
||||
Order original = new Order("test", Arrays.asList("foo", "bar"));
|
||||
|
||||
// Create a Proxy that loses the generic type information for the getLineItems() method.
|
||||
OrderSummary proxy = proxyOrder(original);
|
||||
assertThat(OrderSummary.class.getDeclaredMethod("getLineItems").toGenericString())
|
||||
.contains("java.util.List<java.lang.String>");
|
||||
assertThat(proxy.getClass().getDeclaredMethod("getLineItems").toGenericString())
|
||||
.contains("java.util.List")
|
||||
.doesNotContain("<java.lang.String>");
|
||||
|
||||
// Ensure that our custom Proxy works as expected.
|
||||
assertThat(proxy.getId()).isEqualTo("test");
|
||||
assertThat(proxy.getLineItems()).containsExactly("foo", "bar");
|
||||
|
||||
// Copy from proxy to target.
|
||||
Order target = new Order();
|
||||
BeanUtils.copyProperties(proxy, target);
|
||||
assertThat(target.getId()).isEqualTo("test");
|
||||
assertThat(target.getLineItems()).containsExactly("foo", "bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyPropertiesWithEditable() throws Exception {
|
||||
TestBean tb = new TestBean();
|
||||
@@ -633,4 +660,77 @@ class BeanUtilsTests {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class Order {
|
||||
|
||||
private String id;
|
||||
private List<String> lineItems;
|
||||
|
||||
|
||||
Order() {
|
||||
}
|
||||
|
||||
Order(String id, List<String> lineItems) {
|
||||
this.id = id;
|
||||
this.lineItems = lineItems;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public List<String> getLineItems() {
|
||||
return this.lineItems;
|
||||
}
|
||||
|
||||
public void setLineItems(List<String> lineItems) {
|
||||
this.lineItems = lineItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Order [id=" + this.id + ", lineItems=" + this.lineItems + "]";
|
||||
}
|
||||
}
|
||||
|
||||
private interface OrderSummary {
|
||||
|
||||
String getId();
|
||||
|
||||
List<String> getLineItems();
|
||||
}
|
||||
|
||||
|
||||
private OrderSummary proxyOrder(Order order) {
|
||||
return (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(),
|
||||
new Class<?>[] { OrderSummary.class }, new OrderInvocationHandler(order));
|
||||
}
|
||||
|
||||
|
||||
private static class OrderInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final Order order;
|
||||
|
||||
|
||||
OrderInvocationHandler(Order order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
try {
|
||||
// Ignore args since OrderSummary doesn't declare any methods with arguments,
|
||||
// and we're not supporting equals(Object), etc.
|
||||
return Order.class.getDeclaredMethod(method.getName()).invoke(this.order);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw ex.getTargetException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+193
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -297,6 +297,121 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
||||
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalResourceInjectionWithSingletonRemoval() {
|
||||
RootBeanDefinition rbd = new RootBeanDefinition(OptionalResourceInjectionBean.class);
|
||||
rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("annotatedBean", rbd);
|
||||
TestBean tb = new TestBean();
|
||||
bf.registerSingleton("testBean", tb);
|
||||
IndexedTestBean itb = new IndexedTestBean();
|
||||
bf.registerSingleton("indexedTestBean", itb);
|
||||
NestedTestBean ntb1 = new NestedTestBean();
|
||||
bf.registerSingleton("nestedTestBean1", ntb1);
|
||||
NestedTestBean ntb2 = new NestedTestBean();
|
||||
bf.registerSingleton("nestedTestBean2", ntb2);
|
||||
|
||||
OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean2()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean3()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean4()).isSameAs(tb);
|
||||
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
|
||||
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
|
||||
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
|
||||
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
|
||||
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
|
||||
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
|
||||
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
|
||||
|
||||
bf.destroySingleton("testBean");
|
||||
|
||||
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isNull();
|
||||
assertThat(bean.getTestBean2()).isNull();
|
||||
assertThat(bean.getTestBean3()).isNull();
|
||||
assertThat(bean.getTestBean4()).isNull();
|
||||
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
|
||||
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
|
||||
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
|
||||
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
|
||||
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
|
||||
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
|
||||
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
|
||||
|
||||
bf.registerSingleton("testBean", tb);
|
||||
|
||||
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean2()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean3()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean4()).isSameAs(tb);
|
||||
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
|
||||
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
|
||||
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
|
||||
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
|
||||
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
|
||||
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
|
||||
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalResourceInjectionWithBeanDefinitionRemoval() {
|
||||
RootBeanDefinition rbd = new RootBeanDefinition(OptionalResourceInjectionBean.class);
|
||||
rbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("annotatedBean", rbd);
|
||||
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
|
||||
IndexedTestBean itb = new IndexedTestBean();
|
||||
bf.registerSingleton("indexedTestBean", itb);
|
||||
NestedTestBean ntb1 = new NestedTestBean();
|
||||
bf.registerSingleton("nestedTestBean1", ntb1);
|
||||
NestedTestBean ntb2 = new NestedTestBean();
|
||||
bf.registerSingleton("nestedTestBean2", ntb2);
|
||||
|
||||
OptionalResourceInjectionBean bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
|
||||
assertThat(bean.getTestBean2()).isSameAs(bf.getBean("testBean"));
|
||||
assertThat(bean.getTestBean3()).isSameAs(bf.getBean("testBean"));
|
||||
assertThat(bean.getTestBean4()).isSameAs(bf.getBean("testBean"));
|
||||
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
|
||||
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
|
||||
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
|
||||
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
|
||||
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
|
||||
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
|
||||
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
|
||||
|
||||
bf.removeBeanDefinition("testBean");
|
||||
|
||||
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isNull();
|
||||
assertThat(bean.getTestBean2()).isNull();
|
||||
assertThat(bean.getTestBean3()).isNull();
|
||||
assertThat(bean.getTestBean4()).isNull();
|
||||
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
|
||||
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
|
||||
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
|
||||
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
|
||||
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
|
||||
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
|
||||
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
|
||||
|
||||
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
|
||||
|
||||
bean = (OptionalResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(bf.getBean("testBean"));
|
||||
assertThat(bean.getTestBean2()).isSameAs(bf.getBean("testBean"));
|
||||
assertThat(bean.getTestBean3()).isSameAs(bf.getBean("testBean"));
|
||||
assertThat(bean.getTestBean4()).isSameAs(bf.getBean("testBean"));
|
||||
assertThat(bean.getIndexedTestBean()).isSameAs(itb);
|
||||
assertThat(bean.getNestedTestBeans().length).isEqualTo(2);
|
||||
assertThat(bean.getNestedTestBeans()[0]).isSameAs(ntb1);
|
||||
assertThat(bean.getNestedTestBeans()[1]).isSameAs(ntb2);
|
||||
assertThat(bean.nestedTestBeansField.length).isEqualTo(2);
|
||||
assertThat(bean.nestedTestBeansField[0]).isSameAs(ntb1);
|
||||
assertThat(bean.nestedTestBeansField[1]).isSameAs(ntb2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalCollectionResourceInjection() {
|
||||
RootBeanDefinition rbd = new RootBeanDefinition(OptionalCollectionResourceInjectionBean.class);
|
||||
@@ -533,6 +648,83 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
||||
assertThat(bean.getBeanFactory()).isSameAs(bf);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructorResourceInjectionWithSingletonRemoval() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class);
|
||||
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("annotatedBean", bd);
|
||||
TestBean tb = new TestBean();
|
||||
bf.registerSingleton("testBean", tb);
|
||||
NestedTestBean ntb = new NestedTestBean();
|
||||
bf.registerSingleton("nestedTestBean", ntb);
|
||||
|
||||
ConstructorResourceInjectionBean bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean2()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean3()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean4()).isSameAs(tb);
|
||||
assertThat(bean.getNestedTestBean()).isSameAs(ntb);
|
||||
assertThat(bean.getBeanFactory()).isSameAs(bf);
|
||||
|
||||
bf.destroySingleton("nestedTestBean");
|
||||
|
||||
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean2()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean3()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean4()).isSameAs(tb);
|
||||
assertThat(bean.getNestedTestBean()).isNull();
|
||||
assertThat(bean.getBeanFactory()).isSameAs(bf);
|
||||
|
||||
bf.registerSingleton("nestedTestBean", ntb);
|
||||
|
||||
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean2()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean3()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean4()).isSameAs(tb);
|
||||
assertThat(bean.getNestedTestBean()).isSameAs(ntb);
|
||||
assertThat(bean.getBeanFactory()).isSameAs(bf);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructorResourceInjectionWithBeanDefinitionRemoval() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class);
|
||||
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("annotatedBean", bd);
|
||||
TestBean tb = new TestBean();
|
||||
bf.registerSingleton("testBean", tb);
|
||||
bf.registerBeanDefinition("nestedTestBean", new RootBeanDefinition(NestedTestBean.class));
|
||||
|
||||
ConstructorResourceInjectionBean bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean2()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean3()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean4()).isSameAs(tb);
|
||||
assertThat(bean.getNestedTestBean()).isSameAs(bf.getBean("nestedTestBean"));
|
||||
assertThat(bean.getBeanFactory()).isSameAs(bf);
|
||||
|
||||
bf.removeBeanDefinition("nestedTestBean");
|
||||
|
||||
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean2()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean3()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean4()).isSameAs(tb);
|
||||
assertThat(bean.getNestedTestBean()).isNull();
|
||||
assertThat(bean.getBeanFactory()).isSameAs(bf);
|
||||
|
||||
bf.registerBeanDefinition("nestedTestBean", new RootBeanDefinition(NestedTestBean.class));
|
||||
|
||||
bean = (ConstructorResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean2()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean3()).isSameAs(tb);
|
||||
assertThat(bean.getTestBean4()).isSameAs(tb);
|
||||
assertThat(bean.getNestedTestBean()).isSameAs(bf.getBean("nestedTestBean"));
|
||||
assertThat(bean.getBeanFactory()).isSameAs(bf);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructorResourceInjectionWithNullFromFactoryBean() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(ConstructorResourceInjectionBean.class);
|
||||
|
||||
+27
-15
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,9 +17,11 @@
|
||||
package org.springframework.beans.factory.config;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.yaml.snakeyaml.constructor.ConstructorException;
|
||||
@@ -28,7 +30,6 @@ import org.yaml.snakeyaml.scanner.ScannerException;
|
||||
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
@@ -39,10 +40,12 @@ import static org.assertj.core.api.Assertions.entry;
|
||||
* @author Dave Syer
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class YamlProcessorTests {
|
||||
|
||||
private final YamlProcessor processor = new YamlProcessor() {};
|
||||
private final YamlProcessor processor = new YamlProcessor() {
|
||||
};
|
||||
|
||||
|
||||
@Test
|
||||
@@ -79,8 +82,8 @@ class YamlProcessorTests {
|
||||
void badResource() {
|
||||
setYaml("foo: bar\ncd\nspam:\n foo: baz");
|
||||
assertThatExceptionOfType(ScannerException.class)
|
||||
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
|
||||
.withMessageContaining("line 3, column 1");
|
||||
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
|
||||
.withMessageContaining("line 3, column 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -127,8 +130,8 @@ class YamlProcessorTests {
|
||||
Map<String, Object> bar = (Map<String, Object>) map.get("bar");
|
||||
assertThat(bar.get("spam")).isEqualTo("bucket");
|
||||
|
||||
List<Object> keysFromProperties = properties.keySet().stream().collect(toList());
|
||||
List<String> keysFromFlattenedMap = flattenedMap.keySet().stream().collect(toList());
|
||||
List<Object> keysFromProperties = new ArrayList<>(properties.keySet());
|
||||
List<String> keysFromFlattenedMap = new ArrayList<>(flattenedMap.keySet());
|
||||
assertThat(keysFromProperties).containsExactlyInAnyOrderElementsOf(keysFromFlattenedMap);
|
||||
// Keys in the Properties object are sorted.
|
||||
assertThat(keysFromProperties).containsExactly("bar.spam", "cat", "foo");
|
||||
@@ -138,14 +141,23 @@ class YamlProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void customTypeSupportedByDefault() throws Exception {
|
||||
void standardTypesSupportedByDefault() throws Exception {
|
||||
setYaml("value: !!set\n ? first\n ? second");
|
||||
this.processor.process((properties, map) -> {
|
||||
assertThat(properties).containsExactly(entry("value[0]", "first"), entry("value[1]", "second"));
|
||||
assertThat(map.get("value")).isInstanceOf(Set.class);
|
||||
Set<String> set = (Set<String>) map.get("value");
|
||||
assertThat(set).containsExactly("first", "second");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void customTypeNotSupportedByDefault() throws Exception {
|
||||
URL url = new URL("https://localhost:9000/");
|
||||
setYaml("value: !!java.net.URL [\"" + url + "\"]");
|
||||
|
||||
this.processor.process((properties, map) -> {
|
||||
assertThat(properties).containsExactly(entry("value", url));
|
||||
assertThat(map).containsExactly(entry("value", url));
|
||||
});
|
||||
assertThatExceptionOfType(ConstructorException.class)
|
||||
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
|
||||
.withMessageContaining("Unsupported type encountered in YAML document: java.net.URL");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -168,8 +180,8 @@ class YamlProcessorTests {
|
||||
setYaml("value: !!java.net.URL [\"https://localhost:9000/\"]");
|
||||
|
||||
assertThatExceptionOfType(ConstructorException.class)
|
||||
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
|
||||
.withMessageContaining("Unsupported type encountered in YAML document: java.net.URL");
|
||||
.isThrownBy(() -> this.processor.process((properties, map) -> {}))
|
||||
.withMessageContaining("Unsupported type encountered in YAML document: java.net.URL");
|
||||
}
|
||||
|
||||
private void setYaml(String yaml) {
|
||||
|
||||
+11
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -133,6 +133,16 @@ public class BeanDefinitionTests {
|
||||
assertThat(bd.equals(otherBd)).isTrue();
|
||||
assertThat(otherBd.equals(bd)).isTrue();
|
||||
assertThat(bd.hashCode() == otherBd.hashCode()).isTrue();
|
||||
|
||||
bd.getPropertyValues();
|
||||
assertThat(bd.equals(otherBd)).isTrue();
|
||||
assertThat(otherBd.equals(bd)).isTrue();
|
||||
assertThat(bd.hashCode() == otherBd.hashCode()).isTrue();
|
||||
|
||||
bd.getConstructorArgumentValues();
|
||||
assertThat(bd.equals(otherBd)).isTrue();
|
||||
assertThat(otherBd.equals(bd)).isTrue();
|
||||
assertThat(bd.hashCode() == otherBd.hashCode()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+8
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,7 +31,8 @@ import org.springframework.core.annotation.AliasFor;
|
||||
*
|
||||
* <p>In contrast to the {@link Cacheable @Cacheable} annotation, this annotation
|
||||
* does not cause the advised method to be skipped. Rather, it always causes the
|
||||
* method to be invoked and its result to be stored in the associated cache. Note
|
||||
* method to be invoked and its result to be stored in the associated cache if the
|
||||
* {@link #condition()} and {@link #unless()} expressions match accordingly. Note
|
||||
* that Java8's {@code Optional} return types are automatically handled and its
|
||||
* content is stored in the cache if present.
|
||||
*
|
||||
@@ -118,10 +119,15 @@ public @interface CachePut {
|
||||
/**
|
||||
* Spring Expression Language (SpEL) expression used for making the cache
|
||||
* put operation conditional.
|
||||
* <p>This expression is evaluated after the method has been called due to the
|
||||
* nature of the put operation and can therefore refer to the {@code result}.
|
||||
* <p>Default is {@code ""}, meaning the method result is always cached.
|
||||
* <p>The SpEL expression evaluates against a dedicated context that provides the
|
||||
* following meta-data:
|
||||
* <ul>
|
||||
* <li>{@code #result} for a reference to the result of the method invocation. For
|
||||
* supported wrappers such as {@code Optional}, {@code #result} refers to the actual
|
||||
* object, not the wrapper</li>
|
||||
* <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
|
||||
* references to the {@link java.lang.reflect.Method method}, target object, and
|
||||
* affected cache(s) respectively.</li>
|
||||
@@ -136,8 +142,6 @@ public @interface CachePut {
|
||||
|
||||
/**
|
||||
* Spring Expression Language (SpEL) expression used to veto the cache put operation.
|
||||
* <p>Unlike {@link #condition}, this expression is evaluated after the method
|
||||
* has been called and can therefore refer to the {@code result}.
|
||||
* <p>Default is {@code ""}, meaning that caching is never vetoed.
|
||||
* <p>The SpEL expression evaluates against a dedicated context that provides the
|
||||
* following meta-data:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,10 +26,10 @@ import org.springframework.core.io.ResourceLoader;
|
||||
* the {@link org.springframework.context.ApplicationContextAware} interface.
|
||||
*
|
||||
* <p>Note that {@link org.springframework.core.io.Resource} dependencies can also
|
||||
* be exposed as bean properties of type {@code Resource}, populated via Strings
|
||||
* with automatic type conversion by the bean factory. This removes the need for
|
||||
* implementing any callback interface just for the purpose of accessing a
|
||||
* specific file resource.
|
||||
* be exposed as bean properties of type {@code Resource} or {@code Resource[]},
|
||||
* populated via Strings with automatic type conversion by the bean factory. This
|
||||
* removes the need for implementing any callback interface just for the purpose
|
||||
* of accessing specific file resources.
|
||||
*
|
||||
* <p>You typically need a {@link ResourceLoader} when your application object has to
|
||||
* access a variety of file resources whose names are calculated. A good strategy is
|
||||
@@ -48,7 +48,7 @@ import org.springframework.core.io.ResourceLoader;
|
||||
* default; see also the {@code ResourcePatternUtils.getResourcePatternResolver} method.
|
||||
*
|
||||
* <p>As an alternative to a {@code ResourcePatternResolver} dependency, consider
|
||||
* exposing bean properties of type {@code Resource} array, populated via pattern
|
||||
* exposing bean properties of type {@code Resource[]} array, populated via pattern
|
||||
* Strings with automatic type conversion by the bean factory at binding time.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
|
||||
+3
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -52,6 +52,7 @@ import org.springframework.context.ApplicationStartupAware;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.context.annotation.ConfigurationClassEnhancer.EnhancedConfiguration;
|
||||
import org.springframework.core.NativeDetector;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.PriorityOrdered;
|
||||
import org.springframework.core.env.Environment;
|
||||
@@ -104,13 +105,6 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
private static final String IMPORT_REGISTRY_BEAN_NAME =
|
||||
ConfigurationClassPostProcessor.class.getName() + ".importRegistry";
|
||||
|
||||
/**
|
||||
* Whether this environment lives within a native image.
|
||||
* Exposed as a private static field rather than in a {@code NativeImageDetector.inNativeImage()} static method due to https://github.com/oracle/graal/issues/2594.
|
||||
* @see <a href="https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java">ImageInfo.java</a>
|
||||
*/
|
||||
private static final boolean IN_NATIVE_IMAGE = (System.getProperty("org.graalvm.nativeimage.imagecode") != null);
|
||||
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
@@ -427,7 +421,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
|
||||
}
|
||||
}
|
||||
if (configBeanDefs.isEmpty() || IN_NATIVE_IMAGE) {
|
||||
if (configBeanDefs.isEmpty() || NativeDetector.inNativeImage()) {
|
||||
// nothing to enhance -> return immediately
|
||||
enhanceConfigClasses.end();
|
||||
return;
|
||||
|
||||
+6
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -66,6 +66,7 @@ import org.springframework.context.event.SimpleApplicationEventMulticaster;
|
||||
import org.springframework.context.expression.StandardBeanExpressionResolver;
|
||||
import org.springframework.context.weaving.LoadTimeWeaverAware;
|
||||
import org.springframework.context.weaving.LoadTimeWeaverAwareProcessor;
|
||||
import org.springframework.core.NativeDetector;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.SpringProperties;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
@@ -164,13 +165,6 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
*/
|
||||
private static final boolean shouldIgnoreSpel = SpringProperties.getFlag("spring.spel.ignore");
|
||||
|
||||
/**
|
||||
* Whether this environment lives within a native image.
|
||||
* Exposed as a private static field rather than in a {@code NativeImageDetector.inNativeImage()} static method due to https://github.com/oracle/graal/issues/2594.
|
||||
* @see <a href="https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java">ImageInfo.java</a>
|
||||
*/
|
||||
private static final boolean IN_NATIVE_IMAGE = (System.getProperty("org.graalvm.nativeimage.imagecode") != null);
|
||||
|
||||
|
||||
static {
|
||||
// Eagerly load the ContextClosedEvent class to avoid weird classloader issues
|
||||
@@ -711,7 +705,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
|
||||
|
||||
// Detect a LoadTimeWeaver and prepare for weaving, if found.
|
||||
if (!IN_NATIVE_IMAGE && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
|
||||
if (!NativeDetector.inNativeImage() && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
|
||||
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
|
||||
// Set a temporary ClassLoader for type matching.
|
||||
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
|
||||
@@ -752,7 +746,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
|
||||
// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
|
||||
// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
|
||||
if (!IN_NATIVE_IMAGE && beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
|
||||
if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
|
||||
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
|
||||
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
|
||||
}
|
||||
@@ -943,7 +937,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
publishEvent(new ContextRefreshedEvent(this));
|
||||
|
||||
// Participate in LiveBeansView MBean, if active.
|
||||
if (!IN_NATIVE_IMAGE) {
|
||||
if (!NativeDetector.inNativeImage()) {
|
||||
LiveBeansView.registerApplicationContext(this);
|
||||
}
|
||||
}
|
||||
@@ -1054,7 +1048,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
logger.debug("Closing " + this);
|
||||
}
|
||||
|
||||
if (!IN_NATIVE_IMAGE) {
|
||||
if (!NativeDetector.inNativeImage()) {
|
||||
LiveBeansView.unregisterApplicationContext(this);
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.context.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@@ -74,6 +75,11 @@ class ContextTypeMatchClassLoader extends DecoratingClassLoader implements Smart
|
||||
return (clazz.getClassLoader() instanceof ContextOverridingClassLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> publicDefineClass(String name, byte[] b, @Nullable ProtectionDomain protectionDomain) {
|
||||
return defineClass(name, b, 0, b.length, protectionDomain);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ClassLoader to be created for each loaded class.
|
||||
|
||||
+27
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -59,6 +59,19 @@ final class PostProcessorRegistrationDelegate {
|
||||
public static void invokeBeanFactoryPostProcessors(
|
||||
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
|
||||
|
||||
// WARNING: Although it may appear that the body of this method can be easily
|
||||
// refactored to avoid the use of multiple loops and multiple lists, the use
|
||||
// of multiple lists and multiple passes over the names of processors is
|
||||
// intentional. We must ensure that we honor the contracts for PriorityOrdered
|
||||
// and Ordered processors. Specifically, we must NOT cause processors to be
|
||||
// instantiated (via getBean() invocations) or registered in the ApplicationContext
|
||||
// in the wrong order.
|
||||
//
|
||||
// Before submitting a pull request (PR) to change this method, please review the
|
||||
// list of all declined PRs involving changes to PostProcessorRegistrationDelegate
|
||||
// to ensure that your proposal does not result in a breaking change:
|
||||
// https://github.com/spring-projects/spring-framework/issues?q=PostProcessorRegistrationDelegate+is%3Aclosed+label%3A%22status%3A+declined%22
|
||||
|
||||
// Invoke BeanDefinitionRegistryPostProcessors first, if any.
|
||||
Set<String> processedBeans = new HashSet<>();
|
||||
|
||||
@@ -192,6 +205,19 @@ final class PostProcessorRegistrationDelegate {
|
||||
public static void registerBeanPostProcessors(
|
||||
ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
|
||||
|
||||
// WARNING: Although it may appear that the body of this method can be easily
|
||||
// refactored to avoid the use of multiple loops and multiple lists, the use
|
||||
// of multiple lists and multiple passes over the names of processors is
|
||||
// intentional. We must ensure that we honor the contracts for PriorityOrdered
|
||||
// and Ordered processors. Specifically, we must NOT cause processors to be
|
||||
// instantiated (via getBean() invocations) or registered in the ApplicationContext
|
||||
// in the wrong order.
|
||||
//
|
||||
// Before submitting a pull request (PR) to change this method, please review the
|
||||
// list of all declined PRs involving changes to PostProcessorRegistrationDelegate
|
||||
// to ensure that your proposal does not result in a breaking change:
|
||||
// https://github.com/spring-projects/spring-framework/issues?q=PostProcessorRegistrationDelegate+is%3Aclosed+label%3A%22status%3A+declined%22
|
||||
|
||||
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
|
||||
|
||||
// Register BeanPostProcessorChecker that logs an info message when
|
||||
|
||||
+98
-25
@@ -38,24 +38,6 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
final class QuartzCronField extends CronField {
|
||||
|
||||
/**
|
||||
* Temporal adjuster that returns the last weekday of the month.
|
||||
*/
|
||||
private static final TemporalAdjuster lastWeekdayOfMonth = temporal -> {
|
||||
Temporal lastDayOfMonth = TemporalAdjusters.lastDayOfMonth().adjustInto(temporal);
|
||||
int dayOfWeek = lastDayOfMonth.get(ChronoField.DAY_OF_WEEK);
|
||||
if (dayOfWeek == 6) { // Saturday
|
||||
return lastDayOfMonth.minus(1, ChronoUnit.DAYS);
|
||||
}
|
||||
else if (dayOfWeek == 7) { // Sunday
|
||||
return lastDayOfMonth.minus(2, ChronoUnit.DAYS);
|
||||
}
|
||||
else {
|
||||
return lastDayOfMonth;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private final Type rollForwardType;
|
||||
|
||||
private final TemporalAdjuster adjuster;
|
||||
@@ -97,11 +79,11 @@ final class QuartzCronField extends CronField {
|
||||
throw new IllegalArgumentException("Unrecognized characters before 'L' in '" + value + "'");
|
||||
}
|
||||
else if (value.length() == 2 && value.charAt(1) == 'W') { // "LW"
|
||||
adjuster = lastWeekdayOfMonth;
|
||||
adjuster = lastWeekdayOfMonth();
|
||||
}
|
||||
else {
|
||||
if (value.length() == 1) { // "L"
|
||||
adjuster = TemporalAdjusters.lastDayOfMonth();
|
||||
adjuster = lastDayOfMonth();
|
||||
}
|
||||
else { // "L-[0-9]+"
|
||||
int offset = Integer.parseInt(value.substring(idx + 1));
|
||||
@@ -155,7 +137,7 @@ final class QuartzCronField extends CronField {
|
||||
}
|
||||
else { // "[0-7]L"
|
||||
DayOfWeek dayOfWeek = parseDayOfWeek(value.substring(0, idx));
|
||||
adjuster = TemporalAdjusters.lastInMonth(dayOfWeek);
|
||||
adjuster = lastInMonth(dayOfWeek);
|
||||
}
|
||||
return new QuartzCronField(Type.DAY_OF_WEEK, Type.DAY_OF_MONTH, adjuster, value);
|
||||
}
|
||||
@@ -171,14 +153,17 @@ final class QuartzCronField extends CronField {
|
||||
// "[0-7]#[0-9]+"
|
||||
DayOfWeek dayOfWeek = parseDayOfWeek(value.substring(0, idx));
|
||||
int ordinal = Integer.parseInt(value.substring(idx + 1));
|
||||
if (ordinal <= 0) {
|
||||
throw new IllegalArgumentException("Ordinal '" + ordinal + "' in '" + value +
|
||||
"' must be positive number ");
|
||||
}
|
||||
|
||||
TemporalAdjuster adjuster = TemporalAdjusters.dayOfWeekInMonth(ordinal, dayOfWeek);
|
||||
TemporalAdjuster adjuster = dayOfWeekInMonth(ordinal, dayOfWeek);
|
||||
return new QuartzCronField(Type.DAY_OF_WEEK, Type.DAY_OF_MONTH, adjuster, value);
|
||||
}
|
||||
throw new IllegalArgumentException("No 'L' or '#' found in '" + value + "'");
|
||||
}
|
||||
|
||||
|
||||
private static DayOfWeek parseDayOfWeek(String value) {
|
||||
int dayOfWeek = Integer.parseInt(value);
|
||||
if (dayOfWeek == 0) {
|
||||
@@ -193,6 +178,54 @@ final class QuartzCronField extends CronField {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an adjuster that resets to midnight.
|
||||
*/
|
||||
private static TemporalAdjuster atMidnight() {
|
||||
return temporal -> {
|
||||
if (temporal.isSupported(ChronoField.NANO_OF_DAY)) {
|
||||
return temporal.with(ChronoField.NANO_OF_DAY, 0);
|
||||
}
|
||||
else {
|
||||
return temporal;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an adjuster that returns a new temporal set to the last
|
||||
* day of the current month at midnight.
|
||||
*/
|
||||
private static TemporalAdjuster lastDayOfMonth() {
|
||||
TemporalAdjuster adjuster = TemporalAdjusters.lastDayOfMonth();
|
||||
return temporal -> {
|
||||
Temporal result = adjuster.adjustInto(temporal);
|
||||
return rollbackToMidnight(temporal, result);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an adjuster that returns the last weekday of the month.
|
||||
*/
|
||||
private static TemporalAdjuster lastWeekdayOfMonth() {
|
||||
TemporalAdjuster adjuster = TemporalAdjusters.lastDayOfMonth();
|
||||
return temporal -> {
|
||||
Temporal lastDom = adjuster.adjustInto(temporal);
|
||||
Temporal result;
|
||||
int dow = lastDom.get(ChronoField.DAY_OF_WEEK);
|
||||
if (dow == 6) { // Saturday
|
||||
result = lastDom.minus(1, ChronoUnit.DAYS);
|
||||
}
|
||||
else if (dow == 7) { // Sunday
|
||||
result = lastDom.minus(2, ChronoUnit.DAYS);
|
||||
}
|
||||
else {
|
||||
result = lastDom;
|
||||
}
|
||||
return rollbackToMidnight(temporal, result);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a temporal adjuster that finds the nth-to-last day of the month.
|
||||
* @param offset the negative offset, i.e. -3 means third-to-last
|
||||
@@ -200,9 +233,10 @@ final class QuartzCronField extends CronField {
|
||||
*/
|
||||
private static TemporalAdjuster lastDayWithOffset(int offset) {
|
||||
Assert.isTrue(offset < 0, "Offset should be < 0");
|
||||
TemporalAdjuster adjuster = TemporalAdjusters.lastDayOfMonth();
|
||||
return temporal -> {
|
||||
Temporal lastDayOfMonth = TemporalAdjusters.lastDayOfMonth().adjustInto(temporal);
|
||||
return lastDayOfMonth.plus(offset, ChronoUnit.DAYS);
|
||||
Temporal result = adjuster.adjustInto(temporal).plus(offset, ChronoUnit.DAYS);
|
||||
return rollbackToMidnight(temporal, result);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -228,6 +262,7 @@ final class QuartzCronField extends CronField {
|
||||
int count = 0;
|
||||
while (count++ < CronExpression.MAX_ATTEMPTS) {
|
||||
temporal = Type.DAY_OF_MONTH.elapseUntil(cast(temporal), dayOfMonth);
|
||||
temporal = atMidnight().adjustInto(temporal);
|
||||
current = Type.DAY_OF_MONTH.get(temporal);
|
||||
if (current == dayOfMonth) {
|
||||
dayOfWeek = temporal.get(ChronoField.DAY_OF_WEEK);
|
||||
@@ -253,6 +288,44 @@ final class QuartzCronField extends CronField {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a temporal adjuster that finds the last of the given doy-of-week
|
||||
* in a month.
|
||||
*/
|
||||
private static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek) {
|
||||
TemporalAdjuster adjuster = TemporalAdjusters.lastInMonth(dayOfWeek);
|
||||
return temporal -> {
|
||||
Temporal result = adjuster.adjustInto(temporal);
|
||||
return rollbackToMidnight(temporal, result);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a temporal adjuster that finds {@code ordinal}-th occurrence of
|
||||
* the given day-of-week in a month.
|
||||
*/
|
||||
private static TemporalAdjuster dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek) {
|
||||
TemporalAdjuster adjuster = TemporalAdjusters.dayOfWeekInMonth(ordinal, dayOfWeek);
|
||||
return temporal -> {
|
||||
Temporal result = adjuster.adjustInto(temporal);
|
||||
return rollbackToMidnight(temporal, result);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Rolls back the given {@code result} to midnight. When
|
||||
* {@code current} has the same day of month as {@code result}, the former
|
||||
* is returned, to make sure that we don't end up before where we started.
|
||||
*/
|
||||
private static Temporal rollbackToMidnight(Temporal current, Temporal result) {
|
||||
if (result.get(ChronoField.DAY_OF_MONTH) == current.get(ChronoField.DAY_OF_MONTH)) {
|
||||
return current;
|
||||
}
|
||||
else {
|
||||
return atMidnight().adjustInto(result);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T extends Temporal & Comparable<? super T>> T cast(Temporal temporal) {
|
||||
return (T) temporal;
|
||||
|
||||
+30
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,8 +17,10 @@
|
||||
package org.springframework.validation.beanvalidation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -291,12 +293,17 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
|
||||
configureParameterNameProvider(this.parameterNameDiscoverer, configuration);
|
||||
}
|
||||
|
||||
List<InputStream> mappingStreams = null;
|
||||
if (this.mappingLocations != null) {
|
||||
mappingStreams = new ArrayList<>(this.mappingLocations.length);
|
||||
for (Resource location : this.mappingLocations) {
|
||||
try {
|
||||
configuration.addMapping(location.getInputStream());
|
||||
InputStream stream = location.getInputStream();
|
||||
mappingStreams.add(stream);
|
||||
configuration.addMapping(stream);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
closeMappingStreams(mappingStreams);
|
||||
throw new IllegalStateException("Cannot read mapping resource: " + location);
|
||||
}
|
||||
}
|
||||
@@ -307,8 +314,13 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
|
||||
// Allow for custom post-processing before we actually build the ValidatorFactory.
|
||||
postProcessConfiguration(configuration);
|
||||
|
||||
this.validatorFactory = configuration.buildValidatorFactory();
|
||||
setTargetValidator(this.validatorFactory.getValidator());
|
||||
try {
|
||||
this.validatorFactory = configuration.buildValidatorFactory();
|
||||
setTargetValidator(this.validatorFactory.getValidator());
|
||||
}
|
||||
finally {
|
||||
closeMappingStreams(mappingStreams);
|
||||
}
|
||||
}
|
||||
|
||||
private void configureParameterNameProvider(ParameterNameDiscoverer discoverer, Configuration<?> configuration) {
|
||||
@@ -329,6 +341,18 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
|
||||
});
|
||||
}
|
||||
|
||||
private void closeMappingStreams(@Nullable List<InputStream> mappingStreams){
|
||||
if (!CollectionUtils.isEmpty(mappingStreams)) {
|
||||
for (InputStream stream : mappingStreams) {
|
||||
try {
|
||||
stream.close();
|
||||
}
|
||||
catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-process the given Bean Validation configuration,
|
||||
* adding to or overriding any of its settings.
|
||||
@@ -397,7 +421,7 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
|
||||
return super.unwrap(type);
|
||||
}
|
||||
catch (ValidationException ex) {
|
||||
// ignore - we'll try ValidatorFactory unwrapping next
|
||||
// Ignore - we'll try ValidatorFactory unwrapping next
|
||||
}
|
||||
}
|
||||
if (this.validatorFactory != null) {
|
||||
@@ -405,7 +429,7 @@ public class LocalValidatorFactoryBean extends SpringValidatorAdapter
|
||||
return this.validatorFactory.unwrap(type);
|
||||
}
|
||||
catch (ValidationException ex) {
|
||||
// ignore if just being asked for ValidatorFactory
|
||||
// Ignore if just being asked for ValidatorFactory
|
||||
if (ValidatorFactory.class == type) {
|
||||
return (T) this.validatorFactory;
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -179,7 +179,7 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
|
||||
}
|
||||
}
|
||||
else {
|
||||
// got no BindingResult - can only do standard rejectValue call
|
||||
// Got no BindingResult - can only do standard rejectValue call
|
||||
// with automatic extraction of the current field value
|
||||
errors.rejectValue(field, errorCode, errorArgs, violation.getMessage());
|
||||
}
|
||||
@@ -386,7 +386,7 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation.
|
||||
return (type != null ? this.targetValidator.unwrap(type) : (T) this.targetValidator);
|
||||
}
|
||||
catch (ValidationException ex) {
|
||||
// ignore if just being asked for plain Validator
|
||||
// Ignore if just being asked for plain JSR-303 Validator
|
||||
if (javax.validation.Validator.class == type) {
|
||||
return (T) this.targetValidator;
|
||||
}
|
||||
|
||||
+69
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,14 +16,23 @@
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
|
||||
/**
|
||||
* @author Andy Wilkinson
|
||||
* @author Liu Dongmiao
|
||||
*/
|
||||
public class AggressiveFactoryBeanInstantiationTests {
|
||||
|
||||
@@ -49,17 +58,66 @@ public class AggressiveFactoryBeanInstantiationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkLinkageError() {
|
||||
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
|
||||
context.register(BeanMethodConfigurationWithExceptionInInitializer.class);
|
||||
context.refresh();
|
||||
fail("Should have thrown BeanCreationException");
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
PrintWriter pw = new PrintWriter(baos);
|
||||
ex.printStackTrace(pw);
|
||||
pw.flush();
|
||||
String stackTrace = baos.toString();
|
||||
assertThat(stackTrace.contains(".<clinit>")).isTrue();
|
||||
assertThat(stackTrace.contains("java.lang.NoClassDefFoundError")).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class BeanMethodConfiguration {
|
||||
|
||||
@Bean
|
||||
public String foo() {
|
||||
return "foo";
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AutowiredBean autowiredBean() {
|
||||
return new AutowiredBean();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@DependsOn("autowiredBean")
|
||||
public SimpleFactoryBean simpleFactoryBean(ApplicationContext applicationContext) {
|
||||
return new SimpleFactoryBean(applicationContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class BeanMethodConfigurationWithExceptionInInitializer extends BeanMethodConfiguration {
|
||||
|
||||
@Bean
|
||||
@DependsOn("autowiredBean")
|
||||
@Override
|
||||
public SimpleFactoryBean simpleFactoryBean(ApplicationContext applicationContext) {
|
||||
new ExceptionInInitializer();
|
||||
return new SimpleFactoryBean(applicationContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class AutowiredBean {
|
||||
|
||||
@Autowired
|
||||
String foo;
|
||||
}
|
||||
|
||||
|
||||
static class SimpleFactoryBean implements FactoryBean<Object> {
|
||||
|
||||
public SimpleFactoryBean(ApplicationContext applicationContext) {
|
||||
@@ -76,4 +134,14 @@ public class AggressiveFactoryBeanInstantiationTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class ExceptionInInitializer {
|
||||
|
||||
private static final int ERROR = callInClinit();
|
||||
|
||||
private static int callInClinit() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -40,7 +40,7 @@ public class EnableLoadTimeWeavingTests {
|
||||
@Test
|
||||
public void control() {
|
||||
GenericXmlApplicationContext ctx =
|
||||
new GenericXmlApplicationContext(getClass(), "EnableLoadTimeWeavingTests-context.xml");
|
||||
new GenericXmlApplicationContext(getClass(), "EnableLoadTimeWeavingTests-context.xml");
|
||||
ctx.getBean("loadTimeWeaver", LoadTimeWeaver.class);
|
||||
}
|
||||
|
||||
@@ -73,9 +73,11 @@ public class EnableLoadTimeWeavingTests {
|
||||
verify(loadTimeWeaver).addTransformer(isA(ClassFileTransformer.class));
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.DISABLED)
|
||||
static class EnableLTWConfig_withAjWeavingDisabled implements LoadTimeWeavingConfigurer {
|
||||
|
||||
@Override
|
||||
public LoadTimeWeaver getLoadTimeWeaver() {
|
||||
return mock(LoadTimeWeaver.class);
|
||||
@@ -85,6 +87,7 @@ public class EnableLoadTimeWeavingTests {
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.AUTODETECT)
|
||||
static class EnableLTWConfig_withAjWeavingAutodetect implements LoadTimeWeavingConfigurer {
|
||||
|
||||
@Override
|
||||
public LoadTimeWeaver getLoadTimeWeaver() {
|
||||
return mock(LoadTimeWeaver.class);
|
||||
@@ -94,9 +97,11 @@ public class EnableLoadTimeWeavingTests {
|
||||
@Configuration
|
||||
@EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.ENABLED)
|
||||
static class EnableLTWConfig_withAjWeavingEnabled implements LoadTimeWeavingConfigurer {
|
||||
|
||||
@Override
|
||||
public LoadTimeWeaver getLoadTimeWeaver() {
|
||||
return mock(LoadTimeWeaver.class);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+106
@@ -1137,4 +1137,110 @@ class CronExpressionTests {
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
assertThat(actual.getDayOfWeek()).isEqualTo(THURSDAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void quartzLastDayOfMonthEveryHour() {
|
||||
CronExpression expression = CronExpression.parse("0 0 * L * *");
|
||||
|
||||
LocalDateTime last = LocalDateTime.of(2021, 1, 30, 0, 1);
|
||||
LocalDateTime expected = LocalDateTime.of(2021, 1, 31, 0, 0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = LocalDateTime.of(2021, 1, 31, 1, 0);
|
||||
expected = LocalDateTime.of(2021, 1, 31, 2, 0);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void quartzLastDayOfMonthOffsetEveryHour() {
|
||||
CronExpression expression = CronExpression.parse("0 0 * L-1 * *");
|
||||
|
||||
LocalDateTime last = LocalDateTime.of(2021, 1, 29, 0, 1);
|
||||
LocalDateTime expected = LocalDateTime.of(2021, 1, 30, 0, 0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = LocalDateTime.of(2021, 1, 30, 1, 0);
|
||||
expected = LocalDateTime.of(2021, 1, 30, 2, 0);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void quartzFirstWeekdayOfMonthEveryHour() {
|
||||
CronExpression expression = CronExpression.parse("0 0 * 1W * *");
|
||||
|
||||
LocalDateTime last = LocalDateTime.of(2021, 1, 31, 0, 1);
|
||||
LocalDateTime expected = LocalDateTime.of(2021, 2, 1, 0, 0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = LocalDateTime.of(2021, 2, 1, 1, 0);
|
||||
expected = LocalDateTime.of(2021, 2, 1, 2, 0);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void quartzLastWeekdayOfMonthEveryHour() {
|
||||
CronExpression expression = CronExpression.parse("0 0 * LW * *");
|
||||
|
||||
LocalDateTime last = LocalDateTime.of(2021, 1, 28, 0, 1);
|
||||
LocalDateTime expected = LocalDateTime.of(2021, 1, 29, 0, 0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = LocalDateTime.of(2021, 1, 29, 1, 0);
|
||||
expected = LocalDateTime.of(2021, 1, 29, 2, 0);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void quartz5thFridayOfTheMonthEveryHour() {
|
||||
CronExpression expression = CronExpression.parse("0 0 * ? * FRI#5");
|
||||
|
||||
LocalDateTime last = LocalDateTime.of(2021, 1, 28, 0, 1);
|
||||
LocalDateTime expected = LocalDateTime.of(2021, 1, 29, 0, 0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
assertThat(actual.getDayOfWeek()).isEqualTo(FRIDAY);
|
||||
|
||||
last = LocalDateTime.of(2021, 1, 29, 1, 0);
|
||||
expected = LocalDateTime.of(2021, 1, 29, 2, 0);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void quartzLastFridayOfTheMonthEveryHour() {
|
||||
CronExpression expression = CronExpression.parse("0 0 * ? * FRIL");
|
||||
|
||||
LocalDateTime last = LocalDateTime.of(2021, 1, 28, 0, 1);
|
||||
LocalDateTime expected = LocalDateTime.of(2021, 1, 29, 0, 0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
assertThat(actual.getDayOfWeek()).isEqualTo(FRIDAY);
|
||||
|
||||
last = LocalDateTime.of(2021, 1, 29, 1, 0);
|
||||
expected = LocalDateTime.of(2021, 1, 29, 2, 0);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+1
@@ -98,6 +98,7 @@ class QuartzCronFieldTests {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> QuartzCronField.parseDaysOfWeek("L#1"));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> QuartzCronField.parseDaysOfWeek("8#1"));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> QuartzCronField.parseDaysOfWeek("2#1,2#3,2#5"));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> QuartzCronField.parseDaysOfWeek("FRI#-1"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
plugins {
|
||||
id "com.github.johnrengelman.shadow" version "5.2.0"
|
||||
}
|
||||
|
||||
description = "Spring Core"
|
||||
|
||||
apply plugin: "kotlin"
|
||||
|
||||
// spring-core includes asm and repackages cglib, inlining both into the spring-core jar.
|
||||
// cglib itself depends on asm and is therefore further transformed by the JarJar task to
|
||||
// cglib itself depends on asm and is therefore further transformed by the ShadowJar task to
|
||||
// depend on org.springframework.asm; this avoids including two different copies of asm.
|
||||
def cglibVersion = "3.3.0"
|
||||
def objenesisVersion = "3.1"
|
||||
|
||||
@@ -77,7 +77,7 @@ public abstract class AnnotationVisitor {
|
||||
&& api != Opcodes.ASM10_EXPERIMENTAL) {
|
||||
throw new IllegalArgumentException("Unsupported api " + api);
|
||||
}
|
||||
// SPRING PATCH: no preview mode check for ASM 9 experimental
|
||||
// SPRING PATCH: no preview mode check for ASM experimental
|
||||
this.api = api;
|
||||
this.av = annotationVisitor;
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ public abstract class ClassVisitor {
|
||||
&& api != Opcodes.ASM10_EXPERIMENTAL) {
|
||||
throw new IllegalArgumentException("Unsupported api " + api);
|
||||
}
|
||||
// SPRING PATCH: no preview mode check for ASM 9 experimental
|
||||
// SPRING PATCH: no preview mode check for ASM experimental
|
||||
this.api = api;
|
||||
this.cv = classVisitor;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public abstract class FieldVisitor {
|
||||
&& api != Opcodes.ASM10_EXPERIMENTAL) {
|
||||
throw new IllegalArgumentException("Unsupported api " + api);
|
||||
}
|
||||
// SPRING PATCH: no preview mode check for ASM 9 experimental
|
||||
// SPRING PATCH: no preview mode check for ASM experimental
|
||||
this.api = api;
|
||||
this.fv = fieldVisitor;
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public abstract class MethodVisitor {
|
||||
&& api != Opcodes.ASM10_EXPERIMENTAL) {
|
||||
throw new IllegalArgumentException("Unsupported api " + api);
|
||||
}
|
||||
// SPRING PATCH: no preview mode check for ASM 9 experimental
|
||||
// SPRING PATCH: no preview mode check for ASM experimental
|
||||
this.api = api;
|
||||
this.mv = methodVisitor;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ public abstract class ModuleVisitor {
|
||||
&& api != Opcodes.ASM10_EXPERIMENTAL) {
|
||||
throw new IllegalArgumentException("Unsupported api " + api);
|
||||
}
|
||||
// SPRING PATCH: no preview mode check for ASM 9 experimental
|
||||
// SPRING PATCH: no preview mode check for ASM experimental
|
||||
this.api = api;
|
||||
this.mv = moduleVisitor;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ public abstract class RecordComponentVisitor {
|
||||
&& api != Opcodes.ASM10_EXPERIMENTAL) {
|
||||
throw new IllegalArgumentException("Unsupported api " + api);
|
||||
}
|
||||
// SPRING PATCH: no preview mode check for ASM 9 experimental
|
||||
// SPRING PATCH: no preview mode check for ASM experimental
|
||||
this.api = api;
|
||||
this.delegate = recordComponentVisitor;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,7 +18,7 @@ package org.springframework.asm;
|
||||
|
||||
/**
|
||||
* Utility class exposing constants related to Spring's internal repackaging
|
||||
* of the ASM bytecode library: currently based on ASM 9.0 plus minor patches.
|
||||
* of the ASM bytecode library: currently based on ASM 9.x plus minor patches.
|
||||
*
|
||||
* <p>See <a href="package-summary.html">package-level javadocs</a> for more
|
||||
* information on {@code org.springframework.asm}.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Spring's repackaging of
|
||||
* <a href="https://gitlab.ow2.org/asm/asm">ASM 9.0</a>
|
||||
* <a href="https://gitlab.ow2.org/asm/asm">ASM 9.x</a>
|
||||
* (with Spring-specific patches; for internal use only).
|
||||
*
|
||||
* <p>This repackaging technique avoids any potential conflicts with
|
||||
|
||||
@@ -522,26 +522,46 @@ public class ReflectUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// Classic option: protected ClassLoader.defineClass method
|
||||
if (c == null && classLoaderDefineClassMethod != null) {
|
||||
// Direct defineClass attempt on the target Classloader
|
||||
if (c == null) {
|
||||
if (protectionDomain == null) {
|
||||
protectionDomain = PROTECTION_DOMAIN;
|
||||
}
|
||||
Object[] args = new Object[]{className, b, 0, b.length, protectionDomain};
|
||||
|
||||
// Look for publicDefineClass(String name, byte[] b, ProtectionDomain protectionDomain)
|
||||
try {
|
||||
if (!classLoaderDefineClassMethod.isAccessible()) {
|
||||
classLoaderDefineClassMethod.setAccessible(true);
|
||||
}
|
||||
c = (Class) classLoaderDefineClassMethod.invoke(loader, args);
|
||||
Method publicDefineClass = loader.getClass().getMethod(
|
||||
"publicDefineClass", String.class, byte[].class, ProtectionDomain.class);
|
||||
c = (Class) publicDefineClass.invoke(loader, className, b, protectionDomain);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw new CodeGenerationException(ex.getTargetException());
|
||||
if (!(ex.getTargetException() instanceof UnsupportedOperationException)) {
|
||||
throw new CodeGenerationException(ex.getTargetException());
|
||||
}
|
||||
// in case of UnsupportedOperationException, fall through
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// Fall through if setAccessible fails with InaccessibleObjectException on JDK 9+
|
||||
// (on the module path and/or with a JVM bootstrapped with --illegal-access=deny)
|
||||
if (!ex.getClass().getName().endsWith("InaccessibleObjectException")) {
|
||||
throw new CodeGenerationException(ex);
|
||||
// publicDefineClass method not available -> fall through
|
||||
}
|
||||
|
||||
// Classic option: protected ClassLoader.defineClass method
|
||||
if (c == null && classLoaderDefineClassMethod != null) {
|
||||
Object[] args = new Object[]{className, b, 0, b.length, protectionDomain};
|
||||
try {
|
||||
if (!classLoaderDefineClassMethod.isAccessible()) {
|
||||
classLoaderDefineClassMethod.setAccessible(true);
|
||||
}
|
||||
c = (Class) classLoaderDefineClassMethod.invoke(loader, args);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw new CodeGenerationException(ex.getTargetException());
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// Fall through if setAccessible fails with InaccessibleObjectException on JDK 9+
|
||||
// (on the module path and/or with a JVM bootstrapped with --illegal-access=deny)
|
||||
if (!ex.getClass().getName().endsWith("InaccessibleObjectException")) {
|
||||
throw new CodeGenerationException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -39,15 +39,9 @@ package org.springframework.core;
|
||||
*/
|
||||
public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {
|
||||
|
||||
/**
|
||||
* Whether this environment lives within a native image.
|
||||
* Exposed as a private static field rather than in a {@code NativeImageDetector.inNativeImage()} static method due to https://github.com/oracle/graal/issues/2594.
|
||||
* @see <a href="https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java">ImageInfo.java</a>
|
||||
*/
|
||||
private static final boolean IN_NATIVE_IMAGE = (System.getProperty("org.graalvm.nativeimage.imagecode") != null);
|
||||
|
||||
public DefaultParameterNameDiscoverer() {
|
||||
if (KotlinDetector.isKotlinReflectPresent() && !IN_NATIVE_IMAGE) {
|
||||
// TODO Remove this conditional inclusion when upgrading to Kotlin 1.5, see https://youtrack.jetbrains.com/issue/KT-44594
|
||||
if (KotlinDetector.isKotlinReflectPresent() && !NativeDetector.inNativeImage()) {
|
||||
addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
|
||||
}
|
||||
addDiscoverer(new StandardReflectionParameterNameDiscoverer());
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core;
|
||||
|
||||
/**
|
||||
* A common delegate for detecting a GraalVM native image environment.
|
||||
*
|
||||
* <p>Requires using the {@code -H:+InlineBeforeAnalysis} native image compiler flag in order to allow code removal at
|
||||
* build time.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.3.4
|
||||
*/
|
||||
public abstract class NativeDetector {
|
||||
|
||||
// See https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java
|
||||
private static final boolean imageCode = (System.getProperty("org.graalvm.nativeimage.imagecode") != null);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if invoked in the context of image building or during image runtime, else {@code false}.
|
||||
*/
|
||||
public static boolean inNativeImage() {
|
||||
return imageCode;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -59,13 +59,6 @@ final class SerializableTypeWrapper {
|
||||
private static final Class<?>[] SUPPORTED_SERIALIZABLE_TYPES = {
|
||||
GenericArrayType.class, ParameterizedType.class, TypeVariable.class, WildcardType.class};
|
||||
|
||||
/**
|
||||
* Whether this environment lives within a native image.
|
||||
* Exposed as a private static field rather than in a {@code NativeImageDetector.inNativeImage()} static method due to https://github.com/oracle/graal/issues/2594.
|
||||
* @see <a href="https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java">ImageInfo.java</a>
|
||||
*/
|
||||
private static final boolean IN_NATIVE_IMAGE = (System.getProperty("org.graalvm.nativeimage.imagecode") != null);
|
||||
|
||||
static final ConcurrentReferenceHashMap<Type, Type> cache = new ConcurrentReferenceHashMap<>(256);
|
||||
|
||||
|
||||
@@ -116,7 +109,7 @@ final class SerializableTypeWrapper {
|
||||
// No serializable type wrapping necessary (e.g. for java.lang.Class)
|
||||
return providedType;
|
||||
}
|
||||
if (IN_NATIVE_IMAGE || !Serializable.class.isAssignableFrom(Class.class)) {
|
||||
if (NativeDetector.inNativeImage() || !Serializable.class.isAssignableFrom(Class.class)) {
|
||||
// Let's skip any wrapping attempts if types are generally not serializable in
|
||||
// the current runtime environment (even java.lang.Class itself, e.g. on GraalVM native images)
|
||||
return providedType;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package org.springframework.core;
|
||||
|
||||
import java.security.ProtectionDomain;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Interface to be implemented by a reloading-aware ClassLoader
|
||||
* (e.g. a Groovy-based ClassLoader). Detected for example by
|
||||
@@ -34,10 +38,41 @@ public interface SmartClassLoader {
|
||||
* Determine whether the given class is reloadable (in this ClassLoader).
|
||||
* <p>Typically used to check whether the result may be cached (for this
|
||||
* ClassLoader) or whether it should be reobtained every time.
|
||||
* The default implementation always returns {@code false}.
|
||||
* @param clazz the class to check (usually loaded from this ClassLoader)
|
||||
* @return whether the class should be expected to appear in a reloaded
|
||||
* version (with a different {@code Class} object) later on
|
||||
*/
|
||||
boolean isClassReloadable(Class<?> clazz);
|
||||
default boolean isClassReloadable(Class<?> clazz) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a custom class (typically a CGLIB proxy class) in this class loader.
|
||||
* <p>This is a public equivalent of the protected
|
||||
* {@code defineClass(String, byte[], int, int, ProtectionDomain)} method
|
||||
* in {@link ClassLoader} which is traditionally invoked via reflection.
|
||||
* A concrete implementation in a custom class loader should simply delegate
|
||||
* to that protected method in order to make classloader-specific definitions
|
||||
* publicly available without "illegal access" warnings on JDK 9+:
|
||||
* {@code return defineClass(name, b, 0, b.length, protectionDomain)}.
|
||||
* Note that the JDK 9+ {@code Lookup#defineClass} method does not support
|
||||
* a custom target class loader for the new definition; it rather always
|
||||
* defines the class in the same class loader as the lookup's context class.
|
||||
* @param name the name of the class
|
||||
* @param b the bytes defining the class
|
||||
* @param protectionDomain the protection domain for the class, if any
|
||||
* @return the newly created class
|
||||
* @throws LinkageError in case of a bad class definition
|
||||
* @throws SecurityException in case of an invalid definition attempt
|
||||
* @throws UnsupportedOperationException in case of a custom definition attempt
|
||||
* not being possible (thrown by the default implementation in this interface)
|
||||
* @since 5.3.4
|
||||
* @see ClassLoader#defineClass(String, byte[], int, int, ProtectionDomain)
|
||||
* @see java.lang.invoke.MethodHandles.Lookup#defineClass(byte[])
|
||||
*/
|
||||
default Class<?> publicDefineClass(String name, byte[] b, @Nullable ProtectionDomain protectionDomain) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -38,9 +38,15 @@ import org.springframework.lang.Nullable;
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.2.7
|
||||
* @see org.springframework.core.env.AbstractEnvironment#IGNORE_GETENV_PROPERTY_NAME
|
||||
* @see org.springframework.beans.CachedIntrospectionResults#IGNORE_BEANINFO_PROPERTY_NAME
|
||||
* @see org.springframework.context.index.CandidateComponentsIndexLoader#IGNORE_INDEX
|
||||
* @see org.springframework.core.env.AbstractEnvironment#IGNORE_GETENV_PROPERTY_NAME
|
||||
* @see org.springframework.expression.spel.SpelParserConfiguration#SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME
|
||||
* @see org.springframework.jdbc.core.StatementCreatorUtils#IGNORE_GETPARAMETERTYPE_PROPERTY_NAME
|
||||
* @see org.springframework.jndi.JndiLocatorDelegate#IGNORE_JNDI_PROPERTY_NAME
|
||||
* @see org.springframework.objenesis.SpringObjenesis#IGNORE_OBJENESIS_PROPERTY_NAME
|
||||
* @see org.springframework.test.context.NestedTestConfiguration#ENCLOSING_CONFIGURATION_PROPERTY_NAME
|
||||
* @see org.springframework.test.context.TestConstructor#TEST_CONSTRUCTOR_AUTOWIRE_MODE_PROPERTY_NAME
|
||||
* @see org.springframework.test.context.cache.ContextCache#MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME
|
||||
*/
|
||||
public final class SpringProperties {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.core.codec;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -59,7 +60,7 @@ public interface Decoder<T> {
|
||||
* this type must have been previously passed to the {@link #canDecode}
|
||||
* method and it must have returned {@code true}.
|
||||
* @param mimeType the MIME type associated with the input stream (optional)
|
||||
* @param hints additional information about how to do encode
|
||||
* @param hints additional information about how to do decode
|
||||
* @return the output stream with decoded elements
|
||||
*/
|
||||
Flux<T> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType,
|
||||
@@ -72,7 +73,7 @@ public interface Decoder<T> {
|
||||
* this type must have been previously passed to the {@link #canDecode}
|
||||
* method and it must have returned {@code true}.
|
||||
* @param mimeType the MIME type associated with the input stream (optional)
|
||||
* @param hints additional information about how to do encode
|
||||
* @param hints additional information about how to do decode
|
||||
* @return the output stream with the decoded element
|
||||
*/
|
||||
Mono<T> decodeToMono(Publisher<DataBuffer> inputStream, ResolvableType elementType,
|
||||
@@ -85,7 +86,7 @@ public interface Decoder<T> {
|
||||
* @param buffer the {@code DataBuffer} to decode
|
||||
* @param targetType the expected output type
|
||||
* @param mimeType the MIME type associated with the data
|
||||
* @param hints additional information about how to do encode
|
||||
* @param hints additional information about how to do decode
|
||||
* @return the decoded value, possibly {@code null}
|
||||
* @since 5.2
|
||||
*/
|
||||
@@ -111,8 +112,27 @@ public interface Decoder<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of MIME types this decoder supports.
|
||||
* Return the list of MIME types supported by this Decoder. The list may not
|
||||
* apply to every possible target element type and calls to this method
|
||||
* should typically be guarded via {@link #canDecode(ResolvableType, MimeType)
|
||||
* canDecode(elementType, null)}. The list may also exclude MIME types
|
||||
* supported only for a specific element type. Alternatively, use
|
||||
* {@link #getDecodableMimeTypes(ResolvableType)} for a more precise list.
|
||||
* @return the list of supported MIME types
|
||||
*/
|
||||
List<MimeType> getDecodableMimeTypes();
|
||||
|
||||
/**
|
||||
* Return the list of MIME types supported by this Decoder for the given type
|
||||
* of element. This list may differ from {@link #getDecodableMimeTypes()}
|
||||
* if the Decoder doesn't support the given element type or if it supports
|
||||
* it only for a subset of MIME types.
|
||||
* @param targetType the type of element to check for decoding
|
||||
* @return the list of MIME types supported for the given target type
|
||||
* @since 5.3.4
|
||||
*/
|
||||
default List<MimeType> getDecodableMimeTypes(ResolvableType targetType) {
|
||||
return (canDecode(targetType, null) ? getDecodableMimeTypes() : Collections.emptyList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.core.codec;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -90,8 +91,27 @@ public interface Encoder<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of mime types this encoder supports.
|
||||
* Return the list of MIME types supported by this Encoder. The list may not
|
||||
* apply to every possible target element type and calls to this method should
|
||||
* typically be guarded via {@link #canEncode(ResolvableType, MimeType)
|
||||
* canEncode(elementType, null)}. The list may also exclude MIME types
|
||||
* supported only for a specific element type. Alternatively, use
|
||||
* {@link #getEncodableMimeTypes(ResolvableType)} for a more precise list.
|
||||
* @return the list of supported MIME types
|
||||
*/
|
||||
List<MimeType> getEncodableMimeTypes();
|
||||
|
||||
/**
|
||||
* Return the list of MIME types supported by this Encoder for the given type
|
||||
* of element. This list may differ from the {@link #getEncodableMimeTypes()}
|
||||
* if the Encoder doesn't support the element type or if it supports it only
|
||||
* for a subset of MIME types.
|
||||
* @param elementType the type of element to check for encoding
|
||||
* @return the list of MIME types supported for the given element type
|
||||
* @since 5.3.4
|
||||
*/
|
||||
default List<MimeType> getEncodableMimeTypes(ResolvableType elementType) {
|
||||
return (canEncode(elementType, null) ? getEncodableMimeTypes() : Collections.emptyList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -157,16 +157,20 @@ public final class StringDecoder extends AbstractDataBufferDecoder<String> {
|
||||
int startIndex = buffer.readPosition();
|
||||
int length = (endIndex - startIndex + 1);
|
||||
DataBuffer slice = buffer.retainedSlice(startIndex, length);
|
||||
if (this.stripDelimiter) {
|
||||
slice.writePosition(slice.writePosition() - matcher.delimiter().length);
|
||||
}
|
||||
result = (result != null ? result : new ArrayList<>());
|
||||
if (chunks.isEmpty()) {
|
||||
if (this.stripDelimiter) {
|
||||
slice.writePosition(slice.writePosition() - matcher.delimiter().length);
|
||||
}
|
||||
result.add(slice);
|
||||
}
|
||||
else {
|
||||
chunks.add(slice);
|
||||
result.add(buffer.factory().join(chunks));
|
||||
DataBuffer joined = buffer.factory().join(chunks);
|
||||
if (this.stripDelimiter) {
|
||||
joined.writePosition(joined.writePosition() - matcher.delimiter().length);
|
||||
}
|
||||
result.add(joined);
|
||||
chunks.clear();
|
||||
}
|
||||
buffer.readPosition(endIndex + 1);
|
||||
|
||||
+67
-14
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -107,10 +107,9 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||
|
||||
private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
|
||||
|
||||
private final MutablePropertySources propertySources = new MutablePropertySources();
|
||||
private final MutablePropertySources propertySources;
|
||||
|
||||
private final ConfigurablePropertyResolver propertyResolver =
|
||||
new PropertySourcesPropertyResolver(this.propertySources);
|
||||
private final ConfigurablePropertyResolver propertyResolver;
|
||||
|
||||
|
||||
/**
|
||||
@@ -121,9 +120,44 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||
* @see #customizePropertySources(MutablePropertySources)
|
||||
*/
|
||||
public AbstractEnvironment() {
|
||||
customizePropertySources(this.propertySources);
|
||||
this(new MutablePropertySources());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code Environment} instance with a specific
|
||||
* {@link MutablePropertySources} instance, calling back to
|
||||
* {@link #customizePropertySources(MutablePropertySources)} during
|
||||
* construction to allow subclasses to contribute or manipulate
|
||||
* {@link PropertySource} instances as appropriate.
|
||||
* @since 5.3.4
|
||||
* @see #customizePropertySources(MutablePropertySources)
|
||||
*/
|
||||
protected AbstractEnvironment(MutablePropertySources propertySources) {
|
||||
this.propertySources = propertySources;
|
||||
this.propertyResolver = createPropertyResolver(propertySources);
|
||||
customizePropertySources(propertySources);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Factory method used to create the {@link ConfigurablePropertyResolver}
|
||||
* instance used by the Environment.
|
||||
* @since 5.3.4
|
||||
* @see #getPropertyResolver()
|
||||
*/
|
||||
protected ConfigurablePropertyResolver createPropertyResolver(MutablePropertySources propertySources) {
|
||||
return new PropertySourcesPropertyResolver(propertySources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link ConfigurablePropertyResolver} being used by the
|
||||
* {@link Environment}.
|
||||
* @since 5.3.4
|
||||
* @see #createPropertyResolver(MutablePropertySources)
|
||||
*/
|
||||
protected final ConfigurablePropertyResolver getPropertyResolver() {
|
||||
return this.propertyResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize the set of {@link PropertySource} objects to be searched by this
|
||||
@@ -227,15 +261,15 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||
/**
|
||||
* Return the set of active profiles as explicitly set through
|
||||
* {@link #setActiveProfiles} or if the current set of active profiles
|
||||
* is empty, check for the presence of the {@value #ACTIVE_PROFILES_PROPERTY_NAME}
|
||||
* property and assign its value to the set of active profiles.
|
||||
* is empty, check for the presence of {@link #doGetActiveProfilesProperty()}
|
||||
* and assign its value to the set of active profiles.
|
||||
* @see #getActiveProfiles()
|
||||
* @see #ACTIVE_PROFILES_PROPERTY_NAME
|
||||
* @see #doGetActiveProfilesProperty()
|
||||
*/
|
||||
protected Set<String> doGetActiveProfiles() {
|
||||
synchronized (this.activeProfiles) {
|
||||
if (this.activeProfiles.isEmpty()) {
|
||||
String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
|
||||
String profiles = doGetActiveProfilesProperty();
|
||||
if (StringUtils.hasText(profiles)) {
|
||||
setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
|
||||
StringUtils.trimAllWhitespace(profiles)));
|
||||
@@ -245,6 +279,16 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property value for the active profiles.
|
||||
* @since 5.3.4
|
||||
* @see #ACTIVE_PROFILES_PROPERTY_NAME
|
||||
*/
|
||||
@Nullable
|
||||
protected String doGetActiveProfilesProperty() {
|
||||
return getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setActiveProfiles(String... profiles) {
|
||||
Assert.notNull(profiles, "Profile array must not be null");
|
||||
@@ -282,18 +326,17 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||
* Return the set of default profiles explicitly set via
|
||||
* {@link #setDefaultProfiles(String...)} or if the current set of default profiles
|
||||
* consists only of {@linkplain #getReservedDefaultProfiles() reserved default
|
||||
* profiles}, then check for the presence of the
|
||||
* {@value #DEFAULT_PROFILES_PROPERTY_NAME} property and assign its value (if any)
|
||||
* to the set of default profiles.
|
||||
* profiles}, then check for the presence of {@link #doGetActiveProfilesProperty()}
|
||||
* and assign its value (if any) to the set of default profiles.
|
||||
* @see #AbstractEnvironment()
|
||||
* @see #getDefaultProfiles()
|
||||
* @see #DEFAULT_PROFILES_PROPERTY_NAME
|
||||
* @see #getReservedDefaultProfiles()
|
||||
* @see #doGetDefaultProfilesProperty()
|
||||
*/
|
||||
protected Set<String> doGetDefaultProfiles() {
|
||||
synchronized (this.defaultProfiles) {
|
||||
if (this.defaultProfiles.equals(getReservedDefaultProfiles())) {
|
||||
String profiles = getProperty(DEFAULT_PROFILES_PROPERTY_NAME);
|
||||
String profiles = doGetDefaultProfilesProperty();
|
||||
if (StringUtils.hasText(profiles)) {
|
||||
setDefaultProfiles(StringUtils.commaDelimitedListToStringArray(
|
||||
StringUtils.trimAllWhitespace(profiles)));
|
||||
@@ -303,6 +346,16 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property value for the default profiles.
|
||||
* @since 5.3.4
|
||||
* @see #DEFAULT_PROFILES_PROPERTY_NAME
|
||||
*/
|
||||
@Nullable
|
||||
protected String doGetDefaultProfilesProperty() {
|
||||
return getProperty(DEFAULT_PROFILES_PROPERTY_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the set of profiles to be made active by default if no other profiles
|
||||
* are explicitly made active through {@link #setActiveProfiles}.
|
||||
|
||||
+9
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -60,6 +60,14 @@ public class StandardEnvironment extends AbstractEnvironment {
|
||||
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
|
||||
|
||||
|
||||
public StandardEnvironment() {
|
||||
}
|
||||
|
||||
protected StandardEnvironment(MutablePropertySources propertySources) {
|
||||
super(propertySources);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Customize the set of property sources with those appropriate for any standard
|
||||
* Java environment:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,15 +20,15 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
|
||||
/**
|
||||
* Strategy interface for loading resources (e.. class path or file system
|
||||
* Strategy interface for loading resources (e.g., class path or file system
|
||||
* resources). An {@link org.springframework.context.ApplicationContext}
|
||||
* is required to provide this functionality, plus extended
|
||||
* is required to provide this functionality plus extended
|
||||
* {@link org.springframework.core.io.support.ResourcePatternResolver} support.
|
||||
*
|
||||
* <p>{@link DefaultResourceLoader} is a standalone implementation that is
|
||||
* usable outside an ApplicationContext, also used by {@link ResourceEditor}.
|
||||
* usable outside an ApplicationContext and is also used by {@link ResourceEditor}.
|
||||
*
|
||||
* <p>Bean properties of type Resource and Resource array can be populated
|
||||
* <p>Bean properties of type {@code Resource} and {@code Resource[]} can be populated
|
||||
* from Strings when running in an ApplicationContext, using the particular
|
||||
* context's resource loading strategy.
|
||||
*
|
||||
@@ -46,7 +46,7 @@ public interface ResourceLoader {
|
||||
|
||||
|
||||
/**
|
||||
* Return a Resource handle for the specified resource location.
|
||||
* Return a {@code Resource} handle for the specified resource location.
|
||||
* <p>The handle should always be a reusable resource descriptor,
|
||||
* allowing for multiple {@link Resource#getInputStream()} calls.
|
||||
* <p><ul>
|
||||
@@ -56,10 +56,10 @@ public interface ResourceLoader {
|
||||
* (This will be implementation-specific, typically provided by an
|
||||
* ApplicationContext implementation.)
|
||||
* </ul>
|
||||
* <p>Note that a Resource handle does not imply an existing resource;
|
||||
* <p>Note that a {@code Resource} handle does not imply an existing resource;
|
||||
* you need to invoke {@link Resource#exists} to check for existence.
|
||||
* @param location the resource location
|
||||
* @return a corresponding Resource handle (never {@code null})
|
||||
* @return a corresponding {@code Resource} handle (never {@code null})
|
||||
* @see #CLASSPATH_URL_PREFIX
|
||||
* @see Resource#exists()
|
||||
* @see Resource#getInputStream()
|
||||
@@ -67,12 +67,12 @@ public interface ResourceLoader {
|
||||
Resource getResource(String location);
|
||||
|
||||
/**
|
||||
* Expose the ClassLoader used by this ResourceLoader.
|
||||
* <p>Clients which need to access the ClassLoader directly can do so
|
||||
* in a uniform manner with the ResourceLoader, rather than relying
|
||||
* on the thread context ClassLoader.
|
||||
* @return the ClassLoader
|
||||
* (only {@code null} if even the system ClassLoader isn't accessible)
|
||||
* Expose the {@link ClassLoader} used by this {@code ResourceLoader}.
|
||||
* <p>Clients which need to access the {@code ClassLoader} directly can do so
|
||||
* in a uniform manner with the {@code ResourceLoader}, rather than relying
|
||||
* on the thread context {@code ClassLoader}.
|
||||
* @return the {@code ClassLoader}
|
||||
* (only {@code null} if even the system {@code ClassLoader} isn't accessible)
|
||||
* @see org.springframework.util.ClassUtils#getDefaultClassLoader()
|
||||
* @see org.springframework.util.ClassUtils#forName(String, ClassLoader)
|
||||
*/
|
||||
|
||||
+11
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2007 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,17 +23,18 @@ import org.springframework.core.io.ResourceLoader;
|
||||
|
||||
/**
|
||||
* Strategy interface for resolving a location pattern (for example,
|
||||
* an Ant-style path pattern) into Resource objects.
|
||||
* an Ant-style path pattern) into {@link Resource} objects.
|
||||
*
|
||||
* <p>This is an extension to the {@link org.springframework.core.io.ResourceLoader}
|
||||
* interface. A passed-in ResourceLoader (for example, an
|
||||
* interface. A passed-in {@code ResourceLoader} (for example, an
|
||||
* {@link org.springframework.context.ApplicationContext} passed in via
|
||||
* {@link org.springframework.context.ResourceLoaderAware} when running in a context)
|
||||
* can be checked whether it implements this extended interface too.
|
||||
*
|
||||
* <p>{@link PathMatchingResourcePatternResolver} is a standalone implementation
|
||||
* that is usable outside an ApplicationContext, also used by
|
||||
* {@link ResourceArrayPropertyEditor} for populating Resource array bean properties.
|
||||
* that is usable outside an {@code ApplicationContext}, also used by
|
||||
* {@link ResourceArrayPropertyEditor} for populating {@code Resource} array bean
|
||||
* properties.
|
||||
*
|
||||
* <p>Can be used with any sort of location pattern (e.g. "/WEB-INF/*-context.xml"):
|
||||
* Input patterns have to match the strategy implementation. This interface just
|
||||
@@ -42,7 +43,8 @@ import org.springframework.core.io.ResourceLoader;
|
||||
* <p>This interface also suggests a new resource prefix "classpath*:" for all
|
||||
* matching resources from the class path. Note that the resource location is
|
||||
* expected to be a path without placeholders in this case (e.g. "/beans.xml");
|
||||
* JAR files or classes directories can contain multiple files of the same name.
|
||||
* JAR files or different directories in the class path can contain multiple files
|
||||
* of the same name.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.0.2
|
||||
@@ -55,7 +57,7 @@ public interface ResourcePatternResolver extends ResourceLoader {
|
||||
|
||||
/**
|
||||
* Pseudo URL prefix for all matching resources from the class path: "classpath*:"
|
||||
* This differs from ResourceLoader's classpath URL prefix in that it
|
||||
* <p>This differs from ResourceLoader's classpath URL prefix in that it
|
||||
* retrieves all matching resources for a given name (e.g. "/beans.xml"),
|
||||
* for example in the root of all deployed JAR files.
|
||||
* @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX
|
||||
@@ -63,12 +65,12 @@ public interface ResourcePatternResolver extends ResourceLoader {
|
||||
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
|
||||
|
||||
/**
|
||||
* Resolve the given location pattern into Resource objects.
|
||||
* Resolve the given location pattern into {@code Resource} objects.
|
||||
* <p>Overlapping resource entries that point to the same physical
|
||||
* resource should be avoided, as far as possible. The result should
|
||||
* have set semantics.
|
||||
* @param locationPattern the location pattern to resolve
|
||||
* @return the corresponding Resource objects
|
||||
* @return the corresponding {@code Resource} objects
|
||||
* @throws IOException in case of I/O errors
|
||||
*/
|
||||
Resource[] getResources(String locationPattern) throws IOException;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -74,8 +74,8 @@ public abstract class ClassUtils {
|
||||
/** The path separator character: {@code '/'}. */
|
||||
private static final char PATH_SEPARATOR = '/';
|
||||
|
||||
/** The inner class separator character: {@code '$'}. */
|
||||
private static final char INNER_CLASS_SEPARATOR = '$';
|
||||
/** The nested class separator character: {@code '$'}. */
|
||||
private static final char NESTED_CLASS_SEPARATOR = '$';
|
||||
|
||||
/** The CGLIB class separator: {@code "$$"}. */
|
||||
public static final String CGLIB_CLASS_SEPARATOR = "$$";
|
||||
@@ -232,7 +232,7 @@ public abstract class ClassUtils {
|
||||
/**
|
||||
* Replacement for {@code Class.forName()} that also returns Class instances
|
||||
* for primitives (e.g. "int") and array class names (e.g. "String[]").
|
||||
* Furthermore, it is also capable of resolving inner class names in Java source
|
||||
* Furthermore, it is also capable of resolving nested class names in Java source
|
||||
* style (e.g. "java.lang.Thread.State" instead of "java.lang.Thread$State").
|
||||
* @param name the name of the Class
|
||||
* @param classLoader the class loader to use
|
||||
@@ -286,10 +286,10 @@ public abstract class ClassUtils {
|
||||
catch (ClassNotFoundException ex) {
|
||||
int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
|
||||
if (lastDotIndex != -1) {
|
||||
String innerClassName =
|
||||
name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
|
||||
String nestedClassName =
|
||||
name.substring(0, lastDotIndex) + NESTED_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
|
||||
try {
|
||||
return Class.forName(innerClassName, false, clToUse);
|
||||
return Class.forName(nestedClassName, false, clToUse);
|
||||
}
|
||||
catch (ClassNotFoundException ex2) {
|
||||
// Swallow - let original exception get through
|
||||
@@ -953,7 +953,7 @@ public abstract class ClassUtils {
|
||||
nameEndIndex = className.length();
|
||||
}
|
||||
String shortName = className.substring(lastDotIndex + 1, nameEndIndex);
|
||||
shortName = shortName.replace(INNER_CLASS_SEPARATOR, PACKAGE_SEPARATOR);
|
||||
shortName = shortName.replace(NESTED_CLASS_SEPARATOR, PACKAGE_SEPARATOR);
|
||||
return shortName;
|
||||
}
|
||||
|
||||
@@ -968,7 +968,7 @@ public abstract class ClassUtils {
|
||||
|
||||
/**
|
||||
* Return the short string name of a Java class in uncapitalized JavaBeans
|
||||
* property format. Strips the outer class name in case of an inner class.
|
||||
* property format. Strips the outer class name in case of a nested class.
|
||||
* @param clazz the class
|
||||
* @return the short name rendered in a standard JavaBeans property format
|
||||
* @see java.beans.Introspector#decapitalize(String)
|
||||
|
||||
@@ -125,10 +125,10 @@ class StringDecoderTests extends AbstractDecoderTests<StringDecoder> {
|
||||
);
|
||||
|
||||
testDecode(input, String.class, step -> step
|
||||
.expectNext("")
|
||||
.expectNext("").as("1st")
|
||||
.expectNext("abc")
|
||||
.expectNext("defghi")
|
||||
.expectNext("")
|
||||
.expectNext("").as("2nd")
|
||||
.expectNext("jklmno")
|
||||
.expectNext("pqr")
|
||||
.expectNext("stuvwxyz")
|
||||
@@ -136,6 +136,21 @@ class StringDecoderTests extends AbstractDecoderTests<StringDecoder> {
|
||||
.verify());
|
||||
}
|
||||
|
||||
@Test
|
||||
void decodeNewlinesAcrossBuffers() {
|
||||
Flux<DataBuffer> input = Flux.just(
|
||||
stringBuffer("\r"),
|
||||
stringBuffer("\n"),
|
||||
stringBuffer("xyz")
|
||||
);
|
||||
|
||||
testDecode(input, String.class, step -> step
|
||||
.expectNext("")
|
||||
.expectNext("xyz")
|
||||
.expectComplete()
|
||||
.verify());
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxInMemoryLimit() {
|
||||
Flux<DataBuffer> input = Flux.just(
|
||||
|
||||
+63
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,10 +18,14 @@ package org.springframework.core.env;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
@@ -32,8 +36,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*/
|
||||
class CustomEnvironmentTests {
|
||||
|
||||
// -- tests relating to customizing reserved default profiles ----------------------
|
||||
|
||||
@Test
|
||||
void control() {
|
||||
Environment env = new AbstractEnvironment() { };
|
||||
@@ -105,10 +107,66 @@ class CustomEnvironmentTests {
|
||||
assertThat(env.acceptsProfiles(Profiles.of("a1 | a2"))).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withNoProfileProperties() {
|
||||
ConfigurableEnvironment env = new AbstractEnvironment() {
|
||||
@Override
|
||||
@Nullable
|
||||
protected String doGetActiveProfilesProperty() {
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
@Nullable
|
||||
protected String doGetDefaultProfilesProperty() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
Map<String, Object> values = new LinkedHashMap<>();
|
||||
values.put(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "a,b,c");
|
||||
values.put(AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME, "d,e,f");
|
||||
PropertySource<?> propertySource = new MapPropertySource("test", values);
|
||||
env.getPropertySources().addFirst(propertySource);
|
||||
assertThat(env.getActiveProfiles()).isEmpty();
|
||||
assertThat(env.getDefaultProfiles()).containsExactly(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withCustomMutablePropertySources() {
|
||||
class CustomMutablePropertySources extends MutablePropertySources {}
|
||||
MutablePropertySources propertySources = new CustomMutablePropertySources();
|
||||
ConfigurableEnvironment env = new AbstractEnvironment(propertySources) {};
|
||||
assertThat(env.getPropertySources()).isInstanceOf(CustomMutablePropertySources.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withCustomPropertyResolver() {
|
||||
class CustomPropertySourcesPropertyResolver extends PropertySourcesPropertyResolver {
|
||||
public CustomPropertySourcesPropertyResolver(PropertySources propertySources) {
|
||||
super(propertySources);
|
||||
}
|
||||
@Override
|
||||
@Nullable
|
||||
public String getProperty(String key) {
|
||||
return super.getProperty(key)+"-test";
|
||||
}
|
||||
}
|
||||
|
||||
ConfigurableEnvironment env = new AbstractEnvironment() {
|
||||
@Override
|
||||
protected ConfigurablePropertyResolver createPropertyResolver(MutablePropertySources propertySources) {
|
||||
return new CustomPropertySourcesPropertyResolver(propertySources);
|
||||
}
|
||||
};
|
||||
|
||||
Map<String, Object> values = new LinkedHashMap<>();
|
||||
values.put("spring", "framework");
|
||||
PropertySource<?> propertySource = new MapPropertySource("test", values);
|
||||
env.getPropertySources().addFirst(propertySource);
|
||||
assertThat(env.getProperty("spring")).isEqualTo("framework-test");
|
||||
}
|
||||
|
||||
private Profiles defaultProfile() {
|
||||
return Profiles.of(AbstractEnvironment.RESERVED_DEFAULT_PROFILE_NAME);
|
||||
}
|
||||
|
||||
|
||||
// -- tests relating to customizing property sources -------------------------------
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -33,6 +33,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
@@ -59,14 +60,6 @@ class ClassUtilsTests {
|
||||
private final ClassLoader classLoader = getClass().getClassLoader();
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void clearStatics() {
|
||||
InnerClass.noArgCalled = false;
|
||||
InnerClass.argCalled = false;
|
||||
InnerClass.overloadedCalled = false;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void isPresent() {
|
||||
assertThat(ClassUtils.isPresent("java.lang.String", classLoader)).isTrue();
|
||||
@@ -88,6 +81,12 @@ class ClassUtilsTests {
|
||||
assertThat(ClassUtils.forName("[[[S", classLoader)).isEqualTo(short[][][].class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void forNameWithNestedType() throws ClassNotFoundException {
|
||||
assertThat(ClassUtils.forName("org.springframework.util.ClassUtilsTests$NestedClass", classLoader)).isEqualTo(NestedClass.class);
|
||||
assertThat(ClassUtils.forName("org.springframework.util.ClassUtilsTests.NestedClass", classLoader)).isEqualTo(NestedClass.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void forNameWithPrimitiveClasses() throws ClassNotFoundException {
|
||||
assertThat(ClassUtils.forName("boolean", classLoader)).isEqualTo(boolean.class);
|
||||
@@ -143,11 +142,11 @@ class ClassUtilsTests {
|
||||
assertThat(ClassUtils.isCacheSafe(String.class, childLoader1)).isTrue();
|
||||
assertThat(ClassUtils.isCacheSafe(String.class, childLoader2)).isTrue();
|
||||
assertThat(ClassUtils.isCacheSafe(String.class, childLoader3)).isTrue();
|
||||
assertThat(ClassUtils.isCacheSafe(InnerClass.class, null)).isFalse();
|
||||
assertThat(ClassUtils.isCacheSafe(InnerClass.class, classLoader)).isTrue();
|
||||
assertThat(ClassUtils.isCacheSafe(InnerClass.class, childLoader1)).isTrue();
|
||||
assertThat(ClassUtils.isCacheSafe(InnerClass.class, childLoader2)).isTrue();
|
||||
assertThat(ClassUtils.isCacheSafe(InnerClass.class, childLoader3)).isTrue();
|
||||
assertThat(ClassUtils.isCacheSafe(NestedClass.class, null)).isFalse();
|
||||
assertThat(ClassUtils.isCacheSafe(NestedClass.class, classLoader)).isTrue();
|
||||
assertThat(ClassUtils.isCacheSafe(NestedClass.class, childLoader1)).isTrue();
|
||||
assertThat(ClassUtils.isCacheSafe(NestedClass.class, childLoader2)).isTrue();
|
||||
assertThat(ClassUtils.isCacheSafe(NestedClass.class, childLoader3)).isTrue();
|
||||
assertThat(ClassUtils.isCacheSafe(composite, null)).isFalse();
|
||||
assertThat(ClassUtils.isCacheSafe(composite, classLoader)).isFalse();
|
||||
assertThat(ClassUtils.isCacheSafe(composite, childLoader1)).isTrue();
|
||||
@@ -209,9 +208,9 @@ class ClassUtilsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getShortNameForInnerClass() {
|
||||
String className = ClassUtils.getShortName(InnerClass.class);
|
||||
assertThat(className).as("Class name did not match").isEqualTo("ClassUtilsTests.InnerClass");
|
||||
void getShortNameForNestedClass() {
|
||||
String className = ClassUtils.getShortName(NestedClass.class);
|
||||
assertThat(className).as("Class name did not match").isEqualTo("ClassUtilsTests.NestedClass");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -299,27 +298,6 @@ class ClassUtilsTests {
|
||||
assertThat(ClassUtils.hasAtLeastOneMethodWithName(TestObject.class, "setAge")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void noArgsStaticMethod() throws IllegalAccessException, InvocationTargetException {
|
||||
Method method = ClassUtils.getStaticMethod(InnerClass.class, "staticMethod");
|
||||
method.invoke(null, (Object[]) null);
|
||||
assertThat(InnerClass.noArgCalled).as("no argument method was not invoked.").isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void argsStaticMethod() throws IllegalAccessException, InvocationTargetException {
|
||||
Method method = ClassUtils.getStaticMethod(InnerClass.class, "argStaticMethod", String.class);
|
||||
method.invoke(null, "test");
|
||||
assertThat(InnerClass.argCalled).as("argument method was not invoked.").isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void overloadedStaticMethod() throws IllegalAccessException, InvocationTargetException {
|
||||
Method method = ClassUtils.getStaticMethod(InnerClass.class, "staticMethod", String.class);
|
||||
method.invoke(null, "test");
|
||||
assertThat(InnerClass.overloadedCalled).as("argument method was not invoked.").isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAssignable() {
|
||||
assertThat(ClassUtils.isAssignable(Object.class, Object.class)).isTrue();
|
||||
@@ -431,6 +409,40 @@ class ClassUtilsTests {
|
||||
}
|
||||
|
||||
|
||||
@Nested
|
||||
class GetStaticMethodTests {
|
||||
|
||||
@BeforeEach
|
||||
void clearStatics() {
|
||||
NestedClass.noArgCalled = false;
|
||||
NestedClass.argCalled = false;
|
||||
NestedClass.overloadedCalled = false;
|
||||
}
|
||||
|
||||
@Test
|
||||
void noArgsStaticMethod() throws IllegalAccessException, InvocationTargetException {
|
||||
Method method = ClassUtils.getStaticMethod(NestedClass.class, "staticMethod");
|
||||
method.invoke(null, (Object[]) null);
|
||||
assertThat(NestedClass.noArgCalled).as("no argument method was not invoked.").isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void argsStaticMethod() throws IllegalAccessException, InvocationTargetException {
|
||||
Method method = ClassUtils.getStaticMethod(NestedClass.class, "argStaticMethod", String.class);
|
||||
method.invoke(null, "test");
|
||||
assertThat(NestedClass.argCalled).as("argument method was not invoked.").isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void overloadedStaticMethod() throws IllegalAccessException, InvocationTargetException {
|
||||
Method method = ClassUtils.getStaticMethod(NestedClass.class, "staticMethod", String.class);
|
||||
method.invoke(null, "test");
|
||||
assertThat(NestedClass.overloadedCalled).as("argument method was not invoked.").isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@ValueSource(classes = { Boolean.class, Character.class, Byte.class, Short.class,
|
||||
@@ -445,7 +457,7 @@ class ClassUtilsTests {
|
||||
@interface PrimitiveTypes {
|
||||
}
|
||||
|
||||
public static class InnerClass {
|
||||
public static class NestedClass {
|
||||
|
||||
static boolean noArgCalled;
|
||||
static boolean argCalled;
|
||||
|
||||
+7
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -30,10 +30,14 @@ import org.springframework.lang.Nullable;
|
||||
*/
|
||||
public class SpelParserConfiguration {
|
||||
|
||||
/** System property to configure the default compiler mode for SpEL expression parsers: {@value}. */
|
||||
public static final String SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME = "spring.expression.compiler.mode";
|
||||
|
||||
|
||||
private static final SpelCompilerMode defaultCompilerMode;
|
||||
|
||||
static {
|
||||
String compilerMode = SpringProperties.getProperty("spring.expression.compiler.mode");
|
||||
String compilerMode = SpringProperties.getProperty(SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME);
|
||||
defaultCompilerMode = (compilerMode != null ?
|
||||
SpelCompilerMode.valueOf(compilerMode.toUpperCase()) : SpelCompilerMode.OFF);
|
||||
}
|
||||
@@ -107,7 +111,7 @@ public class SpelParserConfiguration {
|
||||
|
||||
|
||||
/**
|
||||
* Return the configuration mode for parsers using this configuration object.
|
||||
* Return the compiler mode for parsers using this configuration object.
|
||||
*/
|
||||
public SpelCompilerMode getCompilerMode() {
|
||||
return this.compilerMode;
|
||||
|
||||
+10
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -39,6 +39,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
*/
|
||||
public class GenericCallMetaDataProvider implements CallMetaDataProvider {
|
||||
@@ -414,8 +415,15 @@ public class GenericCallMetaDataProvider implements CallMetaDataProvider {
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Error while retrieving meta-data for procedure columns: " + ex);
|
||||
logger.warn("Error while retrieving meta-data for procedure columns. " +
|
||||
"Consider declaring explicit parameters -- for example, via SimpleJdbcCall#addDeclaredParameter().",
|
||||
ex);
|
||||
}
|
||||
// Although we could invoke `this.callParameterMetaData.clear()` so that
|
||||
// we don't retain a partial list of column names (like we do in
|
||||
// GenericTableMetaDataProvider.processTableColumns(...)), we choose
|
||||
// not to do that here, since invocation of the stored procedure will
|
||||
// likely fail anyway with an incorrect argument list.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -39,6 +39,7 @@ import org.springframework.lang.Nullable;
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
*/
|
||||
public class GenericTableMetaDataProvider implements TableMetaDataProvider {
|
||||
@@ -422,8 +423,12 @@ public class GenericTableMetaDataProvider implements TableMetaDataProvider {
|
||||
}
|
||||
catch (SQLException ex) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Error while retrieving meta-data for table columns: " + ex.getMessage());
|
||||
logger.warn("Error while retrieving meta-data for table columns. " +
|
||||
"Consider specifying explicit column names -- for example, via SimpleJdbcInsert#usingColumns().",
|
||||
ex);
|
||||
}
|
||||
// Clear the metadata so that we don't retain a partial list of column names
|
||||
this.tableParameterMetaData.clear();
|
||||
}
|
||||
finally {
|
||||
JdbcUtils.closeResultSet(tableColumns);
|
||||
@@ -432,7 +437,7 @@ public class GenericTableMetaDataProvider implements TableMetaDataProvider {
|
||||
|
||||
|
||||
/**
|
||||
* Inner class representing table meta-data.
|
||||
* Class representing table meta-data.
|
||||
*/
|
||||
private static class TableMetaData {
|
||||
|
||||
|
||||
+8
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -43,6 +43,7 @@ import org.springframework.util.CollectionUtils;
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
*/
|
||||
public class TableMetaDataContext {
|
||||
@@ -302,8 +303,12 @@ public class TableMetaDataContext {
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new InvalidDataAccessApiUsageException("Unable to locate columns for table '" +
|
||||
getTableName() + "' so an insert statement can't be generated");
|
||||
String message = "Unable to locate columns for table '" + getTableName()
|
||||
+ "' so an insert statement can't be generated.";
|
||||
if (isAccessTableColumnMetaData()) {
|
||||
message += " Consider specifying explicit column names -- for example, via SimpleJdbcInsert#usingColumns().";
|
||||
}
|
||||
throw new InvalidDataAccessApiUsageException(message);
|
||||
}
|
||||
}
|
||||
String params = String.join(", ", Collections.nCopies(columnCount, "?"));
|
||||
|
||||
+5
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -51,7 +51,6 @@ public final class TableMetaDataProviderFactory {
|
||||
try {
|
||||
return JdbcUtils.extractDatabaseMetaData(dataSource, databaseMetaData -> {
|
||||
String databaseProductName = JdbcUtils.commonDatabaseName(databaseMetaData.getDatabaseProductName());
|
||||
boolean accessTableColumnMetaData = context.isAccessTableColumnMetaData();
|
||||
TableMetaDataProvider provider;
|
||||
|
||||
if ("Oracle".equals(databaseProductName)) {
|
||||
@@ -70,15 +69,17 @@ public final class TableMetaDataProviderFactory {
|
||||
else {
|
||||
provider = new GenericTableMetaDataProvider(databaseMetaData);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Using " + provider.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
provider.initializeWithMetaData(databaseMetaData);
|
||||
if (accessTableColumnMetaData) {
|
||||
|
||||
if (context.isAccessTableColumnMetaData()) {
|
||||
provider.initializeWithTableColumnMetaData(databaseMetaData,
|
||||
context.getCatalogName(), context.getSchemaName(), context.getTableName());
|
||||
}
|
||||
|
||||
return provider;
|
||||
});
|
||||
}
|
||||
|
||||
+10
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -36,6 +36,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Juergen Hoeller
|
||||
* @author Yanming Zhou
|
||||
* @since 2.0
|
||||
*/
|
||||
public abstract class NamedParameterUtils {
|
||||
@@ -346,8 +347,14 @@ public abstract class NamedParameterUtils {
|
||||
String paramName = paramNames.get(i);
|
||||
try {
|
||||
SqlParameter param = findParameter(declaredParams, paramName, i);
|
||||
paramArray[i] = (param != null ? new SqlParameterValue(param, paramSource.getValue(paramName)) :
|
||||
SqlParameterSourceUtils.getTypedValue(paramSource, paramName));
|
||||
Object paramValue = paramSource.getValue(paramName);
|
||||
if (paramValue instanceof SqlParameterValue) {
|
||||
paramArray[i] = paramValue;
|
||||
}
|
||||
else {
|
||||
paramArray[i] = (param != null ? new SqlParameterValue(param, paramValue) :
|
||||
SqlParameterSourceUtils.getTypedValue(paramSource, paramName));
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -347,7 +347,7 @@ public abstract class AbstractJdbcCall {
|
||||
/**
|
||||
* Check whether this operation has been compiled already;
|
||||
* lazily compile it if not already compiled.
|
||||
* <p>Automatically called by {@code doExecute}.
|
||||
* <p>Automatically called by all {@code doExecute(...)} methods.
|
||||
*/
|
||||
protected void checkCompiled() {
|
||||
if (!isCompiled()) {
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -70,7 +70,7 @@ public abstract class AbstractJdbcInsert {
|
||||
/** Context used to retrieve and manage database meta-data. */
|
||||
private final TableMetaDataContext tableMetaDataContext = new TableMetaDataContext();
|
||||
|
||||
/** List of columns objects to be used in insert statement. */
|
||||
/** List of column names to be used in insert statement. */
|
||||
private final List<String> declaredColumns = new ArrayList<>();
|
||||
|
||||
/** The names of the columns holding the generated key. */
|
||||
@@ -301,7 +301,7 @@ public abstract class AbstractJdbcInsert {
|
||||
/**
|
||||
* Check whether this operation has been compiled already;
|
||||
* lazily compile it if not already compiled.
|
||||
* <p>Automatically called by {@code validateParameters}.
|
||||
* <p>Automatically called by all {@code doExecute*(...)} methods.
|
||||
*/
|
||||
protected void checkCompiled() {
|
||||
if (!isCompiled()) {
|
||||
@@ -312,7 +312,7 @@ public abstract class AbstractJdbcInsert {
|
||||
|
||||
/**
|
||||
* Method to check whether we are allowed to make any configuration changes at this time.
|
||||
* If the class has been compiled, then no further changes to the configuration are allowed.
|
||||
* <p>If the class has been compiled, then no further changes to the configuration are allowed.
|
||||
*/
|
||||
protected void checkIfConfigurationModificationIsAllowed() {
|
||||
if (isCompiled()) {
|
||||
|
||||
+3
-3
@@ -26,7 +26,7 @@ package org.springframework.jdbc.core.namedparam
|
||||
* @since 5.0
|
||||
*
|
||||
*/
|
||||
operator fun MapSqlParameterSource.set(paramName: String, value: Any) {
|
||||
operator fun MapSqlParameterSource.set(paramName: String, value: Any?) {
|
||||
this.addValue(paramName, value)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ operator fun MapSqlParameterSource.set(paramName: String, value: Any) {
|
||||
* @since 5.0
|
||||
*
|
||||
*/
|
||||
operator fun MapSqlParameterSource.set(paramName: String, sqlType: Int, value: Any) {
|
||||
operator fun MapSqlParameterSource.set(paramName: String, sqlType: Int, value: Any?) {
|
||||
this.addValue(paramName, value, sqlType)
|
||||
}
|
||||
|
||||
@@ -54,6 +54,6 @@ operator fun MapSqlParameterSource.set(paramName: String, sqlType: Int, value: A
|
||||
* @since 5.0
|
||||
*
|
||||
*/
|
||||
operator fun MapSqlParameterSource.set(paramName: String, sqlType: Int, typeName: String, value: Any) {
|
||||
operator fun MapSqlParameterSource.set(paramName: String, sqlType: Int, typeName: String, value: Any?) {
|
||||
this.addValue(paramName, value, sqlType, typeName)
|
||||
}
|
||||
|
||||
+17
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,6 +23,7 @@ import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.jdbc.core.SqlParameterValue;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@@ -32,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
* @author Juergen Hoeller
|
||||
* @author Rick Evans
|
||||
* @author Artur Geraschenko
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
public class NamedParameterUtilsTests {
|
||||
|
||||
@@ -96,6 +98,20 @@ public class NamedParameterUtilsTests {
|
||||
.buildSqlTypeArray(NamedParameterUtils.parseSqlStatement("xxx :a :b :c xx :a :b"), namedParams)[4]).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertSqlParameterValueToArray() {
|
||||
SqlParameterValue sqlParameterValue = new SqlParameterValue(2, "b");
|
||||
Map<String, Object> paramMap = new HashMap<>();
|
||||
paramMap.put("a", "a");
|
||||
paramMap.put("b", sqlParameterValue);
|
||||
paramMap.put("c", "c");
|
||||
assertThat(NamedParameterUtils.buildValueArray("xxx :a :b :c xx :a :b", paramMap)[4]).isSameAs(sqlParameterValue);
|
||||
MapSqlParameterSource namedParams = new MapSqlParameterSource();
|
||||
namedParams.addValue("a", "a", 1).addValue("b", sqlParameterValue).addValue("c", "c", 3);
|
||||
assertThat(NamedParameterUtils
|
||||
.buildValueArray(NamedParameterUtils.parseSqlStatement("xxx :a :b :c xx :a :b"), namedParams, null)[4]).isSameAs(sqlParameterValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertTypeMapToSqlParameterList() {
|
||||
MapSqlParameterSource namedParams = new MapSqlParameterSource();
|
||||
|
||||
+61
-28
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -46,31 +46,28 @@ import static org.mockito.Mockito.verify;
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Kiril Nugmanov
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class SimpleJdbcCallTests {
|
||||
class SimpleJdbcCallTests {
|
||||
|
||||
private Connection connection;
|
||||
private final Connection connection = mock(Connection.class);
|
||||
|
||||
private DatabaseMetaData databaseMetaData;
|
||||
private final DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class);
|
||||
|
||||
private DataSource dataSource;
|
||||
private final DataSource dataSource = mock(DataSource.class);
|
||||
|
||||
private CallableStatement callableStatement;
|
||||
private final CallableStatement callableStatement = mock(CallableStatement.class);
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
connection = mock(Connection.class);
|
||||
databaseMetaData = mock(DatabaseMetaData.class);
|
||||
dataSource = mock(DataSource.class);
|
||||
callableStatement = mock(CallableStatement.class);
|
||||
void setUp() throws Exception {
|
||||
given(connection.getMetaData()).willReturn(databaseMetaData);
|
||||
given(dataSource.getConnection()).willReturn(connection);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNoSuchStoredProcedure() throws Exception {
|
||||
void noSuchStoredProcedure() throws Exception {
|
||||
final String NO_SUCH_PROC = "x";
|
||||
SQLException sqlException = new SQLException("Syntax error or access violation exception", "42000");
|
||||
given(databaseMetaData.getDatabaseProductName()).willReturn("MyDB");
|
||||
@@ -81,8 +78,8 @@ public class SimpleJdbcCallTests {
|
||||
given(connection.prepareCall("{call " + NO_SUCH_PROC + "()}")).willReturn(callableStatement);
|
||||
SimpleJdbcCall sproc = new SimpleJdbcCall(dataSource).withProcedureName(NO_SUCH_PROC);
|
||||
try {
|
||||
assertThatExceptionOfType(BadSqlGrammarException.class).isThrownBy(() ->
|
||||
sproc.execute())
|
||||
assertThatExceptionOfType(BadSqlGrammarException.class)
|
||||
.isThrownBy(() -> sproc.execute())
|
||||
.withCause(sqlException);
|
||||
}
|
||||
finally {
|
||||
@@ -92,7 +89,7 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnnamedParameterHandling() throws Exception {
|
||||
void unnamedParameterHandling() throws Exception {
|
||||
final String MY_PROC = "my_proc";
|
||||
SimpleJdbcCall sproc = new SimpleJdbcCall(dataSource).withProcedureName(MY_PROC);
|
||||
// Shouldn't succeed in adding unnamed parameter
|
||||
@@ -101,7 +98,7 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddInvoiceProcWithoutMetaDataUsingMapParamSource() throws Exception {
|
||||
void addInvoiceProcWithoutMetaDataUsingMapParamSource() throws Exception {
|
||||
initializeAddInvoiceWithoutMetaData(false);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withProcedureName("add_invoice");
|
||||
adder.declareParameters(
|
||||
@@ -117,7 +114,7 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddInvoiceProcWithoutMetaDataUsingArrayParams() throws Exception {
|
||||
void addInvoiceProcWithoutMetaDataUsingArrayParams() throws Exception {
|
||||
initializeAddInvoiceWithoutMetaData(false);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withProcedureName("add_invoice");
|
||||
adder.declareParameters(
|
||||
@@ -131,7 +128,7 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddInvoiceProcWithMetaDataUsingMapParamSource() throws Exception {
|
||||
void addInvoiceProcWithMetaDataUsingMapParamSource() throws Exception {
|
||||
initializeAddInvoiceWithMetaData(false);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withProcedureName("add_invoice");
|
||||
Number newId = adder.executeObject(Number.class, new MapSqlParameterSource()
|
||||
@@ -143,7 +140,7 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddInvoiceProcWithMetaDataUsingArrayParams() throws Exception {
|
||||
void addInvoiceProcWithMetaDataUsingArrayParams() throws Exception {
|
||||
initializeAddInvoiceWithMetaData(false);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withProcedureName("add_invoice");
|
||||
Number newId = adder.executeObject(Number.class, 1103, 3);
|
||||
@@ -153,7 +150,7 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddInvoiceFuncWithoutMetaDataUsingMapParamSource() throws Exception {
|
||||
void addInvoiceFuncWithoutMetaDataUsingMapParamSource() throws Exception {
|
||||
initializeAddInvoiceWithoutMetaData(true);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
|
||||
adder.declareParameters(
|
||||
@@ -169,7 +166,7 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddInvoiceFuncWithoutMetaDataUsingArrayParams() throws Exception {
|
||||
void addInvoiceFuncWithoutMetaDataUsingArrayParams() throws Exception {
|
||||
initializeAddInvoiceWithoutMetaData(true);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
|
||||
adder.declareParameters(
|
||||
@@ -183,7 +180,7 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddInvoiceFuncWithMetaDataUsingMapParamSource() throws Exception {
|
||||
void addInvoiceFuncWithMetaDataUsingMapParamSource() throws Exception {
|
||||
initializeAddInvoiceWithMetaData(true);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
|
||||
Number newId = adder.executeFunction(Number.class, new MapSqlParameterSource()
|
||||
@@ -192,22 +189,20 @@ public class SimpleJdbcCallTests {
|
||||
assertThat(newId.intValue()).isEqualTo(4);
|
||||
verifyAddInvoiceWithMetaData(true);
|
||||
verify(connection, atLeastOnce()).close();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddInvoiceFuncWithMetaDataUsingArrayParams() throws Exception {
|
||||
void addInvoiceFuncWithMetaDataUsingArrayParams() throws Exception {
|
||||
initializeAddInvoiceWithMetaData(true);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
|
||||
Number newId = adder.executeFunction(Number.class, 1103, 3);
|
||||
assertThat(newId.intValue()).isEqualTo(4);
|
||||
verifyAddInvoiceWithMetaData(true);
|
||||
verify(connection, atLeastOnce()).close();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectFunctionStatement() throws Exception {
|
||||
void correctFunctionStatement() throws Exception {
|
||||
initializeAddInvoiceWithMetaData(true);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withFunctionName("add_invoice");
|
||||
adder.compile();
|
||||
@@ -215,7 +210,7 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectFunctionStatementNamed() throws Exception {
|
||||
void correctFunctionStatementNamed() throws Exception {
|
||||
initializeAddInvoiceWithMetaData(true);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withNamedBinding().withFunctionName("add_invoice");
|
||||
adder.compile();
|
||||
@@ -223,13 +218,51 @@ public class SimpleJdbcCallTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectProcedureStatementNamed() throws Exception {
|
||||
void correctProcedureStatementNamed() throws Exception {
|
||||
initializeAddInvoiceWithMetaData(false);
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withNamedBinding().withProcedureName("add_invoice");
|
||||
adder.compile();
|
||||
verifyStatement(adder, "{call ADD_INVOICE(AMOUNT => ?, CUSTID => ?, NEWID => ?)}");
|
||||
}
|
||||
|
||||
/**
|
||||
* This test demonstrates that a CALL statement will still be generated if
|
||||
* an exception occurs while retrieving metadata, potentially resulting in
|
||||
* missing metadata and consequently a failure while invoking the stored
|
||||
* procedure.
|
||||
*/
|
||||
@Test // gh-26486
|
||||
void exceptionThrownWhileRetrievingColumnNamesFromMetadata() throws Exception {
|
||||
ResultSet proceduresResultSet = mock(ResultSet.class);
|
||||
ResultSet procedureColumnsResultSet = mock(ResultSet.class);
|
||||
|
||||
given(databaseMetaData.getDatabaseProductName()).willReturn("Oracle");
|
||||
given(databaseMetaData.getUserName()).willReturn("ME");
|
||||
given(databaseMetaData.storesUpperCaseIdentifiers()).willReturn(true);
|
||||
given(databaseMetaData.getProcedures("", "ME", "ADD_INVOICE")).willReturn(proceduresResultSet);
|
||||
given(databaseMetaData.getProcedureColumns("", "ME", "ADD_INVOICE", null)).willReturn(procedureColumnsResultSet);
|
||||
|
||||
given(proceduresResultSet.next()).willReturn(true, false);
|
||||
given(proceduresResultSet.getString("PROCEDURE_NAME")).willReturn("add_invoice");
|
||||
|
||||
given(procedureColumnsResultSet.next()).willReturn(true, true, true, false);
|
||||
given(procedureColumnsResultSet.getString("COLUMN_NAME")).willReturn("amount", "custid", "newid");
|
||||
given(procedureColumnsResultSet.getInt("DATA_TYPE"))
|
||||
// Return a valid data type for the first 2 columns.
|
||||
.willReturn(Types.INTEGER, Types.INTEGER)
|
||||
// 3rd time, simulate an error while retrieving metadata.
|
||||
.willThrow(new SQLException("error with DATA_TYPE for column 3"));
|
||||
|
||||
SimpleJdbcCall adder = new SimpleJdbcCall(dataSource).withNamedBinding().withProcedureName("add_invoice");
|
||||
adder.compile();
|
||||
// If an exception were not thrown for column 3, we would expect:
|
||||
// {call ADD_INVOICE(AMOUNT => ?, CUSTID => ?, NEWID => ?)}
|
||||
verifyStatement(adder, "{call ADD_INVOICE(AMOUNT => ?, CUSTID => ?)}");
|
||||
|
||||
verify(proceduresResultSet).close();
|
||||
verify(procedureColumnsResultSet).close();
|
||||
}
|
||||
|
||||
|
||||
private void verifyStatement(SimpleJdbcCall adder, String expected) {
|
||||
assertThat(adder.getCallString()).as("Incorrect call statement").isEqualTo(expected);
|
||||
|
||||
+73
-16
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,7 +19,9 @@ package org.springframework.jdbc.core.simple;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.HashMap;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@@ -29,45 +31,44 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Mock object based tests for SimpleJdbcInsert.
|
||||
* Mock object based tests for {@link SimpleJdbcInsert}.
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class SimpleJdbcInsertTests {
|
||||
class SimpleJdbcInsertTests {
|
||||
|
||||
private Connection connection;
|
||||
private final Connection connection = mock(Connection.class);
|
||||
|
||||
private DatabaseMetaData databaseMetaData;
|
||||
private final DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class);
|
||||
|
||||
private DataSource dataSource;
|
||||
private final DataSource dataSource = mock(DataSource.class);
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
connection = mock(Connection.class);
|
||||
databaseMetaData = mock(DatabaseMetaData.class);
|
||||
dataSource = mock(DataSource.class);
|
||||
void setUp() throws Exception {
|
||||
given(connection.getMetaData()).willReturn(databaseMetaData);
|
||||
given(dataSource.getConnection()).willReturn(connection);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void verifyClosed() throws Exception {
|
||||
void verifyClosed() throws Exception {
|
||||
verify(connection).close();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNoSuchTable() throws Exception {
|
||||
void noSuchTable() throws Exception {
|
||||
ResultSet resultSet = mock(ResultSet.class);
|
||||
given(resultSet.next()).willReturn(false);
|
||||
given(databaseMetaData.getDatabaseProductName()).willReturn("MyDB");
|
||||
|
||||
given(databaseMetaData.getDatabaseProductName()).willReturn("MyDB");
|
||||
given(databaseMetaData.getDatabaseProductVersion()).willReturn("1.0");
|
||||
given(databaseMetaData.getUserName()).willReturn("me");
|
||||
@@ -76,9 +77,65 @@ public class SimpleJdbcInsertTests {
|
||||
|
||||
SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource).withTableName("x");
|
||||
// Shouldn't succeed in inserting into table which doesn't exist
|
||||
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() ->
|
||||
insert.execute(new HashMap<>()));
|
||||
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
|
||||
.isThrownBy(() -> insert.execute(Collections.emptyMap()))
|
||||
.withMessageStartingWith("Unable to locate columns for table 'x' so an insert statement can't be generated");
|
||||
|
||||
verify(resultSet).close();
|
||||
}
|
||||
|
||||
@Test // gh-26486
|
||||
void retrieveColumnNamesFromMetadata() throws Exception {
|
||||
ResultSet tableResultSet = mock(ResultSet.class);
|
||||
given(tableResultSet.next()).willReturn(true, false);
|
||||
|
||||
given(databaseMetaData.getUserName()).willReturn("me");
|
||||
given(databaseMetaData.getTables(null, null, "me", null)).willReturn(tableResultSet);
|
||||
|
||||
ResultSet columnResultSet = mock(ResultSet.class);
|
||||
given(databaseMetaData.getColumns(null, "me", null, null)).willReturn(columnResultSet);
|
||||
given(columnResultSet.next()).willReturn(true, true, false);
|
||||
given(columnResultSet.getString("COLUMN_NAME")).willReturn("col1", "col2");
|
||||
given(columnResultSet.getInt("DATA_TYPE")).willReturn(Types.VARCHAR);
|
||||
given(columnResultSet.getBoolean("NULLABLE")).willReturn(false);
|
||||
|
||||
SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource).withTableName("me");
|
||||
insert.compile();
|
||||
assertThat(insert.getInsertString()).isEqualTo("INSERT INTO me (col1, col2) VALUES(?, ?)");
|
||||
|
||||
verify(columnResultSet).close();
|
||||
verify(tableResultSet).close();
|
||||
}
|
||||
|
||||
@Test // gh-26486
|
||||
void exceptionThrownWhileRetrievingColumnNamesFromMetadata() throws Exception {
|
||||
ResultSet tableResultSet = mock(ResultSet.class);
|
||||
given(tableResultSet.next()).willReturn(true, false);
|
||||
|
||||
given(databaseMetaData.getUserName()).willReturn("me");
|
||||
given(databaseMetaData.getTables(null, null, "me", null)).willReturn(tableResultSet);
|
||||
|
||||
ResultSet columnResultSet = mock(ResultSet.class);
|
||||
given(databaseMetaData.getColumns(null, "me", null, null)).willReturn(columnResultSet);
|
||||
// true, true, false --> simulates processing of two columns
|
||||
given(columnResultSet.next()).willReturn(true, true, false);
|
||||
given(columnResultSet.getString("COLUMN_NAME"))
|
||||
// Return a column name the first time.
|
||||
.willReturn("col1")
|
||||
// Second time, simulate an error while retrieving metadata.
|
||||
.willThrow(new SQLException("error with col2"));
|
||||
given(columnResultSet.getInt("DATA_TYPE")).willReturn(Types.VARCHAR);
|
||||
given(columnResultSet.getBoolean("NULLABLE")).willReturn(false);
|
||||
|
||||
SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource).withTableName("me");
|
||||
|
||||
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
|
||||
.isThrownBy(insert::compile)
|
||||
.withMessage("Unable to locate columns for table 'me' so an insert statement can't be generated. " +
|
||||
"Consider specifying explicit column names -- for example, via SimpleJdbcInsert#usingColumns().");
|
||||
|
||||
verify(columnResultSet).close();
|
||||
verify(tableResultSet).close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+41
-1
@@ -19,6 +19,7 @@ package org.springframework.messaging.simp.broker;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
@@ -58,6 +59,9 @@ public abstract class AbstractBrokerMessageHandler
|
||||
|
||||
private final Collection<String> destinationPrefixes;
|
||||
|
||||
@Nullable
|
||||
private Predicate<String> userDestinationPredicate;
|
||||
|
||||
private boolean preservePublishOrder = false;
|
||||
|
||||
@Nullable
|
||||
@@ -135,6 +139,21 @@ public abstract class AbstractBrokerMessageHandler
|
||||
return this.destinationPrefixes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a Predicate to identify messages with a user destination. When
|
||||
* no {@link #getDestinationPrefixes() destination prefixes} are configured,
|
||||
* this helps to recognize and skip user destination messages that need to
|
||||
* be pre-processed by the
|
||||
* {@link org.springframework.messaging.simp.user.UserDestinationMessageHandler}
|
||||
* before they reach the broker.
|
||||
* @param predicate the predicate to identify user messages with a non-null
|
||||
* destination as messages with a user destinations.
|
||||
* @since 5.3.4
|
||||
*/
|
||||
public void setUserDestinationPredicate(@Nullable Predicate<String> predicate) {
|
||||
this.userDestinationPredicate = predicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the client must receive messages in the order of publication.
|
||||
* <p>By default messages sent to the {@code "clientOutboundChannel"} may
|
||||
@@ -265,10 +284,27 @@ public abstract class AbstractBrokerMessageHandler
|
||||
protected abstract void handleMessageInternal(Message<?> message);
|
||||
|
||||
|
||||
/**
|
||||
* Whether a message with the given destination should be processed. This is
|
||||
* the case if one of the following conditions is true:
|
||||
* <ol>
|
||||
* <li>The destination starts with one of the configured
|
||||
* {@link #getDestinationPrefixes() destination prefixes}.
|
||||
* <li>No prefixes are configured and the destination isn't matched
|
||||
* by the {@link #setUserDestinationPredicate(Predicate)
|
||||
* userDestinationPredicate}.
|
||||
* <li>The message has no destination.
|
||||
* </ol>
|
||||
* @param destination the destination to check
|
||||
* @return whether to process (true) or skip (false) the destination
|
||||
*/
|
||||
protected boolean checkDestinationPrefix(@Nullable String destination) {
|
||||
if (destination == null || CollectionUtils.isEmpty(this.destinationPrefixes)) {
|
||||
if (destination == null) {
|
||||
return true;
|
||||
}
|
||||
if (CollectionUtils.isEmpty(this.destinationPrefixes)) {
|
||||
return !isUserDestination(destination);
|
||||
}
|
||||
for (String prefix : this.destinationPrefixes) {
|
||||
if (destination.startsWith(prefix)) {
|
||||
return true;
|
||||
@@ -277,6 +313,10 @@ public abstract class AbstractBrokerMessageHandler
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isUserDestination(String destination) {
|
||||
return (this.userDestinationPredicate != null && this.userDestinationPredicate.test(destination));
|
||||
}
|
||||
|
||||
protected void publishBrokerAvailableEvent() {
|
||||
boolean shouldPublish = this.brokerAvailable.compareAndSet(false, true);
|
||||
if (this.eventPublisher != null && shouldPublish) {
|
||||
|
||||
+88
-57
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -64,6 +64,7 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
@@ -71,18 +72,18 @@ import org.springframework.validation.Validator;
|
||||
* Provides essential configuration for handling messages with simple messaging
|
||||
* protocols such as STOMP.
|
||||
*
|
||||
* <p>{@link #clientInboundChannel(TaskExecutor)} and {@link #clientOutboundChannel(TaskExecutor)} deliver
|
||||
* <p>{@link #clientInboundChannel} and {@link #clientOutboundChannel} deliver
|
||||
* messages to and from remote clients to several message handlers such as the
|
||||
* following.
|
||||
* <ul>
|
||||
* <li>{@link #simpAnnotationMethodMessageHandler(AbstractSubscribableChannel, AbstractSubscribableChannel, SimpMessagingTemplate, CompositeMessageConverter)}</li>
|
||||
* <li>{@link #simpleBrokerMessageHandler(AbstractSubscribableChannel, AbstractSubscribableChannel, AbstractSubscribableChannel, UserDestinationResolver)}</li>
|
||||
* <li>{@link #stompBrokerRelayMessageHandler(AbstractSubscribableChannel, AbstractSubscribableChannel, AbstractSubscribableChannel, UserDestinationMessageHandler, MessageHandler, UserDestinationResolver)}</li>
|
||||
* <li>{@link #userDestinationMessageHandler(AbstractSubscribableChannel, AbstractSubscribableChannel, AbstractSubscribableChannel, UserDestinationResolver)}</li>
|
||||
* <li>{@link #simpAnnotationMethodMessageHandler}</li>
|
||||
* <li>{@link #simpleBrokerMessageHandler}</li>
|
||||
* <li>{@link #stompBrokerRelayMessageHandler}</li>
|
||||
* <li>{@link #userDestinationMessageHandler}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>{@link #brokerChannel(AbstractSubscribableChannel, AbstractSubscribableChannel, TaskExecutor)} delivers messages from within the application to the
|
||||
* the respective message handlers. {@link #brokerMessagingTemplate(AbstractSubscribableChannel, AbstractSubscribableChannel, AbstractSubscribableChannel, CompositeMessageConverter)} can be injected
|
||||
* <p>{@link #brokerChannel} delivers messages from within the application to the
|
||||
* the respective message handlers. {@link #brokerMessagingTemplate} can be injected
|
||||
* into any application component to send messages.
|
||||
*
|
||||
* <p>Subclasses are responsible for the parts of the configuration that feed messages
|
||||
@@ -222,22 +223,26 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
@Bean
|
||||
public AbstractSubscribableChannel brokerChannel(AbstractSubscribableChannel clientInboundChannel,
|
||||
AbstractSubscribableChannel clientOutboundChannel, TaskExecutor brokerChannelExecutor) {
|
||||
ChannelRegistration reg = getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getBrokerChannelRegistration();
|
||||
ExecutorSubscribableChannel channel = (reg.hasTaskExecutor() ?
|
||||
|
||||
MessageBrokerRegistry registry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
|
||||
ChannelRegistration registration = registry.getBrokerChannelRegistration();
|
||||
ExecutorSubscribableChannel channel = (registration.hasTaskExecutor() ?
|
||||
new ExecutorSubscribableChannel(brokerChannelExecutor) : new ExecutorSubscribableChannel());
|
||||
reg.interceptors(new ImmutableMessageChannelInterceptor());
|
||||
registration.interceptors(new ImmutableMessageChannelInterceptor());
|
||||
channel.setLogger(SimpLogging.forLog(channel.getLogger()));
|
||||
channel.setInterceptors(reg.getInterceptors());
|
||||
channel.setInterceptors(registration.getInterceptors());
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TaskExecutor brokerChannelExecutor(AbstractSubscribableChannel clientInboundChannel,
|
||||
AbstractSubscribableChannel clientOutboundChannel) {
|
||||
ChannelRegistration reg = getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getBrokerChannelRegistration();
|
||||
public TaskExecutor brokerChannelExecutor(
|
||||
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel) {
|
||||
|
||||
MessageBrokerRegistry registry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
|
||||
ChannelRegistration registration = registry.getBrokerChannelRegistration();
|
||||
ThreadPoolTaskExecutor executor;
|
||||
if (reg.hasTaskExecutor()) {
|
||||
executor = reg.taskExecutor().getTaskExecutor();
|
||||
if (registration.hasTaskExecutor()) {
|
||||
executor = registration.taskExecutor().getTaskExecutor();
|
||||
}
|
||||
else {
|
||||
// Should never be used
|
||||
@@ -254,8 +259,9 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
* An accessor for the {@link MessageBrokerRegistry} that ensures its one-time creation
|
||||
* and initialization through {@link #configureMessageBroker(MessageBrokerRegistry)}.
|
||||
*/
|
||||
protected final MessageBrokerRegistry getBrokerRegistry(AbstractSubscribableChannel clientInboundChannel,
|
||||
AbstractSubscribableChannel clientOutboundChannel) {
|
||||
protected final MessageBrokerRegistry getBrokerRegistry(
|
||||
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel) {
|
||||
|
||||
if (this.brokerRegistry == null) {
|
||||
MessageBrokerRegistry registry = new MessageBrokerRegistry(clientInboundChannel, clientOutboundChannel);
|
||||
configureMessageBroker(registry);
|
||||
@@ -276,8 +282,9 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
* configuration classes.
|
||||
*/
|
||||
@Nullable
|
||||
public final PathMatcher getPathMatcher(AbstractSubscribableChannel clientInboundChannel,
|
||||
AbstractSubscribableChannel clientOutboundChannel) {
|
||||
public final PathMatcher getPathMatcher(
|
||||
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel) {
|
||||
|
||||
return getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getPathMatcher();
|
||||
}
|
||||
|
||||
@@ -285,8 +292,10 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
public SimpAnnotationMethodMessageHandler simpAnnotationMethodMessageHandler(
|
||||
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel,
|
||||
SimpMessagingTemplate brokerMessagingTemplate, CompositeMessageConverter brokerMessageConverter) {
|
||||
SimpAnnotationMethodMessageHandler handler = createAnnotationMethodMessageHandler(clientInboundChannel,
|
||||
clientOutboundChannel, brokerMessagingTemplate);
|
||||
|
||||
SimpAnnotationMethodMessageHandler handler = createAnnotationMethodMessageHandler(
|
||||
clientInboundChannel, clientOutboundChannel, brokerMessagingTemplate);
|
||||
|
||||
MessageBrokerRegistry brokerRegistry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
|
||||
handler.setDestinationPrefixes(brokerRegistry.getApplicationDestinationPrefixes());
|
||||
handler.setMessageConverter(brokerMessageConverter);
|
||||
@@ -316,7 +325,9 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
protected SimpAnnotationMethodMessageHandler createAnnotationMethodMessageHandler(
|
||||
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel,
|
||||
SimpMessagingTemplate brokerMessagingTemplate) {
|
||||
return new SimpAnnotationMethodMessageHandler(clientInboundChannel, clientOutboundChannel, brokerMessagingTemplate);
|
||||
|
||||
return new SimpAnnotationMethodMessageHandler(
|
||||
clientInboundChannel, clientOutboundChannel, brokerMessagingTemplate);
|
||||
}
|
||||
|
||||
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
||||
@@ -327,22 +338,30 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
|
||||
@Bean
|
||||
@Nullable
|
||||
public AbstractBrokerMessageHandler simpleBrokerMessageHandler(AbstractSubscribableChannel clientInboundChannel,
|
||||
AbstractSubscribableChannel clientOutboundChannel, AbstractSubscribableChannel brokerChannel,
|
||||
UserDestinationResolver userDestinationResolver) {
|
||||
SimpleBrokerMessageHandler handler = getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getSimpleBroker(brokerChannel);
|
||||
public AbstractBrokerMessageHandler simpleBrokerMessageHandler(
|
||||
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel,
|
||||
AbstractSubscribableChannel brokerChannel, UserDestinationResolver userDestinationResolver) {
|
||||
|
||||
MessageBrokerRegistry registry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
|
||||
SimpleBrokerMessageHandler handler = registry.getSimpleBroker(brokerChannel);
|
||||
if (handler == null) {
|
||||
return null;
|
||||
}
|
||||
updateUserDestinationResolver(handler, userDestinationResolver);
|
||||
updateUserDestinationResolver(handler, userDestinationResolver, registry.getUserDestinationPrefix());
|
||||
return handler;
|
||||
}
|
||||
|
||||
private void updateUserDestinationResolver(AbstractBrokerMessageHandler handler, UserDestinationResolver userDestinationResolver) {
|
||||
private void updateUserDestinationResolver(
|
||||
AbstractBrokerMessageHandler handler, UserDestinationResolver userDestinationResolver,
|
||||
@Nullable String userDestinationPrefix) {
|
||||
|
||||
Collection<String> prefixes = handler.getDestinationPrefixes();
|
||||
if (!prefixes.isEmpty() && !prefixes.iterator().next().startsWith("/")) {
|
||||
((DefaultUserDestinationResolver) userDestinationResolver).setRemoveLeadingSlash(true);
|
||||
}
|
||||
if (StringUtils.hasText(userDestinationPrefix)) {
|
||||
handler.setUserDestinationPredicate(destination -> destination.startsWith(userDestinationPrefix));
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -351,32 +370,36 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
AbstractSubscribableChannel clientOutboundChannel, AbstractSubscribableChannel brokerChannel,
|
||||
UserDestinationMessageHandler userDestinationMessageHandler, @Nullable MessageHandler userRegistryMessageHandler,
|
||||
UserDestinationResolver userDestinationResolver) {
|
||||
MessageBrokerRegistry brokerRegistry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
|
||||
StompBrokerRelayMessageHandler handler = brokerRegistry.getStompBrokerRelay(brokerChannel);
|
||||
|
||||
MessageBrokerRegistry registry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
|
||||
StompBrokerRelayMessageHandler handler = registry.getStompBrokerRelay(brokerChannel);
|
||||
if (handler == null) {
|
||||
return null;
|
||||
}
|
||||
Map<String, MessageHandler> subscriptions = new HashMap<>(4);
|
||||
String destination = brokerRegistry.getUserDestinationBroadcast();
|
||||
String destination = registry.getUserDestinationBroadcast();
|
||||
if (destination != null) {
|
||||
subscriptions.put(destination, userDestinationMessageHandler);
|
||||
}
|
||||
destination = brokerRegistry.getUserRegistryBroadcast();
|
||||
destination = registry.getUserRegistryBroadcast();
|
||||
if (destination != null) {
|
||||
subscriptions.put(destination, userRegistryMessageHandler);
|
||||
}
|
||||
handler.setSystemSubscriptions(subscriptions);
|
||||
updateUserDestinationResolver(handler, userDestinationResolver);
|
||||
updateUserDestinationResolver(handler, userDestinationResolver, registry.getUserDestinationPrefix());
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserDestinationMessageHandler userDestinationMessageHandler(AbstractSubscribableChannel clientInboundChannel,
|
||||
AbstractSubscribableChannel clientOutboundChannel, AbstractSubscribableChannel brokerChannel,
|
||||
UserDestinationResolver userDestinationResolver) {
|
||||
UserDestinationMessageHandler handler = new UserDestinationMessageHandler(clientInboundChannel,
|
||||
brokerChannel, userDestinationResolver);
|
||||
String destination = getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getUserDestinationBroadcast();
|
||||
public UserDestinationMessageHandler userDestinationMessageHandler(
|
||||
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel,
|
||||
AbstractSubscribableChannel brokerChannel, UserDestinationResolver userDestinationResolver) {
|
||||
|
||||
UserDestinationMessageHandler handler =
|
||||
new UserDestinationMessageHandler(clientInboundChannel, brokerChannel, userDestinationResolver);
|
||||
|
||||
MessageBrokerRegistry registry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
|
||||
String destination = registry.getUserDestinationBroadcast();
|
||||
if (destination != null) {
|
||||
handler.setBroadcastDestination(destination);
|
||||
}
|
||||
@@ -385,9 +408,11 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
|
||||
@Bean
|
||||
@Nullable
|
||||
public MessageHandler userRegistryMessageHandler(AbstractSubscribableChannel clientInboundChannel,
|
||||
AbstractSubscribableChannel clientOutboundChannel, SimpUserRegistry userRegistry,
|
||||
SimpMessagingTemplate brokerMessagingTemplate, TaskScheduler messageBrokerTaskScheduler) {
|
||||
public MessageHandler userRegistryMessageHandler(
|
||||
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel,
|
||||
SimpUserRegistry userRegistry, SimpMessagingTemplate brokerMessagingTemplate,
|
||||
TaskScheduler messageBrokerTaskScheduler) {
|
||||
|
||||
MessageBrokerRegistry brokerRegistry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
|
||||
if (brokerRegistry.getUserRegistryBroadcast() == null) {
|
||||
return null;
|
||||
@@ -409,11 +434,13 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SimpMessagingTemplate brokerMessagingTemplate(AbstractSubscribableChannel brokerChannel,
|
||||
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel,
|
||||
CompositeMessageConverter brokerMessageConverter) {
|
||||
public SimpMessagingTemplate brokerMessagingTemplate(
|
||||
AbstractSubscribableChannel brokerChannel, AbstractSubscribableChannel clientInboundChannel,
|
||||
AbstractSubscribableChannel clientOutboundChannel, CompositeMessageConverter brokerMessageConverter) {
|
||||
|
||||
SimpMessagingTemplate template = new SimpMessagingTemplate(brokerChannel);
|
||||
String prefix = getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getUserDestinationPrefix();
|
||||
MessageBrokerRegistry registry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
|
||||
String prefix = registry.getUserDestinationPrefix();
|
||||
if (prefix != null) {
|
||||
template.setUserDestinationPrefix(prefix);
|
||||
}
|
||||
@@ -463,10 +490,13 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UserDestinationResolver userDestinationResolver(SimpUserRegistry userRegistry,
|
||||
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel) {
|
||||
public UserDestinationResolver userDestinationResolver(
|
||||
SimpUserRegistry userRegistry, AbstractSubscribableChannel clientInboundChannel,
|
||||
AbstractSubscribableChannel clientOutboundChannel) {
|
||||
|
||||
DefaultUserDestinationResolver resolver = new DefaultUserDestinationResolver(userRegistry);
|
||||
String prefix = getBrokerRegistry(clientInboundChannel, clientOutboundChannel).getUserDestinationPrefix();
|
||||
MessageBrokerRegistry registry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
|
||||
String prefix = registry.getUserDestinationPrefix();
|
||||
if (prefix != null) {
|
||||
resolver.setUserDestinationPrefix(prefix);
|
||||
}
|
||||
@@ -475,15 +505,16 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings("deprecation")
|
||||
public SimpUserRegistry userRegistry(AbstractSubscribableChannel clientInboundChannel,
|
||||
AbstractSubscribableChannel clientOutboundChannel) {
|
||||
SimpUserRegistry registry = createLocalUserRegistry();
|
||||
public SimpUserRegistry userRegistry(
|
||||
AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel) {
|
||||
|
||||
SimpUserRegistry userRegistry = createLocalUserRegistry();
|
||||
MessageBrokerRegistry brokerRegistry = getBrokerRegistry(clientInboundChannel, clientOutboundChannel);
|
||||
if (registry == null) {
|
||||
registry = createLocalUserRegistry(brokerRegistry.getUserRegistryOrder());
|
||||
if (userRegistry == null) {
|
||||
userRegistry = createLocalUserRegistry(brokerRegistry.getUserRegistryOrder());
|
||||
}
|
||||
boolean broadcast = brokerRegistry.getUserRegistryBroadcast() != null;
|
||||
return (broadcast ? new MultiServerUserRegistry(registry) : registry);
|
||||
return (broadcast ? new MultiServerUserRegistry(userRegistry) : userRegistry);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+55
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,7 +28,10 @@ import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.messaging.simp.SimpMessageType;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -74,28 +77,25 @@ public class BrokerMessageHandlerTests {
|
||||
|
||||
@Test
|
||||
public void publishBrokerAvailableEvent() {
|
||||
|
||||
assertThat(this.handler.isBrokerAvailable()).isFalse();
|
||||
assertThat(this.handler.availabilityEvents).isEqualTo(Collections.emptyList());
|
||||
|
||||
this.handler.publishBrokerAvailableEvent();
|
||||
|
||||
assertThat(this.handler.isBrokerAvailable()).isTrue();
|
||||
assertThat(this.handler.availabilityEvents).isEqualTo(Arrays.asList(true));
|
||||
assertThat(this.handler.availabilityEvents).isEqualTo(Collections.singletonList(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publishBrokerAvailableEventWhenAlreadyAvailable() {
|
||||
|
||||
this.handler.publishBrokerAvailableEvent();
|
||||
this.handler.publishBrokerAvailableEvent();
|
||||
|
||||
assertThat(this.handler.availabilityEvents).isEqualTo(Arrays.asList(true));
|
||||
assertThat(this.handler.availabilityEvents).isEqualTo(Collections.singletonList(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publishBrokerUnavailableEvent() {
|
||||
|
||||
this.handler.publishBrokerAvailableEvent();
|
||||
assertThat(this.handler.isBrokerAvailable()).isTrue();
|
||||
|
||||
@@ -107,7 +107,6 @@ public class BrokerMessageHandlerTests {
|
||||
|
||||
@Test
|
||||
public void publishBrokerUnavailableEventWhenAlreadyUnavailable() {
|
||||
|
||||
this.handler.publishBrokerAvailableEvent();
|
||||
this.handler.publishBrokerUnavailableEvent();
|
||||
this.handler.publishBrokerUnavailableEvent();
|
||||
@@ -115,6 +114,47 @@ public class BrokerMessageHandlerTests {
|
||||
assertThat(this.handler.availabilityEvents).isEqualTo(Arrays.asList(true, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDestination() {
|
||||
TestBrokerMessageHandler theHandler = new TestBrokerMessageHandler("/topic");
|
||||
theHandler.start();
|
||||
|
||||
SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
|
||||
accessor.setLeaveMutable(true);
|
||||
|
||||
accessor.setDestination("/topic/foo");
|
||||
theHandler.handleMessage(MessageBuilder.createMessage("p", accessor.toMessageHeaders()));
|
||||
|
||||
accessor.setDestination("/app/foo");
|
||||
theHandler.handleMessage(MessageBuilder.createMessage("p", accessor.toMessageHeaders()));
|
||||
|
||||
accessor.setDestination(null);
|
||||
theHandler.handleMessage(MessageBuilder.createMessage("p", accessor.toMessageHeaders()));
|
||||
|
||||
List<Message<?>> list = theHandler.messages;
|
||||
assertThat(list).hasSize(2);
|
||||
assertThat(list.get(0).getHeaders().get(SimpMessageHeaderAccessor.DESTINATION_HEADER)).isEqualTo("/topic/foo");
|
||||
assertThat(list.get(1).getHeaders().get(SimpMessageHeaderAccessor.DESTINATION_HEADER)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDestinationWithoutConfiguredPrefixes() {
|
||||
this.handler.setUserDestinationPredicate(destination -> destination.startsWith("/user/"));
|
||||
this.handler.start();
|
||||
|
||||
SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
|
||||
accessor.setLeaveMutable(true);
|
||||
|
||||
accessor.setDestination("/user/1/foo");
|
||||
this.handler.handleMessage(MessageBuilder.createMessage("p", accessor.toMessageHeaders()));
|
||||
|
||||
accessor.setDestination("/foo");
|
||||
this.handler.handleMessage(MessageBuilder.createMessage("p", accessor.toMessageHeaders()));
|
||||
|
||||
List<Message<?>> list = this.handler.messages;
|
||||
assertThat(list).hasSize(1);
|
||||
assertThat(list.get(0).getHeaders().get(SimpMessageHeaderAccessor.DESTINATION_HEADER)).isEqualTo("/foo");
|
||||
}
|
||||
|
||||
private static class TestBrokerMessageHandler extends AbstractBrokerMessageHandler
|
||||
implements ApplicationEventPublisher {
|
||||
@@ -124,14 +164,19 @@ public class BrokerMessageHandlerTests {
|
||||
private final List<Boolean> availabilityEvents = new ArrayList<>();
|
||||
|
||||
|
||||
private TestBrokerMessageHandler() {
|
||||
super(mock(SubscribableChannel.class), mock(MessageChannel.class), mock(SubscribableChannel.class));
|
||||
TestBrokerMessageHandler(String... destinationPrefixes) {
|
||||
super(mock(SubscribableChannel.class), mock(MessageChannel.class),
|
||||
mock(SubscribableChannel.class), Arrays.asList(destinationPrefixes));
|
||||
|
||||
setApplicationEventPublisher(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleMessageInternal(Message<?> message) {
|
||||
this.messages.add(message);
|
||||
String destination = (String) message.getHeaders().get(SimpMessageHeaderAccessor.DESTINATION_HEADER);
|
||||
if (checkDestinationPrefix(destination)) {
|
||||
this.messages.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,14 +21,12 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Internal helper class that serves as value holder for request headers.
|
||||
* Internal helper class that serves as a value holder for request headers.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Rick Evans
|
||||
@@ -39,30 +37,30 @@ class HeaderValueHolder {
|
||||
private final List<Object> values = new LinkedList<>();
|
||||
|
||||
|
||||
public void setValue(@Nullable Object value) {
|
||||
void setValue(@Nullable Object value) {
|
||||
this.values.clear();
|
||||
if (value != null) {
|
||||
this.values.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void addValue(Object value) {
|
||||
void addValue(Object value) {
|
||||
this.values.add(value);
|
||||
}
|
||||
|
||||
public void addValues(Collection<?> values) {
|
||||
void addValues(Collection<?> values) {
|
||||
this.values.addAll(values);
|
||||
}
|
||||
|
||||
public void addValueArray(Object values) {
|
||||
void addValueArray(Object values) {
|
||||
CollectionUtils.mergeArrayIntoCollection(values, this.values);
|
||||
}
|
||||
|
||||
public List<Object> getValues() {
|
||||
List<Object> getValues() {
|
||||
return Collections.unmodifiableList(this.values);
|
||||
}
|
||||
|
||||
public List<String> getStringValues() {
|
||||
List<String> getStringValues() {
|
||||
List<String> stringList = new ArrayList<>(this.values.size());
|
||||
for (Object value : this.values) {
|
||||
stringList.add(value.toString());
|
||||
@@ -71,12 +69,12 @@ class HeaderValueHolder {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Object getValue() {
|
||||
Object getValue() {
|
||||
return (!this.values.isEmpty() ? this.values.get(0) : null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getStringValue() {
|
||||
String getStringValue() {
|
||||
return (!this.values.isEmpty() ? String.valueOf(this.values.get(0)) : null);
|
||||
}
|
||||
|
||||
@@ -85,25 +83,4 @@ class HeaderValueHolder {
|
||||
return this.values.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find a HeaderValueHolder by name, ignoring casing.
|
||||
* @param headers the Map of header names to HeaderValueHolders
|
||||
* @param name the name of the desired header
|
||||
* @return the corresponding HeaderValueHolder, or {@code null} if none found
|
||||
* @deprecated as of 5.1.10 in favor of using
|
||||
* {@link org.springframework.util.LinkedCaseInsensitiveMap}.
|
||||
*/
|
||||
@Nullable
|
||||
@Deprecated
|
||||
public static HeaderValueHolder getByName(Map<String, HeaderValueHolder> headers, String name) {
|
||||
Assert.notNull(name, "Header name must not be null");
|
||||
for (Map.Entry<String, HeaderValueHolder> entry : headers.entrySet()) {
|
||||
if (entry.getKey().equalsIgnoreCase(name)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.mock.web;
|
||||
|
||||
import javax.servlet.http.HttpServletMapping;
|
||||
import javax.servlet.http.MappingMatch;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Mock implementation of {@link HttpServletMapping}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.3.4
|
||||
*/
|
||||
public class MockHttpServletMapping implements HttpServletMapping {
|
||||
|
||||
private final String matchValue;
|
||||
|
||||
private final String pattern;
|
||||
|
||||
private final String servletName;
|
||||
|
||||
@Nullable
|
||||
private final MappingMatch mappingMatch;
|
||||
|
||||
|
||||
public MockHttpServletMapping(
|
||||
String matchValue, String pattern, String servletName, @Nullable MappingMatch match) {
|
||||
|
||||
this.matchValue = matchValue;
|
||||
this.pattern = pattern;
|
||||
this.servletName = servletName;
|
||||
this.mappingMatch = match;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getMatchValue() {
|
||||
return this.matchValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPattern() {
|
||||
return this.pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServletName() {
|
||||
return this.servletName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MappingMatch getMappingMatch() {
|
||||
return this.mappingMatch;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MockHttpServletMapping [matchValue=\"" + this.matchValue + "\", " +
|
||||
"pattern=\"" + this.pattern + "\", servletName=\"" + this.servletName + "\", " +
|
||||
"mappingMatch=" + this.mappingMatch + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -52,6 +52,7 @@ import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletMapping;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
@@ -274,6 +275,8 @@ public class MockHttpServletRequest implements HttpServletRequest {
|
||||
|
||||
private final MultiValueMap<String, Part> parts = new LinkedMultiValueMap<>();
|
||||
|
||||
private HttpServletMapping httpServletMapping = new MockHttpServletMapping("", "", "", null);
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Constructors
|
||||
@@ -1390,6 +1393,15 @@ public class MockHttpServletRequest implements HttpServletRequest {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setHttpServletMapping(HttpServletMapping httpServletMapping) {
|
||||
this.httpServletMapping = httpServletMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpServletMapping getHttpServletMapping() {
|
||||
return this.httpServletMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException {
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
+15
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -344,6 +344,9 @@ public class MockHttpServletResponse implements HttpServletResponse {
|
||||
|
||||
@Override
|
||||
public void setLocale(Locale locale) {
|
||||
if (locale == null) {
|
||||
return;
|
||||
}
|
||||
this.locale = locale;
|
||||
doAddHeaderValue(HttpHeaders.CONTENT_LANGUAGE, locale.toLanguageTag(), true);
|
||||
}
|
||||
@@ -421,7 +424,7 @@ public class MockHttpServletResponse implements HttpServletResponse {
|
||||
|
||||
@Override
|
||||
public boolean containsHeader(String name) {
|
||||
return (this.headers.get(name) != null);
|
||||
return this.headers.containsKey(name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -594,12 +597,12 @@ public class MockHttpServletResponse implements HttpServletResponse {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, String value) {
|
||||
public void setHeader(String name, @Nullable String value) {
|
||||
setHeaderValue(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHeader(String name, String value) {
|
||||
public void addHeader(String name, @Nullable String value) {
|
||||
addHeaderValue(name, value);
|
||||
}
|
||||
|
||||
@@ -613,7 +616,10 @@ public class MockHttpServletResponse implements HttpServletResponse {
|
||||
addHeaderValue(name, value);
|
||||
}
|
||||
|
||||
private void setHeaderValue(String name, Object value) {
|
||||
private void setHeaderValue(String name, @Nullable Object value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
boolean replaceHeader = true;
|
||||
if (setSpecialHeader(name, value, replaceHeader)) {
|
||||
return;
|
||||
@@ -621,7 +627,10 @@ public class MockHttpServletResponse implements HttpServletResponse {
|
||||
doAddHeaderValue(name, value, replaceHeader);
|
||||
}
|
||||
|
||||
private void addHeaderValue(String name, Object value) {
|
||||
private void addHeaderValue(String name, @Nullable Object value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
boolean replaceHeader = false;
|
||||
if (setSpecialHeader(name, value, replaceHeader)) {
|
||||
return;
|
||||
|
||||
+21
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.mock.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
@@ -33,6 +34,7 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.multipart.MultipartException;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||
|
||||
@@ -155,15 +157,28 @@ public class MockMultipartHttpServletRequest extends MockHttpServletRequest impl
|
||||
|
||||
@Override
|
||||
public HttpHeaders getMultipartHeaders(String paramOrFileName) {
|
||||
String contentType = getMultipartContentType(paramOrFileName);
|
||||
if (contentType != null) {
|
||||
MultipartFile file = getFile(paramOrFileName);
|
||||
if (file != null) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add(HttpHeaders.CONTENT_TYPE, contentType);
|
||||
if (file.getContentType() != null) {
|
||||
headers.add(HttpHeaders.CONTENT_TYPE, file.getContentType());
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
try {
|
||||
Part part = getPart(paramOrFileName);
|
||||
if (part != null) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
for (String headerName : part.getHeaderNames()) {
|
||||
headers.put(headerName, new ArrayList<>(part.getHeaders(headerName)));
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new MultipartException("Could not access multipart servlet request", ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -67,7 +67,6 @@ public abstract class ApplicationEventsHolder {
|
||||
* @throws IllegalStateException if an instance of {@code ApplicationEvents}
|
||||
* has not been registered for the current thread
|
||||
*/
|
||||
@Nullable
|
||||
public static ApplicationEvents getRequiredApplicationEvents() {
|
||||
ApplicationEvents events = applicationEvents.get();
|
||||
Assert.state(events != null, "Failed to retrieve ApplicationEvents for the current thread. " +
|
||||
|
||||
+13
-1
@@ -46,6 +46,7 @@ import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
|
||||
import org.springframework.transaction.interceptor.TransactionAttribute;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationUtils;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -258,7 +259,7 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
|
||||
else {
|
||||
DataSource dataSourceFromTxMgr = getDataSourceFromTransactionManager(txMgr);
|
||||
// Ensure user configured an appropriate DataSource/TransactionManager pair.
|
||||
if (dataSource != null && dataSourceFromTxMgr != null && !dataSource.equals(dataSourceFromTxMgr)) {
|
||||
if (dataSource != null && dataSourceFromTxMgr != null && !sameDataSource(dataSource, dataSourceFromTxMgr)) {
|
||||
throw new IllegalStateException(String.format("Failed to execute SQL scripts for test context %s: " +
|
||||
"the configured DataSource [%s] (named '%s') is not the one associated with " +
|
||||
"transaction manager [%s] (named '%s').", testContext, dataSource.getClass().getName(),
|
||||
@@ -292,6 +293,17 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen
|
||||
return populator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the two data sources are effectively the same, unwrapping
|
||||
* proxies as necessary to compare the target instances.
|
||||
* @since 5.3.4
|
||||
* @see TransactionSynchronizationUtils#unwrapResourceIfNecessary(Object)
|
||||
*/
|
||||
private static boolean sameDataSource(DataSource ds1, DataSource ds2) {
|
||||
return TransactionSynchronizationUtils.unwrapResourceIfNecessary(ds1)
|
||||
.equals(TransactionSynchronizationUtils.unwrapResourceIfNecessary(ds2));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DataSource getDataSourceFromTransactionManager(PlatformTransactionManager transactionManager) {
|
||||
try {
|
||||
|
||||
+29
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,6 +18,8 @@ package org.springframework.test.web.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -144,25 +146,42 @@ public abstract class AbstractRequestExpectationManager implements RequestExpect
|
||||
|
||||
@Override
|
||||
public void verify() {
|
||||
if (this.expectations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int count = 0;
|
||||
for (RequestExpectation expectation : this.expectations) {
|
||||
if (!expectation.isSatisfied()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
int count = verifyInternal();
|
||||
if (count > 0) {
|
||||
String message = "Further request(s) expected leaving " + count + " unsatisfied expectation(s).\n";
|
||||
throw new AssertionError(message + getRequestDetails());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(Duration timeout) {
|
||||
Instant endTime = Instant.now().plus(timeout);
|
||||
do {
|
||||
if (verifyInternal() == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
while (Instant.now().isBefore(endTime));
|
||||
verify();
|
||||
}
|
||||
|
||||
private int verifyInternal() {
|
||||
if (this.expectations.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
if (!this.requestFailures.isEmpty()) {
|
||||
throw new AssertionError("Some requests did not execute successfully.\n" +
|
||||
this.requestFailures.entrySet().stream()
|
||||
.map(entry -> "Failed request:\n" + entry.getKey() + "\n" + entry.getValue())
|
||||
.collect(Collectors.joining("\n", "\n", "")));
|
||||
}
|
||||
int count = 0;
|
||||
for (RequestExpectation expectation : this.expectations) {
|
||||
if (!expectation.isSatisfied()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+16
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.test.web.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.BufferingClientHttpRequestFactory;
|
||||
@@ -110,12 +111,25 @@ public final class MockRestServiceServer {
|
||||
/**
|
||||
* Verify that all expected requests set up via
|
||||
* {@link #expect(RequestMatcher)} were indeed performed.
|
||||
* @throws AssertionError when some expectations were not met
|
||||
* @throws AssertionError if not all expectations are met
|
||||
*/
|
||||
public void verify() {
|
||||
this.expectationManager.verify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant of {@link #verify()} that waits for up to the specified time for
|
||||
* all expectations to be fulfilled. This can be useful for tests that
|
||||
* involve asynchronous requests.
|
||||
* @param timeout how long to wait for all expecations to be met
|
||||
* @throws AssertionError if not all expectations are met by the specified
|
||||
* timeout, or if any expectation fails at any time before that.
|
||||
* @since 5.3.4
|
||||
*/
|
||||
public void verify(Duration timeout) {
|
||||
this.expectationManager.verify(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the internal state removing all expectations and recorded requests.
|
||||
*/
|
||||
|
||||
+14
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.test.web.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
|
||||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
@@ -52,11 +53,22 @@ public interface RequestExpectationManager {
|
||||
/**
|
||||
* Verify that all expectations have been met.
|
||||
* <p>This is a delegate for {@link MockRestServiceServer#verify()}.
|
||||
* @throws AssertionError when some expectations were not met
|
||||
* @throws AssertionError if not all expectations are met
|
||||
* @see MockRestServiceServer#verify()
|
||||
*/
|
||||
void verify();
|
||||
|
||||
/**
|
||||
* Variant of {@link #verify()} that waits for up to the specified time for
|
||||
* all expectations to be fulfilled. This can be useful for tests that
|
||||
* involve asynchronous requests.
|
||||
* @param timeout how long to wait for all expecations to be met
|
||||
* @throws AssertionError if not all expectations are met by the specified
|
||||
* timeout, or if any expectation fails at any time before that.
|
||||
* @since 5.3.4
|
||||
*/
|
||||
void verify(Duration timeout);
|
||||
|
||||
/**
|
||||
* Reset the internal state removing all expectations and recorded requests.
|
||||
* <p>This is a delegate for {@link MockRestServiceServer#reset()}.
|
||||
@@ -75,5 +87,4 @@ public interface RequestExpectationManager {
|
||||
* @throws IOException in case of any validation errors
|
||||
*/
|
||||
ClientHttpResponse validateRequest(ClientHttpRequest request) throws IOException;
|
||||
|
||||
}
|
||||
|
||||
+22
-29
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.test.web.servlet.request;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
@@ -38,7 +39,6 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* Default builder for {@link MockMultipartHttpServletRequest}.
|
||||
@@ -144,47 +144,40 @@ public class MockMultipartHttpServletRequestBuilder extends MockHttpServletReque
|
||||
@Override
|
||||
protected final MockHttpServletRequest createServletRequest(ServletContext servletContext) {
|
||||
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest(servletContext);
|
||||
Charset defaultCharset = (request.getCharacterEncoding() != null ?
|
||||
Charset.forName(request.getCharacterEncoding()) : StandardCharsets.UTF_8);
|
||||
|
||||
this.files.forEach(request::addFile);
|
||||
this.parts.values().stream().flatMap(Collection::stream).forEach(part -> {
|
||||
request.addPart(part);
|
||||
try {
|
||||
MultipartFile file = asMultipartFile(part);
|
||||
if (file != null) {
|
||||
request.addFile(file);
|
||||
return;
|
||||
String name = part.getName();
|
||||
String filename = part.getSubmittedFileName();
|
||||
InputStream is = part.getInputStream();
|
||||
if (filename != null) {
|
||||
request.addFile(new MockMultipartFile(name, filename, part.getContentType(), is));
|
||||
}
|
||||
String value = toParameterValue(part);
|
||||
if (value != null) {
|
||||
request.addParameter(part.getName(), toParameterValue(part));
|
||||
else {
|
||||
InputStreamReader reader = new InputStreamReader(is, getCharsetOrDefault(part, defaultCharset));
|
||||
String value = FileCopyUtils.copyToString(reader);
|
||||
request.addParameter(part.getName(), value);
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Failed to read content for part " + part.getName(), ex);
|
||||
}
|
||||
});
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private MultipartFile asMultipartFile(Part part) throws IOException {
|
||||
String name = part.getName();
|
||||
String filename = part.getSubmittedFileName();
|
||||
if (filename != null) {
|
||||
return new MockMultipartFile(name, filename, part.getContentType(), part.getInputStream());
|
||||
private Charset getCharsetOrDefault(Part part, Charset defaultCharset) {
|
||||
if (part.getContentType() != null) {
|
||||
MediaType mediaType = MediaType.parseMediaType(part.getContentType());
|
||||
if (mediaType.getCharset() != null) {
|
||||
return mediaType.getCharset();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return defaultCharset;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String toParameterValue(Part part) throws IOException {
|
||||
String rawType = part.getContentType();
|
||||
MediaType mediaType = (rawType != null ? MediaType.parseMediaType(rawType) : MediaType.TEXT_PLAIN);
|
||||
if (!mediaType.isCompatibleWith(MediaType.TEXT_PLAIN)) {
|
||||
return null;
|
||||
}
|
||||
Charset charset = (mediaType.getCharset() != null ? mediaType.getCharset() : StandardCharsets.UTF_8);
|
||||
InputStreamReader reader = new InputStreamReader(part.getInputStream(), charset);
|
||||
return FileCopyUtils.copyToString(reader);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+36
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,6 +26,8 @@ import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
@@ -57,6 +59,39 @@ class MockHttpServletResponseTests {
|
||||
private MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
|
||||
@ParameterizedTest // gh-26488
|
||||
@ValueSource(strings = {
|
||||
CONTENT_TYPE,
|
||||
CONTENT_LENGTH,
|
||||
CONTENT_LANGUAGE,
|
||||
SET_COOKIE,
|
||||
"enigma"
|
||||
})
|
||||
void addHeaderWithNullValue(String headerName) {
|
||||
response.addHeader(headerName, null);
|
||||
assertThat(response.containsHeader(headerName)).isFalse();
|
||||
}
|
||||
|
||||
@ParameterizedTest // gh-26488
|
||||
@ValueSource(strings = {
|
||||
CONTENT_TYPE,
|
||||
CONTENT_LENGTH,
|
||||
CONTENT_LANGUAGE,
|
||||
SET_COOKIE,
|
||||
"enigma"
|
||||
})
|
||||
void setHeaderWithNullValue(String headerName) {
|
||||
response.setHeader(headerName, null);
|
||||
assertThat(response.containsHeader(headerName)).isFalse();
|
||||
}
|
||||
|
||||
@Test // gh-26493
|
||||
void setLocaleWithNullValue() {
|
||||
assertThat(response.getLocale()).isEqualTo(Locale.getDefault());
|
||||
response.setLocale(null);
|
||||
assertThat(response.getLocale()).isEqualTo(Locale.getDefault());
|
||||
}
|
||||
|
||||
@Test
|
||||
void setContentType() {
|
||||
String contentType = "test/plain";
|
||||
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.context.jdbc;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.InfrastructureProxy;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Transactional integration tests for {@link Sql @Sql} support when the
|
||||
* {@link DataSource} is wrapped in a proxy that implements
|
||||
* {@link InfrastructureProxy}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 5.3.4
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
@DirtiesContext
|
||||
class InfrastructureProxyTransactionalSqlScriptsTests extends AbstractTransactionalTests {
|
||||
|
||||
@BeforeEach
|
||||
void preconditions(@Autowired DataSource dataSource, @Autowired DataSourceTransactionManager transactionManager) {
|
||||
assertThat(dataSource).isNotEqualTo(transactionManager.getDataSource());
|
||||
assertThat(transactionManager.getDataSource()).isNotEqualTo(dataSource);
|
||||
assertThat(transactionManager.getDataSource()).isInstanceOf(InfrastructureProxy.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Sql({ "schema.sql", "data.sql", "data-add-dogbert.sql" })
|
||||
void methodLevelScripts() {
|
||||
assertNumUsers(2);
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class DatabaseConfig {
|
||||
|
||||
@Bean
|
||||
JdbcTemplate jdbcTemplate(DataSource dataSource) {
|
||||
return new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
@Bean
|
||||
PlatformTransactionManager transactionManager(DataSource dataSource) {
|
||||
return new DataSourceTransactionManager(wrapDataSource(dataSource));
|
||||
}
|
||||
|
||||
@Bean
|
||||
DataSource dataSource() {
|
||||
return new EmbeddedDatabaseBuilder()//
|
||||
.setName("empty-sql-scripts-test-db")//
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static DataSource wrapDataSource(DataSource dataSource) {
|
||||
return (DataSource) Proxy.newProxyInstance(
|
||||
InfrastructureProxyTransactionalSqlScriptsTests.class.getClassLoader(),
|
||||
new Class<?>[] { DataSource.class, InfrastructureProxy.class },
|
||||
new DataSourceInvocationHandler(dataSource));
|
||||
}
|
||||
|
||||
|
||||
private static class DataSourceInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
|
||||
DataSourceInvocationHandler(DataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
switch (method.getName()) {
|
||||
case "equals":
|
||||
return (proxy == args[0]);
|
||||
case "hashCode":
|
||||
return System.identityHashCode(proxy);
|
||||
case "getWrappedObject":
|
||||
return this.dataSource;
|
||||
default:
|
||||
try {
|
||||
return method.invoke(this.dataSource, args);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
throw ex.getTargetException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+13
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -74,10 +74,18 @@ class ParallelApplicationEventsIntegrationTests {
|
||||
|
||||
assertThat(payloads).hasSize(10);
|
||||
assertThat(testNames).hasSize(10);
|
||||
// There are probably 10 different thread names on a developer's machine,
|
||||
// but we really just want to assert that at least two different threads
|
||||
// were used, since the CI server seems to have fewer threads available.
|
||||
assertThat(threadNames).hasSizeGreaterThanOrEqualTo(2);
|
||||
|
||||
int availableProcessors = Runtime.getRuntime().availableProcessors();
|
||||
// Skip the following assertion entirely if too few processors are available
|
||||
// to the current JVM.
|
||||
if (availableProcessors >= 6) {
|
||||
// There are probably 10 different thread names on a developer's machine,
|
||||
// but we really just want to assert that at least two different threads
|
||||
// were used, since the CI server often has fewer threads available.
|
||||
assertThat(threadNames)
|
||||
.as("number of threads used with " + availableProcessors + " available processors")
|
||||
.hasSizeGreaterThanOrEqualTo(2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+25
@@ -17,6 +17,7 @@
|
||||
package org.springframework.test.web.client;
|
||||
|
||||
import java.net.SocketException;
|
||||
import java.time.Duration;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -24,6 +25,7 @@ import org.springframework.test.web.client.MockRestServiceServer.MockRestService
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.springframework.http.HttpMethod.POST;
|
||||
import static org.springframework.test.web.client.ExpectedCount.once;
|
||||
@@ -171,4 +173,27 @@ public class MockRestServiceServerTests {
|
||||
.withMessageStartingWith("Some requests did not execute successfully");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyWithTimeout() {
|
||||
MockRestServiceServerBuilder builder = MockRestServiceServer.bindTo(this.restTemplate);
|
||||
|
||||
MockRestServiceServer server1 = builder.build();
|
||||
server1.expect(requestTo("/foo")).andRespond(withSuccess());
|
||||
server1.expect(requestTo("/bar")).andRespond(withSuccess());
|
||||
this.restTemplate.getForObject("/foo", Void.class);
|
||||
|
||||
assertThatThrownBy(() -> server1.verify(Duration.ofMillis(100))).hasMessage(
|
||||
"Further request(s) expected leaving 1 unsatisfied expectation(s).\n" +
|
||||
"1 request(s) executed:\n" +
|
||||
"GET /foo, headers: [Accept:\"application/json, application/*+json\"]\n");
|
||||
|
||||
MockRestServiceServer server2 = builder.build();
|
||||
server2.expect(requestTo("/foo")).andRespond(withSuccess());
|
||||
server2.expect(requestTo("/bar")).andRespond(withSuccess());
|
||||
this.restTemplate.getForObject("/foo", Void.class);
|
||||
this.restTemplate.getForObject("/bar", Void.class);
|
||||
|
||||
server2.verify(Duration.ofMillis(100));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -50,7 +50,7 @@ public class MockMultipartHttpServletRequestBuilderTests {
|
||||
assertThat(mockRequest.getParts()).extracting(Part::getName).containsExactly("name");
|
||||
}
|
||||
|
||||
@Test // gh-26261
|
||||
@Test // gh-26261, gh-26400
|
||||
void addFileWithoutFilename() throws Exception {
|
||||
MockPart jsonPart = new MockPart("data", "{\"node\":\"node\"}".getBytes(UTF_8));
|
||||
jsonPart.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
@@ -62,7 +62,8 @@ public class MockMultipartHttpServletRequestBuilderTests {
|
||||
.buildRequest(new MockServletContext());
|
||||
|
||||
assertThat(mockRequest.getFileMap()).containsOnlyKeys("file");
|
||||
assertThat(mockRequest.getParameterMap()).isEmpty();
|
||||
assertThat(mockRequest.getParameterMap()).hasSize(1);
|
||||
assertThat(mockRequest.getParameter("data")).isEqualTo("{\"node\":\"node\"}");
|
||||
assertThat(mockRequest.getParts()).extracting(Part::getName).containsExactly("data");
|
||||
}
|
||||
|
||||
|
||||
+7
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -45,10 +45,10 @@ public abstract class TransactionSynchronizationUtils {
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the given resource transaction managers refers to the given
|
||||
* Check whether the given resource transaction manager refers to the given
|
||||
* (underlying) resource factory.
|
||||
* @see ResourceTransactionManager#getResourceFactory()
|
||||
* @see org.springframework.core.InfrastructureProxy#getWrappedObject()
|
||||
* @see InfrastructureProxy#getWrappedObject()
|
||||
*/
|
||||
public static boolean sameResourceFactory(ResourceTransactionManager tm, Object resourceFactory) {
|
||||
return unwrapResourceIfNecessary(tm.getResourceFactory()).equals(unwrapResourceIfNecessary(resourceFactory));
|
||||
@@ -57,9 +57,11 @@ public abstract class TransactionSynchronizationUtils {
|
||||
/**
|
||||
* Unwrap the given resource handle if necessary; otherwise return
|
||||
* the given handle as-is.
|
||||
* @see org.springframework.core.InfrastructureProxy#getWrappedObject()
|
||||
* @since 5.3.4
|
||||
* @see InfrastructureProxy#getWrappedObject()
|
||||
* @see ScopedProxyUnwrapper#unwrapIfNecessary(Object)
|
||||
*/
|
||||
static Object unwrapResourceIfNecessary(Object resource) {
|
||||
public static Object unwrapResourceIfNecessary(Object resource) {
|
||||
Assert.notNull(resource, "Resource must not be null");
|
||||
Object resourceRef = resource;
|
||||
// unwrap infrastructure proxy
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,7 +22,10 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -45,6 +48,9 @@ import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
|
||||
*/
|
||||
public final class ContentDisposition {
|
||||
|
||||
private final static Pattern BASE64_ENCODED_PATTERN =
|
||||
Pattern.compile("=\\?([0-9a-zA-Z-_]+)\\?B\\?([+/0-9a-zA-Z]+=*)\\?=");
|
||||
|
||||
private static final String INVALID_HEADER_FIELD_PARAMETER_FORMAT =
|
||||
"Invalid header field parameter format (as defined in RFC 5987)";
|
||||
|
||||
@@ -136,8 +142,9 @@ public final class ContentDisposition {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value of the {@literal filename} parameter (or the value of the
|
||||
* {@literal filename*} one decoded as defined in the RFC 5987), or {@code null} if not defined.
|
||||
* Return the value of the {@literal filename} parameter, possibly decoded
|
||||
* from BASE64 encoding based on RFC 2047, or of the {@literal filename*}
|
||||
* parameter, possibly decoded as defined in the RFC 5987.
|
||||
*/
|
||||
@Nullable
|
||||
public String getFilename() {
|
||||
@@ -362,7 +369,20 @@ public final class ContentDisposition {
|
||||
}
|
||||
}
|
||||
else if (attribute.equals("filename") && (filename == null)) {
|
||||
filename = value;
|
||||
if (value.startsWith("=?") ) {
|
||||
Matcher matcher = BASE64_ENCODED_PATTERN.matcher(value);
|
||||
if (matcher.find()) {
|
||||
String match1 = matcher.group(1);
|
||||
String match2 = matcher.group(2);
|
||||
filename = new String(Base64.getDecoder().decode(match2), Charset.forName(match1));
|
||||
}
|
||||
else {
|
||||
filename = value;
|
||||
}
|
||||
}
|
||||
else {
|
||||
filename = value;
|
||||
}
|
||||
}
|
||||
else if (attribute.equals("size") ) {
|
||||
size = Long.parseLong(value);
|
||||
|
||||
@@ -31,8 +31,8 @@ import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Extension of {@link HttpEntity} that adds a {@link HttpStatus} status code.
|
||||
* Used in {@code RestTemplate} as well {@code @Controller} methods.
|
||||
* Extension of {@link HttpEntity} that adds an {@link HttpStatus} status code.
|
||||
* Used in {@code RestTemplate} as well as in {@code @Controller} methods.
|
||||
*
|
||||
* <p>In {@code RestTemplate}, this class is returned by
|
||||
* {@link org.springframework.web.client.RestTemplate#getForEntity getForEntity()} and
|
||||
@@ -44,7 +44,8 @@ import org.springframework.util.ObjectUtils;
|
||||
* HttpStatus statusCode = entity.getStatusCode();
|
||||
* </pre>
|
||||
*
|
||||
* <p>Can also be used in Spring MVC, as the return value from a @Controller method:
|
||||
* <p>This can also be used in Spring MVC as the return value from an
|
||||
* {@code @Controller} method:
|
||||
* <pre class="code">
|
||||
* @RequestMapping("/handle")
|
||||
* public ResponseEntity<String> handle() {
|
||||
@@ -81,7 +82,7 @@ public class ResponseEntity<T> extends HttpEntity<T> {
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code ResponseEntity} with the given status code, and no body nor headers.
|
||||
* Create an {@code ResponseEntity} with a status code only.
|
||||
* @param status the status code
|
||||
*/
|
||||
public ResponseEntity(HttpStatus status) {
|
||||
@@ -89,7 +90,7 @@ public class ResponseEntity<T> extends HttpEntity<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code ResponseEntity} with the given body and status code, and no headers.
|
||||
* Create an {@code ResponseEntity} with a body and status code.
|
||||
* @param body the entity body
|
||||
* @param status the status code
|
||||
*/
|
||||
@@ -98,7 +99,7 @@ public class ResponseEntity<T> extends HttpEntity<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code HttpEntity} with the given headers and status code, and no body.
|
||||
* Create an {@code HttpEntity} with headers and a status code.
|
||||
* @param headers the entity headers
|
||||
* @param status the status code
|
||||
*/
|
||||
@@ -107,7 +108,7 @@ public class ResponseEntity<T> extends HttpEntity<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code HttpEntity} with the given body, headers, and status code.
|
||||
* Create an {@code HttpEntity} with a body, headers, and a status code.
|
||||
* @param body the entity body
|
||||
* @param headers the entity headers
|
||||
* @param status the status code
|
||||
@@ -117,7 +118,7 @@ public class ResponseEntity<T> extends HttpEntity<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code HttpEntity} with the given body, headers, and status code.
|
||||
* Create an {@code HttpEntity} with a body, headers, and a raw status code.
|
||||
* @param body the entity body
|
||||
* @param headers the entity headers
|
||||
* @param rawStatus the status code value
|
||||
@@ -128,11 +129,7 @@ public class ResponseEntity<T> extends HttpEntity<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code HttpEntity} with the given body, headers, and status code.
|
||||
* Just used behind the nested builder API.
|
||||
* @param body the entity body
|
||||
* @param headers the entity headers
|
||||
* @param status the status code (as {@code HttpStatus} or as {@code Integer} value)
|
||||
* Private constructor.
|
||||
*/
|
||||
private ResponseEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers, Object status) {
|
||||
super(body, headers);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user