mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 91509805b7 | |||
| fceceb480f | |||
| e5ab67bd71 | |||
| 2d53570f4c | |||
| be5eb7037f | |||
| fff2291f2a | |||
| 917f3ad101 | |||
| 2fd6e6e87c | |||
| 570bdbd253 | |||
| b587a16d46 | |||
| dd8ea89bea | |||
| b0dea4d3f6 | |||
| 4ac27e4dab | |||
| b0abdee012 | |||
| e4dc863ad0 | |||
| 689b5566bf | |||
| 234b4719c6 | |||
| ce1ae2f1b2 | |||
| 69ce7d33b9 | |||
| e36d4162c2 | |||
| d387d9ae1e | |||
| 138f6bfd84 | |||
| a1cf6bbc37 | |||
| fb040479eb | |||
| d50375da8e | |||
| fb2e53276c | |||
| c040cd7b05 | |||
| dcc8dcdff8 | |||
| efa360cff7 | |||
| 5804db6479 | |||
| faa749934b | |||
| e1385090e4 | |||
| 3113917c55 | |||
| 994a35d691 | |||
| 8095ba4cd2 | |||
| bd2edf26df | |||
| 4f6d26b25c | |||
| 6d85a8a0cc | |||
| 44b29b6ecf | |||
| 91a6254187 | |||
| c8ad762b40 | |||
| 5c1176786c | |||
| eab61f692f | |||
| ee41ebc1ab | |||
| 885f6dbab9 | |||
| b0f404722e | |||
| cd7b0b11f3 | |||
| 1565f4b83e | |||
| 65a395ef0e | |||
| 947255e377 | |||
| 499be70a71 | |||
| 0cf5005a3d | |||
| f07fc76cf3 | |||
| d3e1c54354 | |||
| a109b4c31a | |||
| 00b56c026a | |||
| fbd2ffdd23 | |||
| b3a47c76f8 | |||
| 1292947f78 | |||
| ef6a582c78 | |||
| a11d1c8510 | |||
| 83c19cd60e | |||
| bcfbde9848 | |||
| 17e6cf1cc1 | |||
| ec33b4241a | |||
| 657641ebaa | |||
| 4597e9b547 | |||
| 721dacca5a | |||
| 9d124fffcb | |||
| 2a47751fcd | |||
| 06e352822a |
@@ -1,4 +1,4 @@
|
||||
# <img src="src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.3.x?groups=Build")
|
||||
# <img src="src/docs/spring-framework.png" width="80" height="80"> Spring Framework [](https://ci.spring.io/teams/spring-framework/pipelines/spring-framework-5.3.x?groups=Build") [](https://ge.spring.io/scans?search.rootProjectNames=spring)
|
||||
|
||||
This is the home of the Spring Framework: the foundation for all [Spring projects](https://spring.io/projects). Collectively the Spring Framework and the family of Spring projects are often referred to simply as "Spring".
|
||||
|
||||
|
||||
+17
-17
@@ -26,8 +26,8 @@ configure(allprojects) { project ->
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.0"
|
||||
mavenBom "io.netty:netty-bom:4.1.54.Final"
|
||||
mavenBom "io.projectreactor:reactor-bom:2020.0.2"
|
||||
mavenBom "io.netty:netty-bom:4.1.56.Final"
|
||||
mavenBom "io.projectreactor:reactor-bom:2020.0.3"
|
||||
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"
|
||||
@@ -63,8 +63,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.19"
|
||||
dependency "io.reactivex.rxjava3:rxjava:3.0.8"
|
||||
dependency "io.reactivex.rxjava2:rxjava:2.2.20"
|
||||
dependency "io.reactivex.rxjava3:rxjava:3.0.9"
|
||||
dependency "io.projectreactor.tools:blockhound:1.0.4.RELEASE"
|
||||
|
||||
dependency "com.caucho:hessian:4.0.63"
|
||||
@@ -95,8 +95,8 @@ configure(allprojects) { project ->
|
||||
}
|
||||
|
||||
dependency "com.h2database:h2:1.4.200"
|
||||
dependency "com.github.ben-manes.caffeine:caffeine:2.8.6"
|
||||
dependency "com.github.librepdf:openpdf:1.3.23"
|
||||
dependency "com.github.ben-manes.caffeine:caffeine:2.8.8"
|
||||
dependency "com.github.librepdf:openpdf:1.3.24"
|
||||
dependency "com.rometools:rome:1.15.0"
|
||||
dependency "commons-io:commons-io:2.5"
|
||||
dependency "io.vavr:vavr:0.10.3"
|
||||
@@ -123,19 +123,19 @@ 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.25.Final"
|
||||
dependency "org.hibernate:hibernate-validator:6.1.6.Final"
|
||||
dependency "org.hibernate:hibernate-core:5.4.27.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.40') {
|
||||
dependencySet(group: 'org.apache.tomcat', version: '9.0.41') {
|
||||
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.40') {
|
||||
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.41') {
|
||||
entry 'tomcat-embed-core'
|
||||
entry 'tomcat-embed-websocket'
|
||||
}
|
||||
@@ -162,7 +162,7 @@ configure(allprojects) { project ->
|
||||
}
|
||||
dependency 'org.apache.httpcomponents.client5:httpclient5:5.0.3'
|
||||
dependency 'org.apache.httpcomponents.core5:httpcore5-reactive:5.0.3'
|
||||
dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.4"
|
||||
dependency "org.eclipse.jetty:jetty-reactive-httpclient:1.1.5"
|
||||
|
||||
dependency "org.jruby:jruby:9.2.13.0"
|
||||
dependency "org.python:jython-standalone:2.7.1"
|
||||
@@ -191,13 +191,13 @@ configure(allprojects) { project ->
|
||||
dependency "org.hamcrest:hamcrest:2.1"
|
||||
dependency "org.awaitility:awaitility:3.1.6"
|
||||
dependency "org.assertj:assertj-core:3.18.1"
|
||||
dependencySet(group: 'org.xmlunit', version: '2.6.2') {
|
||||
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.6.28') {
|
||||
dependencySet(group: 'org.mockito', version: '3.7.0') {
|
||||
entry('mockito-core') {
|
||||
exclude group: "org.hamcrest", name: "hamcrest-core"
|
||||
}
|
||||
@@ -205,10 +205,10 @@ configure(allprojects) { project ->
|
||||
}
|
||||
dependency "io.mockk:mockk:1.10.2"
|
||||
|
||||
dependency("net.sourceforge.htmlunit:htmlunit:2.45.0") {
|
||||
dependency("net.sourceforge.htmlunit:htmlunit:2.46.0") {
|
||||
exclude group: "commons-logging", name: "commons-logging"
|
||||
}
|
||||
dependency("org.seleniumhq.selenium:htmlunit-driver:2.45.0") {
|
||||
dependency("org.seleniumhq.selenium:htmlunit-driver:2.46.0") {
|
||||
exclude group: "commons-logging", name: "commons-logging"
|
||||
}
|
||||
dependency("org.seleniumhq.selenium:selenium-java:3.141.59") {
|
||||
@@ -236,7 +236,7 @@ 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.6"
|
||||
dependency "joda-time:joda-time:2.10.9"
|
||||
dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.7"
|
||||
dependency "org.javamoney:moneta:1.3"
|
||||
|
||||
@@ -339,7 +339,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "8.38"
|
||||
toolVersion = "8.39"
|
||||
configDirectory.set(rootProject.file("src/checkstyle"))
|
||||
}
|
||||
|
||||
|
||||
@@ -3,16 +3,13 @@ set -e
|
||||
|
||||
case "$1" in
|
||||
java8)
|
||||
echo "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u265-b01/OpenJDK8U-jdk_x64_linux_hotspot_8u265b01.tar.gz"
|
||||
echo "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u275-b01/OpenJDK8U-jdk_x64_linux_hotspot_8u275b01.tar.gz"
|
||||
;;
|
||||
java11)
|
||||
echo "https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.8%2B10/OpenJDK11U-jdk_x64_linux_hotspot_11.0.8_10.tar.gz"
|
||||
;;
|
||||
java14)
|
||||
echo "https://github.com/AdoptOpenJDK/openjdk14-binaries/releases/download/jdk-14.0.2%2B12/OpenJDK14U-jdk_x64_linux_hotspot_14.0.2_12.tar.gz"
|
||||
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"
|
||||
;;
|
||||
java15)
|
||||
echo "https://github.com/AdoptOpenJDK/openjdk15-binaries/releases/download/jdk-15%2B36/OpenJDK15U-jdk_x64_linux_hotspot_15_36.tar.gz"
|
||||
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 $"Unknown java version"
|
||||
|
||||
+4
-1
@@ -5,8 +5,11 @@ set -ex
|
||||
# UTILS
|
||||
###########################################################
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install --no-install-recommends -y ca-certificates net-tools libxml2-utils git curl libudev1 libxml2-utils iptables iproute2 jq fontconfig
|
||||
apt-get install --no-install-recommends -y tzdata ca-certificates net-tools libxml2-utils git curl libudev1 libxml2-utils iptables iproute2 jq fontconfig
|
||||
ln -fs /usr/share/zoneinfo/UTC /etc/localtime
|
||||
dpkg-reconfigure --frontend noninteractive tzdata
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
curl https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.3/concourse-java.sh > /opt/concourse-java.sh
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:bionic-20200713
|
||||
FROM ubuntu:focal-20201106
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:bionic-20200713
|
||||
FROM ubuntu:focal-20201106
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
FROM ubuntu:bionic-20200713
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
RUN ./setup.sh java14
|
||||
|
||||
ENV JAVA_HOME /opt/openjdk
|
||||
ENV PATH $JAVA_HOME/bin:$PATH
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:bionic-20200713
|
||||
FROM ubuntu:focal-20201106
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
|
||||
+1
-47
@@ -87,12 +87,6 @@ resources:
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: ((docker-hub-organization))/spring-framework-jdk11-ci-image
|
||||
- name: spring-framework-jdk14-ci-image
|
||||
type: docker-image
|
||||
icon: docker
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: ((docker-hub-organization))/spring-framework-jdk14-ci-image
|
||||
- name: spring-framework-jdk15-ci-image
|
||||
type: docker-image
|
||||
icon: docker
|
||||
@@ -123,14 +117,6 @@ resources:
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: jdk11-build
|
||||
- name: repo-status-jdk14-build
|
||||
type: github-status-resource
|
||||
icon: eye-check-outline
|
||||
source:
|
||||
repository: ((github-repo-name))
|
||||
access_token: ((github-ci-status-token))
|
||||
branch: ((branch))
|
||||
context: jdk14-build
|
||||
- name: repo-status-jdk15-build
|
||||
type: github-status-resource
|
||||
icon: eye-check-outline
|
||||
@@ -176,10 +162,6 @@ jobs:
|
||||
params:
|
||||
build: ci-images-git-repo/ci/images
|
||||
dockerfile: ci-images-git-repo/ci/images/spring-framework-jdk11-ci-image/Dockerfile
|
||||
- put: spring-framework-jdk14-ci-image
|
||||
params:
|
||||
build: ci-images-git-repo/ci/images
|
||||
dockerfile: ci-images-git-repo/ci/images/spring-framework-jdk14-ci-image/Dockerfile
|
||||
- put: spring-framework-jdk15-ci-image
|
||||
params:
|
||||
build: ci-images-git-repo/ci/images
|
||||
@@ -268,34 +250,6 @@ jobs:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-jdk11-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- name: jdk14-build
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
- get: spring-framework-jdk14-ci-image
|
||||
- get: git-repo
|
||||
- get: every-morning
|
||||
trigger: true
|
||||
- put: repo-status-jdk14-build
|
||||
params: { state: "pending", commit: "git-repo" }
|
||||
- do:
|
||||
- task: check-project
|
||||
privileged: true
|
||||
timeout: ((task-timeout))
|
||||
image: spring-framework-jdk14-ci-image
|
||||
file: git-repo/ci/tasks/check-project.yml
|
||||
params:
|
||||
BRANCH: ((branch))
|
||||
<<: *gradle-enterprise-task-params
|
||||
on_failure:
|
||||
do:
|
||||
- put: repo-status-jdk14-build
|
||||
params: { state: "failure", commit: "git-repo" }
|
||||
- put: slack-alert
|
||||
params:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-jdk14-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- name: jdk15-build
|
||||
serial: true
|
||||
public: true
|
||||
@@ -480,7 +434,7 @@ jobs:
|
||||
|
||||
groups:
|
||||
- name: "builds"
|
||||
jobs: ["build", "jdk11-build", "jdk14-build", "jdk15-build"]
|
||||
jobs: ["build", "jdk11-build", "jdk15-build"]
|
||||
- name: "releases"
|
||||
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone","promote-rc", "promote-release", "sync-to-maven-central"]
|
||||
- name: "ci-images"
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=5.3.2
|
||||
version=5.3.3
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
|
||||
@@ -48,6 +48,7 @@ import org.springframework.cglib.proxy.Factory;
|
||||
import org.springframework.cglib.proxy.MethodInterceptor;
|
||||
import org.springframework.cglib.proxy.MethodProxy;
|
||||
import org.springframework.cglib.proxy.NoOp;
|
||||
import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.core.SmartClassLoader;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -752,10 +753,17 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
throw ex;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (ReflectionUtils.declaresException(getMethod(), ex.getClass())) {
|
||||
if (ReflectionUtils.declaresException(getMethod(), ex.getClass()) ||
|
||||
KotlinDetector.isKotlinType(getMethod().getDeclaringClass())) {
|
||||
// Propagate original exception if declared on the target method
|
||||
// (with callers expecting it). Always propagate it for Kotlin code
|
||||
// since checked exceptions do not have to be explicitly declared there.
|
||||
throw ex;
|
||||
}
|
||||
else {
|
||||
// Checked exception thrown in the interceptor but not declared on the
|
||||
// target method signature -> apply an UndeclaredThrowableException,
|
||||
// aligned with standard JDK dynamic proxy behavior.
|
||||
throw new UndeclaredThrowableException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,6 +543,7 @@ public abstract class BeanUtils {
|
||||
if (targetType == null || targetType.isArray() || unknownEditorTypes.contains(targetType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ClassLoader cl = targetType.getClassLoader();
|
||||
if (cl == null) {
|
||||
try {
|
||||
@@ -559,28 +560,34 @@ public abstract class BeanUtils {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String targetTypeName = targetType.getName();
|
||||
String editorName = targetTypeName + "Editor";
|
||||
try {
|
||||
Class<?> editorClass = cl.loadClass(editorName);
|
||||
if (!PropertyEditor.class.isAssignableFrom(editorClass)) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Editor class [" + editorName +
|
||||
"] does not implement [java.beans.PropertyEditor] interface");
|
||||
if (editorClass != null) {
|
||||
if (!PropertyEditor.class.isAssignableFrom(editorClass)) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Editor class [" + editorName +
|
||||
"] does not implement [java.beans.PropertyEditor] interface");
|
||||
}
|
||||
unknownEditorTypes.add(targetType);
|
||||
return null;
|
||||
}
|
||||
unknownEditorTypes.add(targetType);
|
||||
return null;
|
||||
return (PropertyEditor) instantiateClass(editorClass);
|
||||
}
|
||||
return (PropertyEditor) instantiateClass(editorClass);
|
||||
// Misbehaving ClassLoader returned null instead of ClassNotFoundException
|
||||
// - fall back to unknown editor type registration below
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("No property editor [" + editorName + "] found for type " +
|
||||
targetTypeName + " according to 'Editor' suffix convention");
|
||||
}
|
||||
unknownEditorTypes.add(targetType);
|
||||
return null;
|
||||
// Ignore - fall back to unknown editor type registration below
|
||||
}
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("No property editor [" + editorName + "] found for type " +
|
||||
targetTypeName + " according to 'Editor' suffix convention");
|
||||
}
|
||||
unknownEditorTypes.add(targetType);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+12
-7
@@ -250,7 +250,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
throws BeansException {
|
||||
|
||||
String beanName = transformedBeanName(name);
|
||||
Object bean;
|
||||
Object beanInstance;
|
||||
|
||||
// Eagerly check singleton cache for manually registered singletons.
|
||||
Object sharedInstance = getSingleton(beanName);
|
||||
@@ -264,7 +264,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
|
||||
}
|
||||
}
|
||||
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
|
||||
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
|
||||
}
|
||||
|
||||
else {
|
||||
@@ -342,7 +342,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
throw ex;
|
||||
}
|
||||
});
|
||||
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
|
||||
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
|
||||
}
|
||||
|
||||
else if (mbd.isPrototype()) {
|
||||
@@ -355,7 +355,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
finally {
|
||||
afterPrototypeCreation(beanName);
|
||||
}
|
||||
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
|
||||
beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
|
||||
}
|
||||
|
||||
else {
|
||||
@@ -377,7 +377,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
afterPrototypeCreation(beanName);
|
||||
}
|
||||
});
|
||||
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
|
||||
beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
throw new ScopeNotActiveException(beanName, scopeName, ex);
|
||||
@@ -395,14 +395,19 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
}
|
||||
}
|
||||
|
||||
return adaptBeanInstance(name, beanInstance, requiredType);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<T> T adaptBeanInstance(String name, Object bean, @Nullable Class<?> requiredType) {
|
||||
// Check if required type matches the type of the actual bean instance.
|
||||
if (requiredType != null && !requiredType.isInstance(bean)) {
|
||||
try {
|
||||
T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
|
||||
Object convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
|
||||
if (convertedBean == null) {
|
||||
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
|
||||
}
|
||||
return convertedBean;
|
||||
return (T) convertedBean;
|
||||
}
|
||||
catch (TypeMismatchException ex) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
|
||||
+17
-4
@@ -1231,8 +1231,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
|
||||
if (candidateNames.length == 1) {
|
||||
String beanName = candidateNames[0];
|
||||
return new NamedBeanHolder<>(beanName, (T) getBean(beanName, requiredType.toClass(), args));
|
||||
return resolveNamedBean(candidateNames[0], requiredType, args);
|
||||
}
|
||||
else if (candidateNames.length > 1) {
|
||||
Map<String, Object> candidates = CollectionUtils.newLinkedHashMap(candidateNames.length);
|
||||
@@ -1251,8 +1250,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
if (candidateName != null) {
|
||||
Object beanInstance = candidates.get(candidateName);
|
||||
if (beanInstance == null || beanInstance instanceof Class) {
|
||||
beanInstance = getBean(candidateName, requiredType.toClass(), args);
|
||||
if (beanInstance == null) {
|
||||
return null;
|
||||
}
|
||||
if (beanInstance instanceof Class) {
|
||||
return resolveNamedBean(candidateName, requiredType, args);
|
||||
}
|
||||
return new NamedBeanHolder<>(candidateName, (T) beanInstance);
|
||||
}
|
||||
@@ -1264,6 +1266,17 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private <T> NamedBeanHolder<T> resolveNamedBean(
|
||||
String beanName, ResolvableType requiredType, @Nullable Object[] args) throws BeansException {
|
||||
|
||||
Object bean = getBean(beanName, null, args);
|
||||
if (bean instanceof NullBean) {
|
||||
return null;
|
||||
}
|
||||
return new NamedBeanHolder<T>(beanName, adaptBeanInstance(beanName, bean, requiredType.toClass()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
|
||||
|
||||
+6
-3
@@ -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.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.context;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.ResolvableTypeProvider;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -23,11 +25,12 @@ import org.springframework.util.Assert;
|
||||
/**
|
||||
* An {@link ApplicationEvent} that carries an arbitrary payload.
|
||||
*
|
||||
* <p>Mainly intended for internal use within the framework.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.2
|
||||
* @param <T> the payload type of the event
|
||||
* @see ApplicationEventPublisher#publishEvent(Object)
|
||||
* @see ApplicationListener#forPayload(Consumer)
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class PayloadApplicationEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {
|
||||
|
||||
+16
-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.
|
||||
@@ -24,6 +24,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.PayloadApplicationEvent;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ErrorHandler;
|
||||
@@ -55,6 +56,9 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
|
||||
@Nullable
|
||||
private ErrorHandler errorHandler;
|
||||
|
||||
@Nullable
|
||||
private volatile Log lazyLogger;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new SimpleApplicationEventMulticaster.
|
||||
@@ -173,12 +177,18 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
|
||||
}
|
||||
catch (ClassCastException ex) {
|
||||
String msg = ex.getMessage();
|
||||
if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
|
||||
if (msg == null || matchesClassCastMessage(msg, event.getClass()) ||
|
||||
(event instanceof PayloadApplicationEvent &&
|
||||
matchesClassCastMessage(msg, ((PayloadApplicationEvent) event).getPayload().getClass()))) {
|
||||
// Possibly a lambda-defined listener which we could not resolve the generic event type for
|
||||
// -> let's suppress the exception and just log a debug message.
|
||||
Log logger = LogFactory.getLog(getClass());
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Non-matching event type for listener: " + listener, ex);
|
||||
// -> let's suppress the exception.
|
||||
Log loggerToUse = this.lazyLogger;
|
||||
if (loggerToUse == null) {
|
||||
loggerToUse = LogFactory.getLog(getClass());
|
||||
this.lazyLogger = loggerToUse;
|
||||
}
|
||||
if (loggerToUse.isTraceEnabled()) {
|
||||
loggerToUse.trace("Non-matching event type for listener: " + listener, ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
+3
-4
@@ -19,14 +19,13 @@ package org.springframework.scheduling.support;
|
||||
import java.time.DateTimeException;
|
||||
import java.time.temporal.Temporal;
|
||||
import java.time.temporal.ValueRange;
|
||||
import java.util.BitSet;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Efficient {@link BitSet}-based extension of {@link CronField}.
|
||||
* Efficient bitwise-operator extension of {@link CronField}.
|
||||
* Created using the {@code parse*} methods.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
@@ -130,8 +129,8 @@ final class BitsCronField extends CronField {
|
||||
result.setBits(range);
|
||||
}
|
||||
else {
|
||||
String rangeStr = value.substring(0, slashPos);
|
||||
String deltaStr = value.substring(slashPos + 1);
|
||||
String rangeStr = field.substring(0, slashPos);
|
||||
String deltaStr = field.substring(slashPos + 1);
|
||||
ValueRange range = parseRange(rangeStr, type);
|
||||
if (rangeStr.indexOf('-') == -1) {
|
||||
range = ValueRange.of(range.getMinimum(), type.range().getMaximum());
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.scheduling.support;
|
||||
|
||||
import java.time.temporal.Temporal;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Extension of {@link CronField} that wraps an array of cron fields.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 5.3.3
|
||||
*/
|
||||
final class CompositeCronField extends CronField {
|
||||
|
||||
private final CronField[] fields;
|
||||
|
||||
private final String value;
|
||||
|
||||
|
||||
private CompositeCronField(Type type, CronField[] fields, String value) {
|
||||
super(type);
|
||||
this.fields = fields;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes the given fields into a {@link CronField}.
|
||||
*/
|
||||
public static CronField compose(CronField[] fields, Type type, String value) {
|
||||
Assert.notEmpty(fields, "Fields must not be empty");
|
||||
Assert.hasLength(value, "Value must not be empty");
|
||||
|
||||
if (fields.length == 1) {
|
||||
return fields[0];
|
||||
}
|
||||
else {
|
||||
return new CompositeCronField(type, fields, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T extends Temporal & Comparable<? super T>> T nextOrSame(T temporal) {
|
||||
T result = null;
|
||||
for (CronField field : this.fields) {
|
||||
T candidate = field.nextOrSame(temporal);
|
||||
if (result == null ||
|
||||
candidate != null && candidate.compareTo(result) < 0) {
|
||||
result = candidate;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.value.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof CompositeCronField)) {
|
||||
return false;
|
||||
}
|
||||
CompositeCronField other = (CompositeCronField) o;
|
||||
return type() == other.type() &&
|
||||
this.value.equals(other.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return type() + " '" + this.value + "'";
|
||||
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,10 @@ import java.time.DateTimeException;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.time.temporal.Temporal;
|
||||
import java.time.temporal.ValueRange;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -77,11 +79,18 @@ abstract class CronField {
|
||||
* Parse the given value into a days of months {@code CronField}, the fourth entry of a cron expression.
|
||||
*/
|
||||
public static CronField parseDaysOfMonth(String value) {
|
||||
if (value.contains("L") || value.contains("W")) {
|
||||
return QuartzCronField.parseDaysOfMonth(value);
|
||||
if (!QuartzCronField.isQuartzDaysOfMonthField(value)) {
|
||||
return BitsCronField.parseDaysOfMonth(value);
|
||||
}
|
||||
else {
|
||||
return BitsCronField.parseDaysOfMonth(value);
|
||||
return parseList(value, Type.DAY_OF_MONTH, (field, type) -> {
|
||||
if (QuartzCronField.isQuartzDaysOfMonthField(field)) {
|
||||
return QuartzCronField.parseDaysOfMonth(field);
|
||||
}
|
||||
else {
|
||||
return BitsCronField.parseDaysOfMonth(field);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,15 +107,32 @@ abstract class CronField {
|
||||
*/
|
||||
public static CronField parseDaysOfWeek(String value) {
|
||||
value = replaceOrdinals(value, DAYS);
|
||||
if (value.contains("L") || value.contains("#")) {
|
||||
return QuartzCronField.parseDaysOfWeek(value);
|
||||
if (!QuartzCronField.isQuartzDaysOfWeekField(value)) {
|
||||
return BitsCronField.parseDaysOfWeek(value);
|
||||
}
|
||||
else {
|
||||
return BitsCronField.parseDaysOfWeek(value);
|
||||
return parseList(value, Type.DAY_OF_WEEK, (field, type) -> {
|
||||
if (QuartzCronField.isQuartzDaysOfWeekField(field)) {
|
||||
return QuartzCronField.parseDaysOfWeek(field);
|
||||
}
|
||||
else {
|
||||
return BitsCronField.parseDaysOfWeek(field);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static CronField parseList(String value, Type type, BiFunction<String, Type, CronField> parseFieldFunction) {
|
||||
Assert.hasLength(value, "Value must not be empty");
|
||||
String[] fields = StringUtils.delimitedListToStringArray(value, ",");
|
||||
CronField[] cronFields = new CronField[fields.length];
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
cronFields[i] = parseFieldFunction.apply(fields[i], type);
|
||||
}
|
||||
return CompositeCronField.compose(cronFields, type, value);
|
||||
}
|
||||
|
||||
private static String replaceOrdinals(String value, String[] list) {
|
||||
value = value.toUpperCase();
|
||||
for (int i = 0; i < list.length; i++) {
|
||||
|
||||
+13
@@ -78,6 +78,12 @@ final class QuartzCronField extends CronField {
|
||||
this.rollForwardType = rollForwardType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given value is a Quartz day-of-month field.
|
||||
*/
|
||||
public static boolean isQuartzDaysOfMonthField(String value) {
|
||||
return value.contains("L") || value.contains("W");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given value into a days of months {@code QuartzCronField}, the fourth entry of a cron expression.
|
||||
@@ -125,6 +131,13 @@ final class QuartzCronField extends CronField {
|
||||
throw new IllegalArgumentException("No 'L' or 'W' found in '" + value + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given value is a Quartz day-of-week field.
|
||||
*/
|
||||
public static boolean isQuartzDaysOfWeekField(String value) {
|
||||
return value.contains("L") || value.contains("#");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given value into a days of week {@code QuartzCronField}, the sixth entry of a cron expression.
|
||||
* Expects a "L" or "#" in the given value.
|
||||
|
||||
+6
@@ -271,9 +271,15 @@ class AnnotationConfigApplicationContextTests {
|
||||
assertThat(ObjectUtils.containsElement(context.getBeanNamesForType(BeanA.class), "a")).isTrue();
|
||||
assertThat(ObjectUtils.containsElement(context.getBeanNamesForType(BeanB.class), "b")).isTrue();
|
||||
assertThat(ObjectUtils.containsElement(context.getBeanNamesForType(BeanC.class), "c")).isTrue();
|
||||
|
||||
assertThat(context.getBeansOfType(BeanA.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(BeanB.class).values().iterator().next()).isSameAs(context.getBean(BeanB.class));
|
||||
assertThat(context.getBeansOfType(BeanC.class).values().iterator().next()).isSameAs(context.getBean(BeanC.class));
|
||||
|
||||
assertThatExceptionOfType(NoSuchBeanDefinitionException.class).isThrownBy(() ->
|
||||
context.getBeanFactory().resolveNamedBean(BeanA.class));
|
||||
assertThat(context.getBeanFactory().resolveNamedBean(BeanB.class).getBeanInstance()).isSameAs(context.getBean(BeanB.class));
|
||||
assertThat(context.getBeanFactory().resolveNamedBean(BeanC.class).getBeanInstance()).isSameAs(context.getBean(BeanC.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+129
-91
@@ -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,8 +18,10 @@ package org.springframework.context.annotation;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
@@ -31,6 +33,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
@@ -47,13 +50,13 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Phillip Webb
|
||||
* @author Sam Brannen
|
||||
* @since 3.1
|
||||
*/
|
||||
public class PropertySourceAnnotationTests {
|
||||
|
||||
class PropertySourceAnnotationTests {
|
||||
|
||||
@Test
|
||||
public void withExplicitName() {
|
||||
void withExplicitName() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.register(ConfigWithExplicitName.class);
|
||||
ctx.refresh();
|
||||
@@ -73,29 +76,25 @@ public class PropertySourceAnnotationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withImplicitName() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.register(ConfigWithImplicitName.class);
|
||||
ctx.refresh();
|
||||
void withImplicitName() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithImplicitName.class);
|
||||
assertThat(ctx.getEnvironment().getPropertySources().contains("class path resource [org/springframework/context/annotation/p1.properties]")).as("property source p1 was not added").isTrue();
|
||||
assertThat(ctx.getBean(TestBean.class).getName()).isEqualTo("p1TestBean");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withTestProfileBeans() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.register(ConfigWithTestProfileBeans.class);
|
||||
ctx.refresh();
|
||||
void withTestProfileBeans() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithTestProfileBeans.class);
|
||||
assertThat(ctx.containsBean("testBean")).isTrue();
|
||||
assertThat(ctx.containsBean("testProfileBean")).isTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the LIFO behavior of @PropertySource annotaitons.
|
||||
* The last one registered should 'win'.
|
||||
* Tests the LIFO behavior of @PropertySource annotations.
|
||||
* <p>The last one registered should 'win'.
|
||||
*/
|
||||
@Test
|
||||
public void orderingIsLifo() {
|
||||
void orderingIsLifo() {
|
||||
{
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.register(ConfigWithImplicitName.class, P2Config.class);
|
||||
@@ -114,7 +113,7 @@ public class PropertySourceAnnotationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withCustomFactory() {
|
||||
void withCustomFactory() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.register(ConfigWithImplicitName.class, WithCustomFactory.class);
|
||||
ctx.refresh();
|
||||
@@ -122,7 +121,7 @@ public class PropertySourceAnnotationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withCustomFactoryAsMeta() {
|
||||
void withCustomFactoryAsMeta() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.register(ConfigWithImplicitName.class, WithCustomFactoryAsMeta.class);
|
||||
ctx.refresh();
|
||||
@@ -130,59 +129,43 @@ public class PropertySourceAnnotationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withUnresolvablePlaceholder() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.register(ConfigWithUnresolvablePlaceholder.class);
|
||||
try {
|
||||
ctx.refresh();
|
||||
}
|
||||
catch (BeanDefinitionStoreException ex) {
|
||||
assertThat(ex.getCause() instanceof IllegalArgumentException).isTrue();
|
||||
}
|
||||
void withUnresolvablePlaceholder() {
|
||||
assertThatExceptionOfType(BeanDefinitionStoreException.class)
|
||||
.isThrownBy(() -> new AnnotationConfigApplicationContext(ConfigWithUnresolvablePlaceholder.class))
|
||||
.withCauseInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withUnresolvablePlaceholderAndDefault() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.register(ConfigWithUnresolvablePlaceholderAndDefault.class);
|
||||
ctx.refresh();
|
||||
void withUnresolvablePlaceholderAndDefault() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithUnresolvablePlaceholderAndDefault.class);
|
||||
assertThat(ctx.getBean(TestBean.class).getName()).isEqualTo("p1TestBean");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withResolvablePlaceholder() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.register(ConfigWithResolvablePlaceholder.class);
|
||||
void withResolvablePlaceholder() {
|
||||
System.setProperty("path.to.properties", "org/springframework/context/annotation");
|
||||
ctx.refresh();
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithResolvablePlaceholder.class);
|
||||
assertThat(ctx.getBean(TestBean.class).getName()).isEqualTo("p1TestBean");
|
||||
System.clearProperty("path.to.properties");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withResolvablePlaceholderAndFactoryBean() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.register(ConfigWithResolvablePlaceholderAndFactoryBean.class);
|
||||
void withResolvablePlaceholderAndFactoryBean() {
|
||||
System.setProperty("path.to.properties", "org/springframework/context/annotation");
|
||||
ctx.refresh();
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithResolvablePlaceholderAndFactoryBean.class);
|
||||
assertThat(ctx.getBean(TestBean.class).getName()).isEqualTo("p1TestBean");
|
||||
System.clearProperty("path.to.properties");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withEmptyResourceLocations() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.register(ConfigWithEmptyResourceLocations.class);
|
||||
try {
|
||||
ctx.refresh();
|
||||
}
|
||||
catch (BeanDefinitionStoreException ex) {
|
||||
assertThat(ex.getCause() instanceof IllegalArgumentException).isTrue();
|
||||
}
|
||||
void withEmptyResourceLocations() {
|
||||
assertThatExceptionOfType(BeanDefinitionStoreException.class)
|
||||
.isThrownBy(() -> new AnnotationConfigApplicationContext(ConfigWithEmptyResourceLocations.class))
|
||||
.withCauseInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withNameAndMultipleResourceLocations() {
|
||||
void withNameAndMultipleResourceLocations() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithNameAndMultipleResourceLocations.class);
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p1")).isTrue();
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p2")).isTrue();
|
||||
@@ -191,7 +174,7 @@ public class PropertySourceAnnotationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withMultipleResourceLocations() {
|
||||
void withMultipleResourceLocations() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithMultipleResourceLocations.class);
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p1")).isTrue();
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p2")).isTrue();
|
||||
@@ -200,7 +183,7 @@ public class PropertySourceAnnotationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withPropertySources() {
|
||||
void withRepeatedPropertySourcesInContainerAnnotation() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithPropertySources.class);
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p1")).isTrue();
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p2")).isTrue();
|
||||
@@ -209,7 +192,43 @@ public class PropertySourceAnnotationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withNamedPropertySources() {
|
||||
void withRepeatedPropertySources() {
|
||||
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithRepeatedPropertySourceAnnotations.class)) {
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p1")).isTrue();
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p2")).isTrue();
|
||||
// p2 should 'win' as it was registered last
|
||||
assertThat(ctx.getEnvironment().getProperty("testbean.name")).isEqualTo("p2TestBean");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void withRepeatedPropertySourcesOnComposedAnnotation() {
|
||||
Class<?> configClass = ConfigWithRepeatedPropertySourceAnnotationsOnComposedAnnotation.class;
|
||||
String key = "custom.config.package";
|
||||
|
||||
System.clearProperty(key);
|
||||
try (ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(configClass)) {
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p1")).isTrue();
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p2")).isTrue();
|
||||
// p2 should 'win' as it was registered last
|
||||
assertThat(ctx.getEnvironment().getProperty("testbean.name")).isEqualTo("p2TestBean");
|
||||
}
|
||||
|
||||
System.setProperty(key, "org/springframework/context/annotation");
|
||||
try (ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(configClass)) {
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p1")).isTrue();
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p2")).isTrue();
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p3")).isTrue();
|
||||
// p3 should 'win' as it was registered last
|
||||
assertThat(ctx.getEnvironment().getProperty("testbean.name")).isEqualTo("p3TestBean");
|
||||
}
|
||||
finally {
|
||||
System.clearProperty(key);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNamedPropertySources() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithNamedPropertySources.class);
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p1")).isTrue();
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p2")).isTrue();
|
||||
@@ -218,21 +237,21 @@ public class PropertySourceAnnotationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withMissingPropertySource() {
|
||||
assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy(() ->
|
||||
new AnnotationConfigApplicationContext(ConfigWithMissingPropertySource.class))
|
||||
void withMissingPropertySource() {
|
||||
assertThatExceptionOfType(BeanDefinitionStoreException.class)
|
||||
.isThrownBy(() -> new AnnotationConfigApplicationContext(ConfigWithMissingPropertySource.class))
|
||||
.withCauseInstanceOf(FileNotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withIgnoredPropertySource() {
|
||||
void withIgnoredPropertySource() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithIgnoredPropertySource.class);
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p1")).isTrue();
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p2")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withSameSourceImportedInDifferentOrder() {
|
||||
void withSameSourceImportedInDifferentOrder() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithSameSourceImportedInDifferentOrder.class);
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p1")).isTrue();
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p2")).isTrue();
|
||||
@@ -240,7 +259,7 @@ public class PropertySourceAnnotationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void orderingWithAndWithoutNameAndMultipleResourceLocations() {
|
||||
void orderingWithAndWithoutNameAndMultipleResourceLocations() {
|
||||
// SPR-10820: p2 should 'win' as it was registered last
|
||||
AnnotationConfigApplicationContext ctxWithName = new AnnotationConfigApplicationContext(ConfigWithNameAndMultipleResourceLocations.class);
|
||||
AnnotationConfigApplicationContext ctxWithoutName = new AnnotationConfigApplicationContext(ConfigWithMultipleResourceLocations.class);
|
||||
@@ -249,14 +268,14 @@ public class PropertySourceAnnotationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void orderingWithAndWithoutNameAndFourResourceLocations() {
|
||||
void orderingWithAndWithoutNameAndFourResourceLocations() {
|
||||
// SPR-12198: p4 should 'win' as it was registered last
|
||||
AnnotationConfigApplicationContext ctxWithoutName = new AnnotationConfigApplicationContext(ConfigWithFourResourceLocations.class);
|
||||
assertThat(ctxWithoutName.getEnvironment().getProperty("testbean.name")).isEqualTo("p4TestBean");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void orderingDoesntReplaceExisting() throws Exception {
|
||||
void orderingDoesntReplaceExisting() throws Exception {
|
||||
// SPR-12198: mySource should 'win' as it was registered manually
|
||||
AnnotationConfigApplicationContext ctxWithoutName = new AnnotationConfigApplicationContext();
|
||||
MapPropertySource mySource = new MapPropertySource("mine", Collections.singletonMap("testbean.name", "myTestBean"));
|
||||
@@ -267,51 +286,51 @@ public class PropertySourceAnnotationTests {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@PropertySource(value="classpath:${unresolvable}/p1.properties")
|
||||
@PropertySource("classpath:${unresolvable}/p1.properties")
|
||||
static class ConfigWithUnresolvablePlaceholder {
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@PropertySource(value="classpath:${unresolvable:org/springframework/context/annotation}/p1.properties")
|
||||
@PropertySource("classpath:${unresolvable:org/springframework/context/annotation}/p1.properties")
|
||||
static class ConfigWithUnresolvablePlaceholderAndDefault {
|
||||
|
||||
@Inject Environment env;
|
||||
|
||||
@Bean
|
||||
public TestBean testBean() {
|
||||
TestBean testBean() {
|
||||
return new TestBean(env.getProperty("testbean.name"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@PropertySource(value="classpath:${path.to.properties}/p1.properties")
|
||||
@PropertySource("classpath:${path.to.properties}/p1.properties")
|
||||
static class ConfigWithResolvablePlaceholder {
|
||||
|
||||
@Inject Environment env;
|
||||
|
||||
@Bean
|
||||
public TestBean testBean() {
|
||||
TestBean testBean() {
|
||||
return new TestBean(env.getProperty("testbean.name"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@PropertySource(value="classpath:${path.to.properties}/p1.properties")
|
||||
@PropertySource("classpath:${path.to.properties}/p1.properties")
|
||||
static class ConfigWithResolvablePlaceholderAndFactoryBean {
|
||||
|
||||
@Inject Environment env;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Bean
|
||||
public FactoryBean testBean() {
|
||||
FactoryBean<TestBean> testBean() {
|
||||
final String name = env.getProperty("testbean.name");
|
||||
return new FactoryBean() {
|
||||
return new FactoryBean<TestBean>() {
|
||||
@Override
|
||||
public Object getObject() {
|
||||
public TestBean getObject() {
|
||||
return new TestBean(name);
|
||||
}
|
||||
@Override
|
||||
@@ -334,7 +353,7 @@ public class PropertySourceAnnotationTests {
|
||||
@Inject Environment env;
|
||||
|
||||
@Bean
|
||||
public TestBean testBean() {
|
||||
TestBean testBean() {
|
||||
return new TestBean(env.getProperty("testbean.name"));
|
||||
}
|
||||
}
|
||||
@@ -347,7 +366,7 @@ public class PropertySourceAnnotationTests {
|
||||
@Inject Environment env;
|
||||
|
||||
@Bean
|
||||
public TestBean testBean() {
|
||||
TestBean testBean() {
|
||||
return new TestBean(env.getProperty("testbean.name"));
|
||||
}
|
||||
}
|
||||
@@ -361,7 +380,7 @@ public class PropertySourceAnnotationTests {
|
||||
@Inject Environment env;
|
||||
|
||||
@Bean @Profile("test")
|
||||
public TestBean testBean() {
|
||||
TestBean testBean() {
|
||||
return new TestBean(env.getProperty("testbean.name"));
|
||||
}
|
||||
}
|
||||
@@ -380,21 +399,21 @@ public class PropertySourceAnnotationTests {
|
||||
|
||||
|
||||
@Configuration
|
||||
@MyPropertySource(value = "classpath:org/springframework/context/annotation/p2.properties")
|
||||
@MyPropertySource("classpath:org/springframework/context/annotation/p2.properties")
|
||||
static class WithCustomFactoryAsMeta {
|
||||
}
|
||||
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PropertySource(value = {}, factory = MyCustomFactory.class)
|
||||
public @interface MyPropertySource {
|
||||
@interface MyPropertySource {
|
||||
|
||||
@AliasFor(annotation = PropertySource.class)
|
||||
String value();
|
||||
}
|
||||
|
||||
|
||||
public static class MyCustomFactory implements PropertySourceFactory {
|
||||
static class MyCustomFactory implements PropertySourceFactory {
|
||||
|
||||
@Override
|
||||
public org.springframework.core.env.PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
|
||||
@@ -422,11 +441,10 @@ public class PropertySourceAnnotationTests {
|
||||
|
||||
|
||||
@Configuration
|
||||
@PropertySource(
|
||||
value = {
|
||||
"classpath:org/springframework/context/annotation/p1.properties",
|
||||
"classpath:org/springframework/context/annotation/p2.properties"
|
||||
})
|
||||
@PropertySource({
|
||||
"classpath:org/springframework/context/annotation/p1.properties",
|
||||
"classpath:org/springframework/context/annotation/p2.properties"
|
||||
})
|
||||
static class ConfigWithMultipleResourceLocations {
|
||||
}
|
||||
|
||||
@@ -434,12 +452,33 @@ public class PropertySourceAnnotationTests {
|
||||
@Configuration
|
||||
@PropertySources({
|
||||
@PropertySource("classpath:org/springframework/context/annotation/p1.properties"),
|
||||
@PropertySource("classpath:${base.package}/p2.properties"),
|
||||
@PropertySource("classpath:${base.package}/p2.properties")
|
||||
})
|
||||
static class ConfigWithPropertySources {
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@PropertySource("classpath:org/springframework/context/annotation/p1.properties")
|
||||
@PropertySource(value = "classpath:${base.package}/p2.properties", ignoreResourceNotFound = true)
|
||||
static class ConfigWithRepeatedPropertySourceAnnotations {
|
||||
}
|
||||
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Configuration
|
||||
@PropertySource("classpath:org/springframework/context/annotation/p1.properties")
|
||||
@PropertySource(value = "classpath:${base.package}/p2.properties", ignoreResourceNotFound = true)
|
||||
@PropertySource(value = "classpath:${custom.config.package:bogus/config}/p3.properties", ignoreResourceNotFound = true)
|
||||
@interface ComposedConfiguration {
|
||||
}
|
||||
|
||||
@ComposedConfiguration
|
||||
static class ConfigWithRepeatedPropertySourceAnnotationsOnComposedAnnotation {
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@PropertySources({
|
||||
@PropertySource(name = "psName", value = "classpath:org/springframework/context/annotation/p1.properties"),
|
||||
@@ -471,7 +510,7 @@ public class PropertySourceAnnotationTests {
|
||||
|
||||
|
||||
@Configuration
|
||||
@PropertySource(value = {})
|
||||
@PropertySource({})
|
||||
static class ConfigWithEmptyResourceLocations {
|
||||
}
|
||||
|
||||
@@ -482,7 +521,7 @@ public class PropertySourceAnnotationTests {
|
||||
@PropertySource("classpath:org/springframework/context/annotation/p2.properties")
|
||||
})
|
||||
@Configuration
|
||||
public static class ConfigWithSameSourceImportedInDifferentOrder {
|
||||
static class ConfigWithSameSourceImportedInDifferentOrder {
|
||||
|
||||
}
|
||||
|
||||
@@ -492,18 +531,17 @@ public class PropertySourceAnnotationTests {
|
||||
@PropertySource("classpath:org/springframework/context/annotation/p2.properties"),
|
||||
@PropertySource("classpath:org/springframework/context/annotation/p1.properties")
|
||||
})
|
||||
public static class ConfigImportedWithSameSourceImportedInDifferentOrder {
|
||||
static class ConfigImportedWithSameSourceImportedInDifferentOrder {
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@PropertySource(
|
||||
value = {
|
||||
"classpath:org/springframework/context/annotation/p1.properties",
|
||||
"classpath:org/springframework/context/annotation/p2.properties",
|
||||
"classpath:org/springframework/context/annotation/p3.properties",
|
||||
"classpath:org/springframework/context/annotation/p4.properties"
|
||||
})
|
||||
@PropertySource({
|
||||
"classpath:org/springframework/context/annotation/p1.properties",
|
||||
"classpath:org/springframework/context/annotation/p2.properties",
|
||||
"classpath:org/springframework/context/annotation/p3.properties",
|
||||
"classpath:org/springframework/context/annotation/p4.properties"
|
||||
})
|
||||
static class ConfigWithFourResourceLocations {
|
||||
}
|
||||
|
||||
|
||||
+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.
|
||||
@@ -49,9 +49,11 @@ public class PayloadApplicationEventTests {
|
||||
public void testProgrammaticEventListener() {
|
||||
List<Auditable> events = new ArrayList<>();
|
||||
ApplicationListener<AuditablePayloadEvent<String>> listener = events::add;
|
||||
ApplicationListener<AuditablePayloadEvent<Integer>> mismatch = (event -> event.getPayload().intValue());
|
||||
|
||||
ConfigurableApplicationContext ac = new GenericApplicationContext();
|
||||
ac.addApplicationListener(listener);
|
||||
ac.addApplicationListener(mismatch);
|
||||
ac.refresh();
|
||||
|
||||
AuditablePayloadEvent<String> event = new AuditablePayloadEvent<>(this, "xyz");
|
||||
@@ -63,9 +65,11 @@ public class PayloadApplicationEventTests {
|
||||
public void testProgrammaticPayloadListener() {
|
||||
List<String> events = new ArrayList<>();
|
||||
ApplicationListener<PayloadApplicationEvent<String>> listener = ApplicationListener.forPayload(events::add);
|
||||
ApplicationListener<PayloadApplicationEvent<Integer>> mismatch = ApplicationListener.forPayload(payload -> payload.intValue());
|
||||
|
||||
ConfigurableApplicationContext ac = new GenericApplicationContext();
|
||||
ac.addApplicationListener(listener);
|
||||
ac.addApplicationListener(mismatch);
|
||||
ac.refresh();
|
||||
|
||||
AuditablePayloadEvent<String> event = new AuditablePayloadEvent<>(this, "xyz");
|
||||
|
||||
+33
-4
@@ -25,18 +25,47 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link BitsCronField}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class BitsCronFieldTests {
|
||||
class BitsCronFieldTests {
|
||||
|
||||
@Test
|
||||
void parse() {
|
||||
assertThat(BitsCronField.parseSeconds("42")).has(clearRange(0, 41)).has(set(42)).has(clearRange(43, 59));
|
||||
assertThat(BitsCronField.parseMinutes("1,2,5,9")).has(clear(0)).has(set(1, 2)).has(clearRange(3,4)).has(set(5)).has(clearRange(6,8)).has(set(9)).has(clearRange(10,59));
|
||||
assertThat(BitsCronField.parseSeconds("0-4,8-12")).has(setRange(0, 4)).has(clearRange(5,7)).has(setRange(8, 12)).has(clearRange(13,59));
|
||||
assertThat(BitsCronField.parseHours("0-23/2")).has(set(0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22)).has(clear(1,3,5,7,9,11,13,15,17,19,21,23));
|
||||
assertThat(BitsCronField.parseDaysOfWeek("0")).has(clearRange(0, 6)).has(set(7, 7));
|
||||
assertThat(BitsCronField.parseSeconds("57/2")).has(clearRange(0, 56)).has(set(57)).has(clear(58)).has(set(59));
|
||||
|
||||
assertThat(BitsCronField.parseMinutes("30")).has(set(30)).has(clearRange(1, 29)).has(clearRange(31, 59));
|
||||
|
||||
assertThat(BitsCronField.parseHours("23")).has(set(23)).has(clearRange(0, 23));
|
||||
assertThat(BitsCronField.parseHours("0-23/2")).has(set(0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22)).has(clear(1,3,5,7,9,11,13,15,17,19,21,23));
|
||||
|
||||
assertThat(BitsCronField.parseDaysOfMonth("1")).has(set(1)).has(clearRange(2, 31));
|
||||
|
||||
assertThat(BitsCronField.parseMonth("1")).has(set(1)).has(clearRange(2, 12));
|
||||
|
||||
assertThat(BitsCronField.parseDaysOfWeek("0")).has(set(7, 7)).has(clearRange(0, 6));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseLists() {
|
||||
assertThat(BitsCronField.parseSeconds("15,30")).has(set(15, 30)).has(clearRange(1, 15)).has(clearRange(31, 59));
|
||||
assertThat(BitsCronField.parseMinutes("1,2,5,9")).has(set(1, 2, 5, 9)).has(clear(0)).has(clearRange(3, 4)).has(clearRange(6, 8)).has(clearRange(10, 59));
|
||||
assertThat(BitsCronField.parseHours("1,2,3")).has(set(1, 2, 3)).has(clearRange(4, 23));
|
||||
assertThat(BitsCronField.parseDaysOfMonth("1,2,3")).has(set(1, 2, 3)).has(clearRange(4, 31));
|
||||
assertThat(BitsCronField.parseMonth("1,2,3")).has(set(1, 2, 3)).has(clearRange(4, 12));
|
||||
assertThat(BitsCronField.parseDaysOfWeek("1,2,3")).has(set(1, 2, 3)).has(clearRange(4, 7));
|
||||
|
||||
assertThat(BitsCronField.parseMinutes("5,10-30/2"))
|
||||
.has(clearRange(0, 5))
|
||||
.has(set(5))
|
||||
.has(clearRange(6,10))
|
||||
.has(set(10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30))
|
||||
.has(clear(11, 13, 15, 17, 19, 21, 23, 25, 27, 29))
|
||||
.has(clearRange(31, 60));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+95
@@ -31,6 +31,7 @@ import org.junit.jupiter.api.Test;
|
||||
import static java.time.DayOfWeek.FRIDAY;
|
||||
import static java.time.DayOfWeek.MONDAY;
|
||||
import static java.time.DayOfWeek.SUNDAY;
|
||||
import static java.time.DayOfWeek.THURSDAY;
|
||||
import static java.time.DayOfWeek.TUESDAY;
|
||||
import static java.time.DayOfWeek.WEDNESDAY;
|
||||
import static java.time.temporal.TemporalAdjusters.next;
|
||||
@@ -116,6 +117,7 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.now().withMinute(10);
|
||||
LocalDateTime expected = last.plusMinutes(1).withSecond(0).withNano(0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -149,6 +151,7 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.of(year, 10, 30, 11, 1);
|
||||
LocalDateTime expected = last.withHour(12).withMinute(0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -173,6 +176,7 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.now().withDayOfMonth(1);
|
||||
LocalDateTime expected = last.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -207,6 +211,7 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.now().withMonth(9).withDayOfMonth(30);
|
||||
LocalDateTime expected = LocalDateTime.of(last.getYear(), 10, 1, 0, 0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -222,6 +227,7 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.now().withMonth(8).withDayOfMonth(30);
|
||||
LocalDateTime expected = last.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -237,6 +243,7 @@ class CronExpressionTests {
|
||||
ZonedDateTime last = ZonedDateTime.now(ZoneId.of("CET")).withMonth(10).withDayOfMonth(30);
|
||||
ZonedDateTime expected = last.withDayOfMonth(31).withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
ZonedDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -251,6 +258,7 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.now().withMonth(10).withDayOfMonth(30);
|
||||
LocalDateTime expected = LocalDateTime.of(last.getYear(), 11, 1, 0, 0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -265,6 +273,7 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.now().withYear(2010).withMonth(12).withDayOfMonth(31);
|
||||
LocalDateTime expected = LocalDateTime.of(2011, 1, 1, 0, 0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -297,6 +306,7 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.now().with(next(MONDAY));
|
||||
LocalDateTime expected = last.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
assertThat(actual.getDayOfWeek()).isEqualTo(TUESDAY);
|
||||
}
|
||||
@@ -308,6 +318,7 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.now().with(next(WEDNESDAY));
|
||||
LocalDateTime expected = last.plusDays(6).withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
assertThat(actual.getDayOfWeek()).isEqualTo(TUESDAY);
|
||||
}
|
||||
@@ -319,6 +330,7 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.now().withMinute(4).withSecond(54);
|
||||
LocalDateTime expected = last.plusMinutes(1).withSecond(55).withNano(0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -333,6 +345,7 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.now().withHour(9).withSecond(54);
|
||||
LocalDateTime expected = last.plusHours(1).withMinute(0).withSecond(55).withNano(0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -347,6 +360,7 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.now().withHour(9).withMinute(4);
|
||||
LocalDateTime expected = last.plusHours(1).plusMinutes(1).withSecond(0).withNano(0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -362,6 +376,7 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.now().withDayOfMonth(2).withSecond(54);
|
||||
LocalDateTime expected = last.plusDays(1).withHour(0).withMinute(0).withSecond(55).withNano(0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -376,6 +391,7 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.now().withMonth(10).withDayOfMonth(2);
|
||||
LocalDateTime expected = LocalDateTime.of(last.getYear(), 11, 3, 0, 0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -398,6 +414,7 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.now().withYear(2007).withMonth(2).withDayOfMonth(10);
|
||||
LocalDateTime expected = LocalDateTime.of(2008, 2, 29, 0, 0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -413,12 +430,14 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.of(LocalDate.of(2009, 9, 26), LocalTime.now());
|
||||
LocalDateTime expected = last.plusDays(2).withHour(7).withMinute(0).withSecond(0).withNano(0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
// Next day is a week day so add one
|
||||
last = actual;
|
||||
expected = expected.plusDays(1);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -433,12 +452,14 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.of(LocalDate.of(2010, 12, 30), LocalTime.now());
|
||||
LocalDateTime expected = last.plusMonths(1).withHour(23).withMinute(30).withSecond(0).withSecond(0).withNano(0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
// Next trigger is 3 months later
|
||||
last = actual;
|
||||
expected = expected.plusMonths(3);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -485,11 +506,13 @@ class CronExpressionTests {
|
||||
LocalDateTime expected = LocalDateTime.of(last.getYear() + 1, 1, 1, 0, 0);
|
||||
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = expected.plusYears(1);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -513,11 +536,13 @@ class CronExpressionTests {
|
||||
LocalDateTime expected = LocalDateTime.of(last.getYear(), 11, 1, 0, 0);
|
||||
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = expected.plusMonths(1);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -534,11 +559,13 @@ class CronExpressionTests {
|
||||
LocalDateTime expected = last.with(next(SUNDAY)).withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = expected.plusWeeks(1);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -555,11 +582,13 @@ class CronExpressionTests {
|
||||
LocalDateTime expected = last.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = expected.plusDays(1);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -583,11 +612,13 @@ class CronExpressionTests {
|
||||
LocalDateTime expected = last.plusHours(1).withMinute(0).withSecond(0).withNano(0);
|
||||
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = expected.plusHours(1);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -603,16 +634,19 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.of(LocalDate.of(2008, 1, 4), LocalTime.now());
|
||||
LocalDateTime expected = LocalDateTime.of(2008, 1, 31, 0, 0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = LocalDateTime.of(2008, 2, 29, 0, 0);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = LocalDateTime.of(2008, 3, 31, 0, 0);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -628,16 +662,19 @@ class CronExpressionTests {
|
||||
LocalDateTime last = LocalDateTime.of(LocalDate.of(2008, 1, 4), LocalTime.now());
|
||||
LocalDateTime expected = LocalDateTime.of(2008, 1, 28, 0, 0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = LocalDateTime.of(2008, 2, 26, 0, 0);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = LocalDateTime.of(2008, 3, 28, 0, 0);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
@@ -1042,4 +1079,62 @@ class CronExpressionTests {
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
assertThat(actual.getDayOfWeek()).isEqualTo(WEDNESDAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void dayOfMonthListWithQuartz() {
|
||||
CronExpression expression = CronExpression.parse("0 0 0 1W,15,LW * ?");
|
||||
|
||||
LocalDateTime last = LocalDateTime.of(2019, 12, 30, 0, 0);
|
||||
LocalDateTime expected = LocalDateTime.of(2019, 12, 31, 0, 0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
assertThat(actual).is(weekday);
|
||||
|
||||
last = actual;
|
||||
expected = LocalDateTime.of(2020, 1, 1, 0, 0);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
assertThat(actual).is(weekday);
|
||||
|
||||
last = actual;
|
||||
expected = LocalDateTime.of(2020, 1, 15, 0, 0);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
||||
last = actual;
|
||||
expected = LocalDateTime.of(2020, 1, 31, 0, 0);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
assertThat(actual).is(weekday);
|
||||
}
|
||||
|
||||
@Test
|
||||
void dayOfWeekListWithQuartz() {
|
||||
CronExpression expression = CronExpression.parse("0 0 0 ? * THU#1,THU#3,THU#5");
|
||||
|
||||
LocalDateTime last = LocalDateTime.of(2019, 12, 31, 0, 0);
|
||||
LocalDateTime expected = LocalDateTime.of(2020, 1, 2, 0, 0);
|
||||
LocalDateTime actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
assertThat(actual.getDayOfWeek()).isEqualTo(THURSDAY);
|
||||
|
||||
last = actual;
|
||||
expected = LocalDateTime.of(2020, 1, 16, 0, 0);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
assertThat(actual.getDayOfWeek()).isEqualTo(THURSDAY);
|
||||
|
||||
last = actual;
|
||||
expected = LocalDateTime.of(2020, 1, 30, 0, 0);
|
||||
actual = expression.next(last);
|
||||
assertThat(actual).isNotNull();
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
assertThat(actual.getDayOfWeek()).isEqualTo(THURSDAY);
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -25,6 +25,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link QuartzCronField}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
class QuartzCronFieldTests {
|
||||
@@ -95,6 +97,7 @@ class QuartzCronFieldTests {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> QuartzCronField.parseDaysOfWeek("1#L"));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> QuartzCronField.parseDaysOfWeek("L#1"));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> QuartzCronField.parseDaysOfWeek("8#1"));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> QuartzCronField.parseDaysOfWeek("2#1,2#3,2#5"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
testbean.name=p3TestBean
|
||||
from.p3=p3Value
|
||||
|
||||
@@ -129,9 +129,9 @@ public abstract class AnnotationVisitor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits an array value of the annotation. Note that arrays of primitive types (such as byte,
|
||||
* Visits an array value of the annotation. Note that arrays of primitive values (such as byte,
|
||||
* boolean, short, char, int, long, float or double) can be passed as value to {@link #visit
|
||||
* visit}. This is what {@link ClassReader} does.
|
||||
* visit}. This is what {@link ClassReader} does for non empty arrays of primitive values.
|
||||
*
|
||||
* @param name the value name.
|
||||
* @return a visitor to visit the actual array value elements, or {@literal null} if this visitor
|
||||
|
||||
@@ -100,8 +100,10 @@ public class ClassReader {
|
||||
@Deprecated
|
||||
// DontCheck(MemberName): can't be renamed (for backward binary compatibility).
|
||||
public final byte[] b;
|
||||
|
||||
/** The offset in bytes of the ClassFile's access_flags field. */
|
||||
public final int header;
|
||||
|
||||
/**
|
||||
* A byte array containing the JVMS ClassFile structure to be parsed. <i>The content of this array
|
||||
* must not be modified. This field is intended for {@link Attribute} sub classes, and is normally
|
||||
@@ -112,6 +114,7 @@ public class ClassReader {
|
||||
* ClassFile element offsets within this byte array.
|
||||
*/
|
||||
final byte[] classFileBuffer;
|
||||
|
||||
/**
|
||||
* The offset in bytes, in {@link #classFileBuffer}, of each cp_info entry of the ClassFile's
|
||||
* constant_pool array, <i>plus one</i>. In other words, the offset of constant pool entry i is
|
||||
@@ -119,16 +122,19 @@ public class ClassReader {
|
||||
* 1].
|
||||
*/
|
||||
private final int[] cpInfoOffsets;
|
||||
|
||||
/**
|
||||
* The String objects corresponding to the CONSTANT_Utf8 constant pool items. This cache avoids
|
||||
* multiple parsing of a given CONSTANT_Utf8 constant pool item.
|
||||
*/
|
||||
private final String[] constantUtf8Values;
|
||||
|
||||
/**
|
||||
* The ConstantDynamic objects corresponding to the CONSTANT_Dynamic constant pool items. This
|
||||
* cache avoids multiple parsing of a given CONSTANT_Dynamic constant pool item.
|
||||
*/
|
||||
private final ConstantDynamic[] constantDynamicValues;
|
||||
|
||||
/**
|
||||
* The start offsets in {@link #classFileBuffer} of each element of the bootstrap_methods array
|
||||
* (in the BootstrapMethods attribute).
|
||||
@@ -137,6 +143,7 @@ public class ClassReader {
|
||||
* 4.7.23</a>
|
||||
*/
|
||||
private final int[] bootstrapMethodOffsets;
|
||||
|
||||
/**
|
||||
* A conservative estimate of the maximum length of the strings contained in the constant pool of
|
||||
* the class.
|
||||
@@ -184,7 +191,7 @@ public class ClassReader {
|
||||
this.b = classFileBuffer;
|
||||
// Check the class' major_version. This field is after the magic and minor_version fields, which
|
||||
// use 4 and 2 bytes respectively.
|
||||
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V16) {
|
||||
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V17) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unsupported class file major version " + readShort(classFileOffset + 6));
|
||||
}
|
||||
@@ -499,6 +506,9 @@ public class ClassReader {
|
||||
} else if (Constants.SYNTHETIC.equals(attributeName)) {
|
||||
accessFlags |= Opcodes.ACC_SYNTHETIC;
|
||||
} else if (Constants.SOURCE_DEBUG_EXTENSION.equals(attributeName)) {
|
||||
if (attributeLength > classFileBuffer.length - currentAttributeOffset) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
sourceDebugExtension =
|
||||
readUtf(currentAttributeOffset, attributeLength, new char[attributeLength]);
|
||||
} else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) {
|
||||
@@ -1509,6 +1519,9 @@ public class ClassReader {
|
||||
final int maxLocals = readUnsignedShort(currentOffset + 2);
|
||||
final int codeLength = readInt(currentOffset + 4);
|
||||
currentOffset += 8;
|
||||
if (codeLength > classFileBuffer.length - currentOffset) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
// Read the bytecode 'code' array to create a label for each referenced instruction.
|
||||
final int bytecodeStartOffset = currentOffset;
|
||||
@@ -2965,7 +2978,7 @@ public class ClassReader {
|
||||
// Parse the array_value array.
|
||||
while (numElementValuePairs-- > 0) {
|
||||
currentOffset =
|
||||
readElementValue(annotationVisitor, currentOffset, /* elementName = */ null, charBuffer);
|
||||
readElementValue(annotationVisitor, currentOffset, /* elementName= */ null, charBuffer);
|
||||
}
|
||||
}
|
||||
if (annotationVisitor != null) {
|
||||
|
||||
@@ -132,7 +132,7 @@ public interface Opcodes {
|
||||
* <pre>
|
||||
* public class StuffVisitor {
|
||||
* @Deprecated public void visitOldStuff(int arg, ...) {
|
||||
* visitNewStuf(arg | SOURCE_DEPRECATED, ...);
|
||||
* visitNewStuff(arg | SOURCE_DEPRECATED, ...);
|
||||
* }
|
||||
* public void visitNewStuff(int argAndSource...) {
|
||||
* if ((argAndSource & SOURCE_DEPRECATED) == 0) {
|
||||
@@ -154,7 +154,7 @@ public interface Opcodes {
|
||||
* <p>and there are two cases:
|
||||
*
|
||||
* <ul>
|
||||
* <li>call visitOldSuff: in the call to super.visitOldStuff, the source is set to
|
||||
* <li>call visitOldStuff: in the call to super.visitOldStuff, the source is set to
|
||||
* SOURCE_DEPRECATED and visitNewStuff is called. Here 'do stuff' is run because the source
|
||||
* was previously set to SOURCE_DEPRECATED, and execution eventually returns to
|
||||
* UserStuffVisitor.visitOldStuff, where 'do user stuff' is run.
|
||||
@@ -281,6 +281,7 @@ public interface Opcodes {
|
||||
int V14 = 0 << 16 | 58;
|
||||
int V15 = 0 << 16 | 59;
|
||||
int V16 = 0 << 16 | 60;
|
||||
int V17 = 0 << 16 | 61;
|
||||
|
||||
/**
|
||||
* Version flag indicating that the class is using 'preview' features.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 the original author 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,20 +16,24 @@
|
||||
|
||||
package org.springframework.core;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Interface defining a generic contract for attaching and accessing metadata
|
||||
* to/from arbitrary objects.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Sam Brannen
|
||||
* @since 2.0
|
||||
*/
|
||||
public interface AttributeAccessor {
|
||||
|
||||
/**
|
||||
* Set the attribute defined by {@code name} to the supplied {@code value}.
|
||||
* If {@code value} is {@code null}, the attribute is {@link #removeAttribute removed}.
|
||||
* <p>If {@code value} is {@code null}, the attribute is {@link #removeAttribute removed}.
|
||||
* <p>In general, users should take care to prevent overlaps with other
|
||||
* metadata attributes by using fully-qualified names, perhaps using
|
||||
* class or package names as prefix.
|
||||
@@ -40,16 +44,48 @@ public interface AttributeAccessor {
|
||||
|
||||
/**
|
||||
* Get the value of the attribute identified by {@code name}.
|
||||
* Return {@code null} if the attribute doesn't exist.
|
||||
* <p>Return {@code null} if the attribute doesn't exist.
|
||||
* @param name the unique attribute key
|
||||
* @return the current value of the attribute, if any
|
||||
*/
|
||||
@Nullable
|
||||
Object getAttribute(String name);
|
||||
|
||||
/**
|
||||
* Compute a new value for the attribute identified by {@code name} if
|
||||
* necessary and {@linkplain #setAttribute set} the new value in this
|
||||
* {@code AttributeAccessor}.
|
||||
* <p>If a value for the attribute identified by {@code name} already exists
|
||||
* in this {@code AttributeAccessor}, the existing value will be returned
|
||||
* without applying the supplied compute function.
|
||||
* <p>The default implementation of this method is not thread safe but can
|
||||
* overridden by concrete implementations of this interface.
|
||||
* @param <T> the type of the attribute value
|
||||
* @param name the unique attribute key
|
||||
* @param computeFunction a function that computes a new value for the attribute
|
||||
* name; the function must not return a {@code null} value
|
||||
* @return the existing value or newly computed value for the named attribute
|
||||
* @see #getAttribute(String)
|
||||
* @see #setAttribute(String, Object)
|
||||
* @since 5.3.3
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T> T computeAttribute(String name, Function<String, T> computeFunction) {
|
||||
Assert.notNull(name, "Name must not be null");
|
||||
Assert.notNull(computeFunction, "Compute function must not be null");
|
||||
Object value = getAttribute(name);
|
||||
if (value == null) {
|
||||
value = computeFunction.apply(name);
|
||||
Assert.state(value != null,
|
||||
() -> String.format("Compute function must not return null for attribute named '%s'", name));
|
||||
setAttribute(name, value);
|
||||
}
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the attribute identified by {@code name} and return its value.
|
||||
* Return {@code null} if no attribute under {@code name} is found.
|
||||
* <p>Return {@code null} if no attribute under {@code name} is found.
|
||||
* @param name the unique attribute key
|
||||
* @return the last value of the attribute, if any
|
||||
*/
|
||||
@@ -58,7 +94,7 @@ public interface AttributeAccessor {
|
||||
|
||||
/**
|
||||
* Return {@code true} if the attribute identified by {@code name} exists.
|
||||
* Otherwise return {@code false}.
|
||||
* <p>Otherwise return {@code false}.
|
||||
* @param name the unique attribute key
|
||||
*/
|
||||
boolean hasAttribute(String name);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.core;
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -32,6 +33,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 2.0
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@@ -59,6 +61,17 @@ public abstract class AttributeAccessorSupport implements AttributeAccessor, Ser
|
||||
return this.attributes.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T computeAttribute(String name, Function<String, T> computeFunction) {
|
||||
Assert.notNull(name, "Name must not be null");
|
||||
Assert.notNull(computeFunction, "Compute function must not be null");
|
||||
Object value = this.attributes.computeIfAbsent(name, computeFunction);
|
||||
Assert.state(value != null,
|
||||
() -> String.format("Compute function must not return null for attribute named '%s'", name));
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object removeAttribute(String name) {
|
||||
|
||||
@@ -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.
|
||||
@@ -60,7 +60,28 @@ public class ReactiveAdapterRegistry {
|
||||
@Nullable
|
||||
private static volatile ReactiveAdapterRegistry sharedInstance;
|
||||
|
||||
private final boolean reactorPresent;
|
||||
private static final boolean reactorPresent;
|
||||
|
||||
private static final boolean rxjava1Present;
|
||||
|
||||
private static final boolean rxjava2Present;
|
||||
|
||||
private static final boolean rxjava3Present;
|
||||
|
||||
private static final boolean flowPublisherPresent;
|
||||
|
||||
private static final boolean kotlinCoroutinesPresent;
|
||||
|
||||
static {
|
||||
ClassLoader classLoader = ReactiveAdapterRegistry.class.getClassLoader();
|
||||
reactorPresent = ClassUtils.isPresent("reactor.core.publisher.Flux", classLoader);
|
||||
rxjava1Present = ClassUtils.isPresent("rx.Observable", classLoader) &&
|
||||
ClassUtils.isPresent("rx.RxReactiveStreams", classLoader);
|
||||
rxjava2Present = ClassUtils.isPresent("io.reactivex.Flowable", classLoader);
|
||||
rxjava3Present = ClassUtils.isPresent("io.reactivex.rxjava3.core.Flowable", classLoader);
|
||||
flowPublisherPresent = ClassUtils.isPresent("java.util.concurrent.Flow.Publisher", classLoader);
|
||||
kotlinCoroutinesPresent = ClassUtils.isPresent("kotlinx.coroutines.reactor.MonoKt", classLoader);
|
||||
}
|
||||
|
||||
private final List<ReactiveAdapter> adapters = new ArrayList<>();
|
||||
|
||||
@@ -70,41 +91,34 @@ public class ReactiveAdapterRegistry {
|
||||
* @see #getSharedInstance()
|
||||
*/
|
||||
public ReactiveAdapterRegistry() {
|
||||
ClassLoader classLoader = ReactiveAdapterRegistry.class.getClassLoader();
|
||||
|
||||
// Reactor
|
||||
boolean reactorRegistered = false;
|
||||
if (ClassUtils.isPresent("reactor.core.publisher.Flux", classLoader)) {
|
||||
if (reactorPresent) {
|
||||
new ReactorRegistrar().registerAdapters(this);
|
||||
reactorRegistered = true;
|
||||
}
|
||||
this.reactorPresent = reactorRegistered;
|
||||
|
||||
// RxJava1 (deprecated)
|
||||
if (ClassUtils.isPresent("rx.Observable", classLoader) &&
|
||||
ClassUtils.isPresent("rx.RxReactiveStreams", classLoader)) {
|
||||
if (rxjava1Present) {
|
||||
new RxJava1Registrar().registerAdapters(this);
|
||||
}
|
||||
|
||||
// RxJava2
|
||||
if (ClassUtils.isPresent("io.reactivex.Flowable", classLoader)) {
|
||||
if (rxjava2Present) {
|
||||
new RxJava2Registrar().registerAdapters(this);
|
||||
}
|
||||
|
||||
// RxJava3
|
||||
if (ClassUtils.isPresent("io.reactivex.rxjava3.core.Flowable", classLoader)) {
|
||||
if (rxjava3Present) {
|
||||
new RxJava3Registrar().registerAdapters(this);
|
||||
}
|
||||
|
||||
// Java 9+ Flow.Publisher
|
||||
if (ClassUtils.isPresent("java.util.concurrent.Flow.Publisher", classLoader)) {
|
||||
if (flowPublisherPresent) {
|
||||
new ReactorJdkFlowAdapterRegistrar().registerAdapter(this);
|
||||
}
|
||||
// If not present, do nothing for the time being...
|
||||
// We can fall back on "reactive-streams-flow-bridge" (once released)
|
||||
|
||||
// Coroutines
|
||||
if (this.reactorPresent && ClassUtils.isPresent("kotlinx.coroutines.reactor.MonoKt", classLoader)) {
|
||||
// Kotlin Coroutines
|
||||
if (reactorPresent && kotlinCoroutinesPresent) {
|
||||
new CoroutinesRegistrar().registerAdapters(this);
|
||||
}
|
||||
}
|
||||
@@ -125,7 +139,7 @@ public class ReactiveAdapterRegistry {
|
||||
public void registerReactiveType(ReactiveTypeDescriptor descriptor,
|
||||
Function<Object, Publisher<?>> toAdapter, Function<Publisher<?>, Object> fromAdapter) {
|
||||
|
||||
if (this.reactorPresent) {
|
||||
if (reactorPresent) {
|
||||
this.adapters.add(new ReactorAdapter(descriptor, toAdapter, fromAdapter));
|
||||
}
|
||||
else {
|
||||
|
||||
+32
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author 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,39 +17,61 @@
|
||||
package org.springframework.core;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AttributeAccessorSupport}.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Sam Brannen
|
||||
* @since 2.0
|
||||
*/
|
||||
class AttributeAccessorSupportTests {
|
||||
|
||||
private static final String NAME = "foo";
|
||||
private static final String NAME = "name";
|
||||
|
||||
private static final String VALUE = "bar";
|
||||
private static final String VALUE = "value";
|
||||
|
||||
private final AttributeAccessor attributeAccessor = new SimpleAttributeAccessorSupport();
|
||||
|
||||
private AttributeAccessor attributeAccessor = new SimpleAttributeAccessorSupport();
|
||||
|
||||
@Test
|
||||
void setAndGet() throws Exception {
|
||||
void setAndGet() {
|
||||
this.attributeAccessor.setAttribute(NAME, VALUE);
|
||||
assertThat(this.attributeAccessor.getAttribute(NAME)).isEqualTo(VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void setAndHas() throws Exception {
|
||||
void setAndHas() {
|
||||
assertThat(this.attributeAccessor.hasAttribute(NAME)).isFalse();
|
||||
this.attributeAccessor.setAttribute(NAME, VALUE);
|
||||
assertThat(this.attributeAccessor.hasAttribute(NAME)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void remove() throws Exception {
|
||||
void computeAttribute() {
|
||||
AtomicInteger atomicInteger = new AtomicInteger();
|
||||
Function<String, String> computeFunction = name -> "computed-" + atomicInteger.incrementAndGet();
|
||||
|
||||
assertThat(this.attributeAccessor.hasAttribute(NAME)).isFalse();
|
||||
this.attributeAccessor.computeAttribute(NAME, computeFunction);
|
||||
assertThat(this.attributeAccessor.getAttribute(NAME)).isEqualTo("computed-1");
|
||||
this.attributeAccessor.computeAttribute(NAME, computeFunction);
|
||||
assertThat(this.attributeAccessor.getAttribute(NAME)).isEqualTo("computed-1");
|
||||
|
||||
this.attributeAccessor.removeAttribute(NAME);
|
||||
assertThat(this.attributeAccessor.hasAttribute(NAME)).isFalse();
|
||||
this.attributeAccessor.computeAttribute(NAME, computeFunction);
|
||||
assertThat(this.attributeAccessor.getAttribute(NAME)).isEqualTo("computed-2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void remove() {
|
||||
assertThat(this.attributeAccessor.hasAttribute(NAME)).isFalse();
|
||||
this.attributeAccessor.setAttribute(NAME, VALUE);
|
||||
assertThat(this.attributeAccessor.removeAttribute(NAME)).isEqualTo(VALUE);
|
||||
@@ -57,13 +79,13 @@ class AttributeAccessorSupportTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void attributeNames() throws Exception {
|
||||
void attributeNames() {
|
||||
this.attributeAccessor.setAttribute(NAME, VALUE);
|
||||
this.attributeAccessor.setAttribute("abc", "123");
|
||||
String[] attributeNames = this.attributeAccessor.attributeNames();
|
||||
Arrays.sort(attributeNames);
|
||||
assertThat(Arrays.binarySearch(attributeNames, NAME) > -1).isTrue();
|
||||
assertThat(Arrays.binarySearch(attributeNames, "abc") > -1).isTrue();
|
||||
assertThat(Arrays.binarySearch(attributeNames, "abc")).isEqualTo(0);
|
||||
assertThat(Arrays.binarySearch(attributeNames, NAME)).isEqualTo(1);
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
|
||||
+1
-1
@@ -433,7 +433,7 @@ public abstract class AbstractJdbcCall {
|
||||
/**
|
||||
* Match the provided in parameter values with registered parameters and
|
||||
* parameters defined via meta-data processing.
|
||||
* @param parameterSource the parameter vakues provided as a {@link SqlParameterSource}
|
||||
* @param parameterSource the parameter values provided as a {@link SqlParameterSource}
|
||||
* @return a Map with parameter names and values
|
||||
*/
|
||||
protected Map<String, Object> matchInParameterValuesWithCallParameters(SqlParameterSource parameterSource) {
|
||||
|
||||
+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.
|
||||
@@ -54,8 +54,10 @@ inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<ou
|
||||
* @author Mario Arias
|
||||
* @since 5.0
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
// TODO Replace by the vararg variant in Spring Framework 6
|
||||
inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<out Any>): T? =
|
||||
queryForObject(sql, T::class.java, args) as T
|
||||
queryForObject(sql, args, T::class.java) as T
|
||||
|
||||
/**
|
||||
* Extension for [JdbcOperations.queryForList] providing a `queryForList<Foo>("...")` variant.
|
||||
@@ -86,8 +88,11 @@ inline fun <reified T> JdbcOperations.queryForList(sql: String, args: Array<out
|
||||
* @author Mario Arias
|
||||
* @since 5.0
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
// TODO Replace by the vararg variant in Spring Framework 6
|
||||
inline fun <reified T> JdbcOperations.queryForList(sql: String, args: Array<out Any>): List<T> =
|
||||
queryForList(sql, T::class.java, args)
|
||||
queryForList(sql, args, T::class.java)
|
||||
|
||||
|
||||
/**
|
||||
* Extension for [JdbcOperations.query] providing a ResultSetExtractor-like function
|
||||
|
||||
@@ -247,7 +247,7 @@
|
||||
<value>03000,42000,42601,42602,42622,42804,42P01</value>
|
||||
</property>
|
||||
<property name="duplicateKeyCodes">
|
||||
<value>23505</value>
|
||||
<value>21000,23505</value>
|
||||
</property>
|
||||
<property name="dataIntegrityViolationCodes">
|
||||
<value>23000,23502,23503,23514</value>
|
||||
|
||||
+9
-7
@@ -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,11 +67,12 @@ class JdbcOperationsExtensionsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("DEPRECATION")
|
||||
fun `queryForObject with reified type parameters and args`() {
|
||||
val args = arrayOf(3)
|
||||
every { template.queryForObject(sql, any<Class<Int>>(), args) } returns 2
|
||||
val args = arrayOf(3, 4)
|
||||
every { template.queryForObject(sql, args, any<Class<Int>>()) } returns 2
|
||||
assertThat(template.queryForObject<Int>(sql, args)).isEqualTo(2)
|
||||
verify { template.queryForObject(sql, any<Class<Int>>(), args) }
|
||||
verify { template.queryForObject(sql, args, any<Class<Int>>()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -93,12 +94,13 @@ class JdbcOperationsExtensionsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("DEPRECATION")
|
||||
fun `queryForList with reified type parameters and args`() {
|
||||
val list = listOf(1, 2, 3)
|
||||
val args = arrayOf(3)
|
||||
every { template.queryForList(sql, any<Class<Int>>(), args) } returns list
|
||||
val args = arrayOf(3, 4)
|
||||
every { template.queryForList(sql, args, any<Class<Int>>()) } returns list
|
||||
template.queryForList<Int>(sql, args)
|
||||
verify { template.queryForList(sql, any<Class<Int>>(), args) }
|
||||
verify { template.queryForList(sql, args, any<Class<Int>>()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+12
-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.
|
||||
@@ -38,6 +38,7 @@ import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.core.codec.DecodingException;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
@@ -232,6 +233,7 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol
|
||||
if (decoder.canDecode(elementType, mimeType)) {
|
||||
if (adapter != null && adapter.isMultiValue()) {
|
||||
Flux<?> flux = content
|
||||
.filter(this::nonEmptyDataBuffer)
|
||||
.map(buffer -> decoder.decode(buffer, elementType, mimeType, hints))
|
||||
.onErrorResume(ex -> Flux.error(handleReadError(parameter, message, ex)));
|
||||
if (isContentRequired) {
|
||||
@@ -245,6 +247,7 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol
|
||||
else {
|
||||
// Single-value (with or without reactive type wrapper)
|
||||
Mono<?> mono = content.next()
|
||||
.filter(this::nonEmptyDataBuffer)
|
||||
.map(buffer -> decoder.decode(buffer, elementType, mimeType, hints))
|
||||
.onErrorResume(ex -> Mono.error(handleReadError(parameter, message, ex)));
|
||||
if (isContentRequired) {
|
||||
@@ -262,6 +265,14 @@ public class PayloadMethodArgumentResolver implements HandlerMethodArgumentResol
|
||||
message, parameter, "Cannot decode to [" + targetType + "]" + message));
|
||||
}
|
||||
|
||||
private boolean nonEmptyDataBuffer(DataBuffer buffer) {
|
||||
if (buffer.readableByteCount() > 0) {
|
||||
return true;
|
||||
}
|
||||
DataBufferUtils.release(buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
private Throwable handleReadError(MethodParameter parameter, Message<?> message, Throwable ex) {
|
||||
return ex instanceof DecodingException ?
|
||||
new MethodArgumentResolutionException(message, parameter, "Failed to read HTTP message", ex) : ex;
|
||||
|
||||
+33
-8
@@ -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.
|
||||
@@ -34,10 +34,24 @@ import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 4.0
|
||||
*/
|
||||
public abstract class AbstractExceptionHandlerMethodResolver {
|
||||
|
||||
private static final Method NO_MATCHING_EXCEPTION_HANDLER_METHOD;
|
||||
|
||||
static {
|
||||
try {
|
||||
NO_MATCHING_EXCEPTION_HANDLER_METHOD =
|
||||
AbstractExceptionHandlerMethodResolver.class.getDeclaredMethod("noMatchingExceptionHandler");
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException("Expected method not found: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);
|
||||
|
||||
private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);
|
||||
@@ -52,9 +66,9 @@ public abstract class AbstractExceptionHandlerMethodResolver {
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the exceptions this method handles.This implementation looks for
|
||||
* Extract the exceptions this method handles. This implementation looks for
|
||||
* sub-classes of Throwable in the method signature.
|
||||
* The method is static to ensure safe use from sub-class constructors.
|
||||
* <p>The method is static to ensure safe use from sub-class constructors.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected static List<Class<? extends Throwable>> getExceptionsFromMethodSignature(Method method) {
|
||||
@@ -80,7 +94,7 @@ public abstract class AbstractExceptionHandlerMethodResolver {
|
||||
|
||||
/**
|
||||
* Find a {@link Method} to handle the given exception.
|
||||
* Use {@link ExceptionDepthComparator} if more than one match is found.
|
||||
* <p>Uses {@link ExceptionDepthComparator} if more than one match is found.
|
||||
* @param exception the exception
|
||||
* @return a Method to handle the exception, or {@code null} if none found
|
||||
*/
|
||||
@@ -99,6 +113,7 @@ public abstract class AbstractExceptionHandlerMethodResolver {
|
||||
/**
|
||||
* Find a {@link Method} to handle the given exception type. This can be
|
||||
* useful if an {@link Exception} instance is not available (e.g. for tools).
|
||||
* <p>Uses {@link ExceptionDepthComparator} if more than one match is found.
|
||||
* @param exceptionType the exception type
|
||||
* @return a Method to handle the exception, or {@code null} if none found
|
||||
* @since 4.3.1
|
||||
@@ -110,11 +125,12 @@ public abstract class AbstractExceptionHandlerMethodResolver {
|
||||
method = getMappedMethod(exceptionType);
|
||||
this.exceptionLookupCache.put(exceptionType, method);
|
||||
}
|
||||
return method;
|
||||
return (method != NO_MATCHING_EXCEPTION_HANDLER_METHOD ? method : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Method} mapped to the given exception type, or {@code null} if none.
|
||||
* Return the {@link Method} mapped to the given exception type, or
|
||||
* {@link #NO_MATCHING_EXCEPTION_HANDLER_METHOD} if none.
|
||||
*/
|
||||
@Nullable
|
||||
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
|
||||
@@ -125,12 +141,21 @@ public abstract class AbstractExceptionHandlerMethodResolver {
|
||||
}
|
||||
}
|
||||
if (!matches.isEmpty()) {
|
||||
matches.sort(new ExceptionDepthComparator(exceptionType));
|
||||
if (matches.size() > 1) {
|
||||
matches.sort(new ExceptionDepthComparator(exceptionType));
|
||||
}
|
||||
return this.mappedMethods.get(matches.get(0));
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
return NO_MATCHING_EXCEPTION_HANDLER_METHOD;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For the {@link #NO_MATCHING_EXCEPTION_HANDLER_METHOD} constant.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private void noMatchingExceptionHandler() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+18
-7
@@ -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,7 +19,6 @@ package org.springframework.messaging.rsocket;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import io.rsocket.Payload;
|
||||
import io.rsocket.RSocket;
|
||||
import io.rsocket.SocketAcceptor;
|
||||
import io.rsocket.core.RSocketServer;
|
||||
@@ -43,6 +42,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.handler.annotation.Header;
|
||||
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
import org.springframework.messaging.rsocket.annotation.ConnectMapping;
|
||||
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
|
||||
import org.springframework.stereotype.Controller;
|
||||
@@ -164,6 +164,12 @@ public class RSocketClientToServerIntegrationTests {
|
||||
.verify(Duration.ofSeconds(5));
|
||||
}
|
||||
|
||||
@Test // gh-26344
|
||||
public void echoChannelWithEmptyInput() {
|
||||
Flux<String> result = requester.route("echo-channel-empty").data(Flux.empty()).retrieveFlux(String.class);
|
||||
StepVerifier.create(result).verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void metadataPush() {
|
||||
Flux.just("bar", "baz")
|
||||
@@ -254,6 +260,11 @@ public class RSocketClientToServerIntegrationTests {
|
||||
return payloads.delayElements(Duration.ofMillis(10)).map(payload -> payload + " async");
|
||||
}
|
||||
|
||||
@MessageMapping("echo-channel-empty")
|
||||
Flux<String> echoChannelEmpty(@Payload(required = false) Flux<String> payloads) {
|
||||
return payloads.map(payload -> payload + " echoed");
|
||||
}
|
||||
|
||||
@MessageMapping("thrown-exception")
|
||||
Mono<String> handleAndThrow(String payload) {
|
||||
throw new IllegalArgumentException("Invalid input error");
|
||||
@@ -338,29 +349,29 @@ public class RSocketClientToServerIntegrationTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> fireAndForget(Payload payload) {
|
||||
public Mono<Void> fireAndForget(io.rsocket.Payload payload) {
|
||||
return this.delegate.fireAndForget(payload)
|
||||
.doOnSuccess(aVoid -> this.fireAndForgetCount.incrementAndGet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> metadataPush(Payload payload) {
|
||||
public Mono<Void> metadataPush(io.rsocket.Payload payload) {
|
||||
return this.delegate.metadataPush(payload)
|
||||
.doOnSuccess(aVoid -> this.metadataPushCount.incrementAndGet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Payload> requestResponse(Payload payload) {
|
||||
public Mono<io.rsocket.Payload> requestResponse(io.rsocket.Payload payload) {
|
||||
return this.delegate.requestResponse(payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Payload> requestStream(Payload payload) {
|
||||
public Flux<io.rsocket.Payload> requestStream(io.rsocket.Payload payload) {
|
||||
return this.delegate.requestStream(payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Payload> requestChannel(Publisher<Payload> payloads) {
|
||||
public Flux<io.rsocket.Payload> requestChannel(Publisher<io.rsocket.Payload> payloads) {
|
||||
return this.delegate.requestChannel(payloads);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 the original author 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,6 +21,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
@@ -42,10 +43,10 @@ public class MockMultipartFile implements MultipartFile {
|
||||
|
||||
private final String name;
|
||||
|
||||
private String originalFilename;
|
||||
private final String originalFilename;
|
||||
|
||||
@Nullable
|
||||
private String contentType;
|
||||
private final String contentType;
|
||||
|
||||
private final byte[] content;
|
||||
|
||||
@@ -79,7 +80,7 @@ public class MockMultipartFile implements MultipartFile {
|
||||
public MockMultipartFile(
|
||||
String name, @Nullable String originalFilename, @Nullable String contentType, @Nullable byte[] content) {
|
||||
|
||||
Assert.hasLength(name, "Name must not be null");
|
||||
Assert.hasLength(name, "Name must not be empty");
|
||||
this.name = name;
|
||||
this.originalFilename = (originalFilename != null ? originalFilename : "");
|
||||
this.contentType = contentType;
|
||||
@@ -108,6 +109,7 @@ public class MockMultipartFile implements MultipartFile {
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String getOriginalFilename() {
|
||||
return this.originalFilename;
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ abstract class BootstrapUtils {
|
||||
}
|
||||
|
||||
private static Class<?> resolveDefaultTestContextBootstrapper(Class<?> testClass) throws Exception {
|
||||
boolean webApp = (TestContextAnnotationUtils.findMergedAnnotation(testClass, webAppConfigurationClass) != null);
|
||||
boolean webApp = TestContextAnnotationUtils.hasAnnotation(testClass, webAppConfigurationClass);
|
||||
String bootstrapperClassName = (webApp ? DEFAULT_WEB_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME :
|
||||
DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME);
|
||||
return ClassUtils.forName(bootstrapperClassName, BootstrapUtils.class.getClassLoader());
|
||||
|
||||
+16
@@ -78,6 +78,22 @@ public abstract class TestContextAnnotationUtils {
|
||||
private static volatile EnclosingConfiguration defaultEnclosingConfigurationMode;
|
||||
|
||||
|
||||
/**
|
||||
* Determine if an annotation of the specified {@code annotationType} is
|
||||
* present or meta-present on the supplied {@link Class} according to the
|
||||
* search algorithm used in {@link #findMergedAnnotation(Class, Class)}.
|
||||
* <p>If this method returns {@code true}, then {@code findMergedAnnotation(...)}
|
||||
* will return a non-null value.
|
||||
* @param clazz the class to look for annotations on
|
||||
* @param annotationType the type of annotation to look for
|
||||
* @return {@code true} if a matching annotation is present
|
||||
* @since 5.3.3
|
||||
* @see #findMergedAnnotation(Class, Class)
|
||||
*/
|
||||
public static boolean hasAnnotation(Class<?> clazz, Class<? extends Annotation> annotationType) {
|
||||
return (findMergedAnnotation(clazz, annotationType) != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first annotation of the specified {@code annotationType} within
|
||||
* the annotation hierarchy <em>above</em> the supplied class, merge that
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -48,6 +48,8 @@ package org.springframework.test.context;
|
||||
* ServletTestExecutionListener}</li>
|
||||
* <li>{@link org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener
|
||||
* DirtiesContextBeforeModesTestExecutionListener}</li>
|
||||
* <li>{@link org.springframework.test.context.event.ApplicationEventsTestExecutionListener
|
||||
* ApplicationEventsTestExecutionListener}</li>
|
||||
* <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener
|
||||
* DependencyInjectionTestExecutionListener}</li>
|
||||
* <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener
|
||||
|
||||
@@ -67,6 +67,7 @@ public @interface TestExecutionListeners {
|
||||
* {@link #value}, but it may be used instead of {@link #value}.
|
||||
* @see org.springframework.test.context.web.ServletTestExecutionListener
|
||||
* @see org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener
|
||||
* @see org.springframework.test.context.event.ApplicationEventsTestExecutionListener
|
||||
* @see org.springframework.test.context.support.DependencyInjectionTestExecutionListener
|
||||
* @see org.springframework.test.context.support.DirtiesContextTestExecutionListener
|
||||
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author 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.event;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* {@code ApplicationEvents} encapsulates all {@linkplain ApplicationEvent
|
||||
* application events} that were fired during the execution of a single test method.
|
||||
*
|
||||
* <p>To use {@code ApplicationEvents} in your tests, do the following.
|
||||
* <ul>
|
||||
* <li>Ensure that your test class is annotated or meta-annotated with
|
||||
* {@link RecordApplicationEvents @RecordApplicationEvents}.</li>
|
||||
* <li>Ensure that the {@link ApplicationEventsTestExecutionListener} is
|
||||
* registered. Note, however, that it is registered by default and only needs
|
||||
* to be manually registered if you have custom configuration via
|
||||
* {@link org.springframework.test.context.TestExecutionListeners @TestExecutionListeners}
|
||||
* that does not include the default listeners.</li>
|
||||
* <li>Annotate a field of type {@code ApplicationEvents} with
|
||||
* {@link org.springframework.beans.factory.annotation.Autowired @Autowired} and
|
||||
* use that instance of {@code ApplicationEvents} in your test and lifecycle methods.</li>
|
||||
* <li>With JUnit Jupiter, you may optionally declare a parameter of type
|
||||
* {@code ApplicationEvents} in a test or lifecycle method as an alternative to
|
||||
* an {@code @Autowired} field in the test class.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Oliver Drotbohm
|
||||
* @since 5.3.3
|
||||
* @see RecordApplicationEvents
|
||||
* @see ApplicationEventsTestExecutionListener
|
||||
* @see org.springframework.context.ApplicationEvent
|
||||
*/
|
||||
public interface ApplicationEvents {
|
||||
|
||||
/**
|
||||
* Stream all application events that were fired during test execution.
|
||||
* @return a stream of all application events
|
||||
* @see #stream(Class)
|
||||
* @see #clear()
|
||||
*/
|
||||
Stream<ApplicationEvent> stream();
|
||||
|
||||
/**
|
||||
* Stream all application events or event payloads of the given type that
|
||||
* were fired during test execution.
|
||||
* @param <T> the event type
|
||||
* @param type the type of events or payloads to stream; never {@code null}
|
||||
* @return a stream of all application events or event payloads of the
|
||||
* specified type
|
||||
* @see #stream()
|
||||
* @see #clear()
|
||||
*/
|
||||
<T> Stream<T> stream(Class<T> type);
|
||||
|
||||
/**
|
||||
* Clear all application events recorded by this {@code ApplicationEvents} instance.
|
||||
* <p>Subsequent calls to {@link #stream()} or {@link #stream(Class)} will
|
||||
* only include events recorded since this method was invoked.
|
||||
* @see #stream()
|
||||
* @see #stream(Class)
|
||||
*/
|
||||
void clear();
|
||||
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author 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.event;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
|
||||
/**
|
||||
* {@link ApplicationListener} that listens to all events and adds them to the
|
||||
* current {@link ApplicationEvents} instance if registered for the current thread.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Oliver Drotbohm
|
||||
* @since 5.3.3
|
||||
*/
|
||||
class ApplicationEventsApplicationListener implements ApplicationListener<ApplicationEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
DefaultApplicationEvents applicationEvents =
|
||||
(DefaultApplicationEvents) ApplicationEventsHolder.getApplicationEvents();
|
||||
if (applicationEvents != null) {
|
||||
applicationEvents.addEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author 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.event;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Holder class to expose the application events published during the execution
|
||||
* of a test in the form of a thread-bound {@link ApplicationEvents} object.
|
||||
*
|
||||
* <p>{@code ApplicationEvents} are registered in this holder and managed by
|
||||
* the {@link ApplicationEventsTestExecutionListener}.
|
||||
*
|
||||
* <p>Although this class is {@code public}, it is only intended for use within
|
||||
* the <em>Spring TestContext Framework</em> or in the implementation of
|
||||
* third-party extensions. Test authors should therefore allow the current
|
||||
* instance of {@code ApplicationEvents} to be
|
||||
* {@link org.springframework.beans.factory.annotation.Autowired @Autowired}
|
||||
* into a field in the test class or injected via a parameter in test and
|
||||
* lifecycle methods when using JUnit Jupiter and the {@link
|
||||
* org.springframework.test.context.junit.jupiter.SpringExtension SpringExtension}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @author Oliver Drotbohm
|
||||
* @since 5.3.3
|
||||
* @see ApplicationEvents
|
||||
* @see RecordApplicationEvents
|
||||
* @see ApplicationEventsTestExecutionListener
|
||||
*/
|
||||
public abstract class ApplicationEventsHolder {
|
||||
|
||||
private static final ThreadLocal<DefaultApplicationEvents> applicationEvents = new ThreadLocal<>();
|
||||
|
||||
|
||||
private ApplicationEventsHolder() {
|
||||
// no-op to prevent instantiation of this holder class
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the {@link ApplicationEvents} for the current thread.
|
||||
* @return the current {@code ApplicationEvents}, or {@code null} if not registered
|
||||
*/
|
||||
@Nullable
|
||||
public static ApplicationEvents getApplicationEvents() {
|
||||
return applicationEvents.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ApplicationEvents} for the current thread.
|
||||
* @return the current {@code ApplicationEvents}
|
||||
* @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. " +
|
||||
"Ensure that your test class is annotated with @RecordApplicationEvents " +
|
||||
"and that the ApplicationEventsTestExecutionListener is registered.");
|
||||
return events;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register a new {@link DefaultApplicationEvents} instance to be used for the
|
||||
* current thread, if necessary.
|
||||
* <p>If {@link #registerApplicationEvents()} has already been called for the
|
||||
* current thread, this method does not do anything.
|
||||
*/
|
||||
static void registerApplicationEventsIfNecessary() {
|
||||
if (getApplicationEvents() == null) {
|
||||
registerApplicationEvents();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new {@link DefaultApplicationEvents} instance to be used for the
|
||||
* current thread.
|
||||
*/
|
||||
static void registerApplicationEvents() {
|
||||
applicationEvents.set(new DefaultApplicationEvents());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the registration of the {@link ApplicationEvents} for the current thread.
|
||||
*/
|
||||
static void unregisterApplicationEvents() {
|
||||
applicationEvents.remove();
|
||||
}
|
||||
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author 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.event;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.AbstractApplicationContext;
|
||||
import org.springframework.core.Conventions;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.TestContextAnnotationUtils;
|
||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@code TestExecutionListener} which provides support for {@link ApplicationEvents}.
|
||||
*
|
||||
* <p>This listener manages the registration of {@code ApplicationEvents} for the
|
||||
* current thread at various points within the test execution lifecycle and makes
|
||||
* the current instance of {@code ApplicationEvents} available to tests via an
|
||||
* {@link org.springframework.beans.factory.annotation.Autowired @Autowired}
|
||||
* field in the test class.
|
||||
*
|
||||
* <p>If the test class is not annotated or meta-annotated with
|
||||
* {@link RecordApplicationEvents @RecordApplicationEvents}, this listener
|
||||
* effectively does nothing.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 5.3.3
|
||||
* @see ApplicationEvents
|
||||
* @see ApplicationEventsHolder
|
||||
*/
|
||||
public class ApplicationEventsTestExecutionListener extends AbstractTestExecutionListener {
|
||||
|
||||
/**
|
||||
* Attribute name for a {@link TestContext} attribute which indicates
|
||||
* whether the test class for the given test context is annotated with
|
||||
* {@link RecordApplicationEvents @RecordApplicationEvents}.
|
||||
* <p>Permissible values include {@link Boolean#TRUE} and {@link Boolean#FALSE}.
|
||||
*/
|
||||
private static final String RECORD_APPLICATION_EVENTS = Conventions.getQualifiedAttributeName(
|
||||
ApplicationEventsTestExecutionListener.class, "recordApplicationEvents");
|
||||
|
||||
private static final Object applicationEventsMonitor = new Object();
|
||||
|
||||
|
||||
/**
|
||||
* Returns {@code 1800}.
|
||||
*/
|
||||
@Override
|
||||
public final int getOrder() {
|
||||
return 1800;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareTestInstance(TestContext testContext) throws Exception {
|
||||
if (recordApplicationEvents(testContext)) {
|
||||
registerListenerAndResolvableDependencyIfNecessary(testContext.getApplicationContext());
|
||||
ApplicationEventsHolder.registerApplicationEvents();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTestMethod(TestContext testContext) throws Exception {
|
||||
if (recordApplicationEvents(testContext)) {
|
||||
// Register a new ApplicationEvents instance for the current thread
|
||||
// in case the test instance is shared -- for example, in TestNG or
|
||||
// JUnit Jupiter with @TestInstance(PER_CLASS) semantics.
|
||||
ApplicationEventsHolder.registerApplicationEventsIfNecessary();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTestMethod(TestContext testContext) throws Exception {
|
||||
if (recordApplicationEvents(testContext)) {
|
||||
ApplicationEventsHolder.unregisterApplicationEvents();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean recordApplicationEvents(TestContext testContext) {
|
||||
return testContext.computeAttribute(RECORD_APPLICATION_EVENTS, name ->
|
||||
TestContextAnnotationUtils.hasAnnotation(testContext.getTestClass(), RecordApplicationEvents.class));
|
||||
}
|
||||
|
||||
private void registerListenerAndResolvableDependencyIfNecessary(ApplicationContext applicationContext) {
|
||||
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext,
|
||||
"The ApplicationContext for the test must be an AbstractApplicationContext");
|
||||
AbstractApplicationContext aac = (AbstractApplicationContext) applicationContext;
|
||||
// Synchronize to avoid race condition in parallel test execution
|
||||
synchronized(applicationEventsMonitor) {
|
||||
boolean notAlreadyRegistered = aac.getApplicationListeners().stream()
|
||||
.map(Object::getClass)
|
||||
.noneMatch(ApplicationEventsApplicationListener.class::equals);
|
||||
if (notAlreadyRegistered) {
|
||||
// Register a new ApplicationEventsApplicationListener.
|
||||
aac.addApplicationListener(new ApplicationEventsApplicationListener());
|
||||
|
||||
// Register ApplicationEvents as a resolvable dependency for @Autowired support in test classes.
|
||||
ConfigurableListableBeanFactory beanFactory = aac.getBeanFactory();
|
||||
beanFactory.registerResolvableDependency(ApplicationEvents.class, new ApplicationEventsObjectFactory());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory that exposes the current {@link ApplicationEvents} object on demand.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
private static class ApplicationEventsObjectFactory implements ObjectFactory<ApplicationEvents>, Serializable {
|
||||
|
||||
@Override
|
||||
public ApplicationEvents getObject() {
|
||||
return ApplicationEventsHolder.getRequiredApplicationEvents();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Current ApplicationEvents";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author 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.event;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.PayloadApplicationEvent;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ApplicationEvents}.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
* @author Sam Brannen
|
||||
* @since 5.3.3
|
||||
*/
|
||||
class DefaultApplicationEvents implements ApplicationEvents {
|
||||
|
||||
private final List<ApplicationEvent> events = new ArrayList<>();
|
||||
|
||||
|
||||
void addEvent(ApplicationEvent event) {
|
||||
this.events.add(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ApplicationEvent> stream() {
|
||||
return this.events.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Stream<T> stream(Class<T> type) {
|
||||
return this.events.stream()
|
||||
.map(this::unwrapPayloadEvent)
|
||||
.filter(type::isInstance)
|
||||
.map(type::cast);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.events.clear();
|
||||
}
|
||||
|
||||
private Object unwrapPayloadEvent(Object source) {
|
||||
return (PayloadApplicationEvent.class.isInstance(source) ?
|
||||
((PayloadApplicationEvent<?>) source).getPayload() : source);
|
||||
}
|
||||
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author 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.event;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* {@code @RecordApplicationEvents} is a class-level annotation that is used to
|
||||
* instruct the <em>Spring TestContext Framework</em> to record all
|
||||
* {@linkplain org.springframework.context.ApplicationEvent application events}
|
||||
* that are published in the {@link org.springframework.context.ApplicationContext
|
||||
* ApplicationContext} during the execution of a single test.
|
||||
*
|
||||
* <p>The recorded events can be accessed via the {@link ApplicationEvents} API
|
||||
* within your tests.
|
||||
*
|
||||
* <p>This annotation may be used as a <em>meta-annotation</em> to create custom
|
||||
* <em>composed annotations</em>.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 5.3.3
|
||||
* @see ApplicationEvents
|
||||
* @see ApplicationEventsTestExecutionListener
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
public @interface RecordApplicationEvents {
|
||||
}
|
||||
+12
@@ -52,6 +52,7 @@ import org.springframework.core.annotation.RepeatableContainers;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.TestConstructor;
|
||||
import org.springframework.test.context.TestContextManager;
|
||||
import org.springframework.test.context.event.ApplicationEvents;
|
||||
import org.springframework.test.context.support.PropertyProvider;
|
||||
import org.springframework.test.context.support.TestConstructorUtils;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -218,6 +219,7 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
|
||||
* invoked with a fallback {@link PropertyProvider} that delegates its lookup
|
||||
* to {@link ExtensionContext#getConfigurationParameter(String)}.</li>
|
||||
* <li>The parameter is of type {@link ApplicationContext} or a sub-type thereof.</li>
|
||||
* <li>The parameter is of type {@link ApplicationEvents} or a sub-type thereof.</li>
|
||||
* <li>{@link ParameterResolutionDelegate#isAutowirable} returns {@code true}.</li>
|
||||
* </ol>
|
||||
* <p><strong>WARNING</strong>: If a test class {@code Constructor} is annotated
|
||||
@@ -238,9 +240,19 @@ public class SpringExtension implements BeforeAllCallback, AfterAllCallback, Tes
|
||||
extensionContext.getConfigurationParameter(propertyName).orElse(null);
|
||||
return (TestConstructorUtils.isAutowirableConstructor(executable, testClass, junitPropertyProvider) ||
|
||||
ApplicationContext.class.isAssignableFrom(parameter.getType()) ||
|
||||
supportsApplicationEvents(parameterContext) ||
|
||||
ParameterResolutionDelegate.isAutowirable(parameter, parameterContext.getIndex()));
|
||||
}
|
||||
|
||||
private boolean supportsApplicationEvents(ParameterContext parameterContext) {
|
||||
if (ApplicationEvents.class.isAssignableFrom(parameterContext.getParameter().getType())) {
|
||||
Assert.isTrue(parameterContext.getDeclaringExecutable() instanceof Method,
|
||||
"ApplicationEvents can only be injected into test and lifecycle methods");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a value for the {@link Parameter} in the supplied {@link ParameterContext} by
|
||||
* retrieving the corresponding dependency from the test's {@link ApplicationContext}.
|
||||
|
||||
+6
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,6 +27,7 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.TestContextManager;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.event.ApplicationEventsTestExecutionListener;
|
||||
import org.springframework.test.context.event.EventPublishingTestExecutionListener;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
import org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener;
|
||||
@@ -54,6 +55,7 @@ import org.springframework.test.context.web.ServletTestExecutionListener;
|
||||
* <ul>
|
||||
* <li>{@link org.springframework.test.context.web.ServletTestExecutionListener}
|
||||
* <li>{@link org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener}
|
||||
* <li>{@link org.springframework.test.context.event.ApplicationEventsTestExecutionListener}
|
||||
* <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener}
|
||||
* <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener}
|
||||
* <li>{@link org.springframework.test.context.event.EventPublishingTestExecutionListener}
|
||||
@@ -82,6 +84,7 @@ import org.springframework.test.context.web.ServletTestExecutionListener;
|
||||
* @see TestExecutionListeners
|
||||
* @see ServletTestExecutionListener
|
||||
* @see DirtiesContextBeforeModesTestExecutionListener
|
||||
* @see ApplicationEventsTestExecutionListener
|
||||
* @see DependencyInjectionTestExecutionListener
|
||||
* @see DirtiesContextTestExecutionListener
|
||||
* @see EventPublishingTestExecutionListener
|
||||
@@ -90,8 +93,8 @@ import org.springframework.test.context.web.ServletTestExecutionListener;
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@TestExecutionListeners({ ServletTestExecutionListener.class, DirtiesContextBeforeModesTestExecutionListener.class,
|
||||
DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class,
|
||||
EventPublishingTestExecutionListener.class })
|
||||
ApplicationEventsTestExecutionListener.class, DependencyInjectionTestExecutionListener.class,
|
||||
DirtiesContextTestExecutionListener.class, EventPublishingTestExecutionListener.class })
|
||||
public abstract class AbstractJUnit4SpringContextTests implements ApplicationContextAware {
|
||||
|
||||
/**
|
||||
|
||||
+5
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,6 +27,7 @@ import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.event.ApplicationEventsTestExecutionListener;
|
||||
import org.springframework.test.context.event.EventPublishingTestExecutionListener;
|
||||
import org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
@@ -62,6 +63,7 @@ import org.springframework.util.Assert;
|
||||
* <ul>
|
||||
* <li>{@link org.springframework.test.context.web.ServletTestExecutionListener}
|
||||
* <li>{@link org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener}
|
||||
* <li>{@link org.springframework.test.context.event.ApplicationEventsTestExecutionListener}</li>
|
||||
* <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener}
|
||||
* <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener}
|
||||
* <li>{@link org.springframework.test.context.transaction.TransactionalTestExecutionListener}
|
||||
@@ -100,8 +102,8 @@ import org.springframework.util.Assert;
|
||||
* @see org.springframework.test.jdbc.JdbcTestUtils
|
||||
* @see org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests
|
||||
*/
|
||||
@TestExecutionListeners(listeners = { ServletTestExecutionListener.class,
|
||||
DirtiesContextBeforeModesTestExecutionListener.class, DependencyInjectionTestExecutionListener.class,
|
||||
@TestExecutionListeners(listeners = { ServletTestExecutionListener.class, DirtiesContextBeforeModesTestExecutionListener.class,
|
||||
ApplicationEventsTestExecutionListener.class, DependencyInjectionTestExecutionListener.class,
|
||||
DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class,
|
||||
SqlScriptsTestExecutionListener.class, EventPublishingTestExecutionListener.class }, inheritListeners = false)
|
||||
@Transactional
|
||||
|
||||
+13
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.test.context.support;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
@@ -200,6 +201,17 @@ public class DefaultTestContext implements TestContext {
|
||||
return this.attributes.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T computeAttribute(String name, Function<String, T> computeFunction) {
|
||||
Assert.notNull(name, "Name must not be null");
|
||||
Assert.notNull(computeFunction, "Compute function must not be null");
|
||||
Object value = this.attributes.computeIfAbsent(name, computeFunction);
|
||||
Assert.state(value != null,
|
||||
() -> String.format("Compute function must not return null for attribute named '%s'", name));
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object removeAttribute(String name) {
|
||||
|
||||
+6
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author 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.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.TestContextManager;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.event.ApplicationEventsTestExecutionListener;
|
||||
import org.springframework.test.context.event.EventPublishingTestExecutionListener;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
import org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener;
|
||||
@@ -64,6 +65,7 @@ import org.springframework.test.context.web.ServletTestExecutionListener;
|
||||
* <ul>
|
||||
* <li>{@link org.springframework.test.context.web.ServletTestExecutionListener}
|
||||
* <li>{@link org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener}
|
||||
* <li>{@link org.springframework.test.context.event.ApplicationEventsTestExecutionListener}
|
||||
* <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener}
|
||||
* <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener}
|
||||
* <li>{@link org.springframework.test.context.event.EventPublishingTestExecutionListener}
|
||||
@@ -78,6 +80,7 @@ import org.springframework.test.context.web.ServletTestExecutionListener;
|
||||
* @see TestExecutionListeners
|
||||
* @see ServletTestExecutionListener
|
||||
* @see DirtiesContextBeforeModesTestExecutionListener
|
||||
* @see ApplicationEventsTestExecutionListener
|
||||
* @see DependencyInjectionTestExecutionListener
|
||||
* @see DirtiesContextTestExecutionListener
|
||||
* @see EventPublishingTestExecutionListener
|
||||
@@ -85,8 +88,8 @@ import org.springframework.test.context.web.ServletTestExecutionListener;
|
||||
* @see org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests
|
||||
*/
|
||||
@TestExecutionListeners({ ServletTestExecutionListener.class, DirtiesContextBeforeModesTestExecutionListener.class,
|
||||
DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class,
|
||||
EventPublishingTestExecutionListener.class })
|
||||
ApplicationEventsTestExecutionListener.class, DependencyInjectionTestExecutionListener.class,
|
||||
DirtiesContextTestExecutionListener.class, EventPublishingTestExecutionListener.class })
|
||||
public abstract class AbstractTestNGSpringContextTests implements IHookable, ApplicationContextAware {
|
||||
|
||||
/** Logger available to subclasses. */
|
||||
|
||||
+5
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,6 +26,7 @@ import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.event.ApplicationEventsTestExecutionListener;
|
||||
import org.springframework.test.context.event.EventPublishingTestExecutionListener;
|
||||
import org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
@@ -61,6 +62,7 @@ import org.springframework.util.Assert;
|
||||
* <ul>
|
||||
* <li>{@link org.springframework.test.context.web.ServletTestExecutionListener}
|
||||
* <li>{@link org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener}
|
||||
* <li>{@link org.springframework.test.context.event.ApplicationEventsTestExecutionListener}</li>
|
||||
* <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener}
|
||||
* <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener}
|
||||
* <li>{@link org.springframework.test.context.transaction.TransactionalTestExecutionListener}
|
||||
@@ -84,8 +86,8 @@ import org.springframework.util.Assert;
|
||||
* @see org.springframework.test.jdbc.JdbcTestUtils
|
||||
* @see org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests
|
||||
*/
|
||||
@TestExecutionListeners(listeners = { ServletTestExecutionListener.class,
|
||||
DirtiesContextBeforeModesTestExecutionListener.class, DependencyInjectionTestExecutionListener.class,
|
||||
@TestExecutionListeners(listeners = { ServletTestExecutionListener.class, DirtiesContextBeforeModesTestExecutionListener.class,
|
||||
ApplicationEventsTestExecutionListener.class, DependencyInjectionTestExecutionListener.class,
|
||||
DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class,
|
||||
SqlScriptsTestExecutionListener.class, EventPublishingTestExecutionListener.class }, inheritListeners = false)
|
||||
@Transactional
|
||||
|
||||
+9
-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.
|
||||
@@ -57,6 +57,8 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
|
||||
|
||||
private static final boolean httpComponentsClientPresent;
|
||||
|
||||
private static final boolean webFluxPresent;
|
||||
|
||||
static {
|
||||
ClassLoader loader = DefaultWebTestClientBuilder.class.getClassLoader();
|
||||
reactorClientPresent = ClassUtils.isPresent("reactor.netty.http.client.HttpClient", loader);
|
||||
@@ -64,6 +66,8 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
|
||||
httpComponentsClientPresent =
|
||||
ClassUtils.isPresent("org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient", loader) &&
|
||||
ClassUtils.isPresent("org.apache.hc.core5.reactive.ReactiveDataConsumer", loader);
|
||||
webFluxPresent = ClassUtils.isPresent(
|
||||
"org.springframework.web.reactive.function.client.ExchangeFunction", loader);
|
||||
}
|
||||
|
||||
|
||||
@@ -119,6 +123,10 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
|
||||
Assert.isTrue(httpHandlerBuilder == null || connector == null,
|
||||
"Expected WebHttpHandlerBuilder or ClientHttpConnector but not both.");
|
||||
|
||||
// Helpful message especially for MockMvcWebTestClient users
|
||||
Assert.state(webFluxPresent,
|
||||
"To use WebTestClient, please add spring-webflux to the test classpath.");
|
||||
|
||||
this.connector = connector;
|
||||
this.httpHandlerBuilder = (httpHandlerBuilder != null ? httpHandlerBuilder.clone() : null);
|
||||
}
|
||||
|
||||
+9
-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.
|
||||
@@ -24,6 +24,8 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -54,6 +56,8 @@ import org.springframework.util.MultiValueMap;
|
||||
*/
|
||||
public class ExchangeResult {
|
||||
|
||||
private static Log logger = LogFactory.getLog(ExchangeResult.class);
|
||||
|
||||
private static final List<MediaType> PRINTABLE_MEDIA_TYPES = Arrays.asList(
|
||||
MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML,
|
||||
MediaType.parseMediaType("text/*"), MediaType.APPLICATION_FORM_URLENCODED);
|
||||
@@ -223,7 +227,10 @@ public class ExchangeResult {
|
||||
assertion.run();
|
||||
}
|
||||
catch (AssertionError ex) {
|
||||
throw new AssertionError(ex.getMessage() + "\n" + this, ex);
|
||||
if (logger.isErrorEnabled()) {
|
||||
logger.error("Request details for assertion failure:\n" + this);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-5
@@ -35,7 +35,6 @@ import org.springframework.web.context.request.async.CallableProcessingIntercept
|
||||
import org.springframework.web.context.request.async.DeferredResult;
|
||||
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
|
||||
import org.springframework.web.context.request.async.WebAsyncUtils;
|
||||
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
@@ -68,10 +67,6 @@ final class TestDispatcherServlet extends DispatcherServlet {
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
|
||||
if (!request.getParts().isEmpty()) {
|
||||
request = new StandardMultipartHttpServletRequest(request);
|
||||
}
|
||||
|
||||
registerAsyncResultInterceptors(request);
|
||||
|
||||
super.service(request, response);
|
||||
|
||||
+41
-17
@@ -17,7 +17,10 @@
|
||||
package org.springframework.test.web.servlet.request;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -25,17 +28,17 @@ import java.util.List;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.mock.web.MockMultipartHttpServletRequest;
|
||||
import org.springframework.mock.web.MockPart;
|
||||
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}.
|
||||
@@ -141,26 +144,47 @@ public class MockMultipartHttpServletRequestBuilder extends MockHttpServletReque
|
||||
@Override
|
||||
protected final MockHttpServletRequest createServletRequest(ServletContext servletContext) {
|
||||
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest(servletContext);
|
||||
this.files.forEach(file -> request.addPart(toMockPart(file)));
|
||||
this.parts.values().stream().flatMap(Collection::stream).forEach(request::addPart);
|
||||
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 value = toParameterValue(part);
|
||||
if (value != null) {
|
||||
request.addParameter(part.getName(), toParameterValue(part));
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Failed to read content for part " + part.getName(), ex);
|
||||
}
|
||||
});
|
||||
return request;
|
||||
}
|
||||
|
||||
private MockPart toMockPart(MockMultipartFile file) {
|
||||
byte[] bytes = null;
|
||||
if (!file.isEmpty()) {
|
||||
try {
|
||||
bytes = file.getBytes();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Unexpected IOException", ex);
|
||||
}
|
||||
@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());
|
||||
}
|
||||
MockPart part = new MockPart(file.getName(), file.getOriginalFilename(), bytes);
|
||||
if (file.getContentType() != null) {
|
||||
part.getHeaders().set(HttpHeaders.CONTENT_TYPE, file.getContentType());
|
||||
return null;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
return part;
|
||||
Charset charset = (mediaType.getCharset() != null ? mediaType.getCharset() : StandardCharsets.UTF_8);
|
||||
InputStreamReader reader = new InputStreamReader(part.getInputStream(), charset);
|
||||
return FileCopyUtils.copyToString(reader);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
org.springframework.test.context.TestExecutionListener = \
|
||||
org.springframework.test.context.web.ServletTestExecutionListener,\
|
||||
org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener,\
|
||||
org.springframework.test.context.event.ApplicationEventsTestExecutionListener,\
|
||||
org.springframework.test.context.support.DependencyInjectionTestExecutionListener,\
|
||||
org.springframework.test.context.support.DirtiesContextTestExecutionListener,\
|
||||
org.springframework.test.context.transaction.TransactionalTestExecutionListener,\
|
||||
|
||||
+41
-19
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.test.context.event.ApplicationEventsTestExecutionListener;
|
||||
import org.springframework.test.context.event.EventPublishingTestExecutionListener;
|
||||
import org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener;
|
||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||
@@ -56,10 +57,15 @@ class TestExecutionListenersTests {
|
||||
|
||||
@Test
|
||||
void defaultListeners() {
|
||||
List<Class<?>> expected = asList(ServletTestExecutionListener.class,
|
||||
DirtiesContextBeforeModesTestExecutionListener.class, DependencyInjectionTestExecutionListener.class,
|
||||
DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class,
|
||||
SqlScriptsTestExecutionListener.class, EventPublishingTestExecutionListener.class);
|
||||
List<Class<?>> expected = asList(ServletTestExecutionListener.class,//
|
||||
DirtiesContextBeforeModesTestExecutionListener.class,//
|
||||
ApplicationEventsTestExecutionListener.class,//
|
||||
DependencyInjectionTestExecutionListener.class,//
|
||||
DirtiesContextTestExecutionListener.class,//
|
||||
TransactionalTestExecutionListener.class,//
|
||||
SqlScriptsTestExecutionListener.class,//
|
||||
EventPublishingTestExecutionListener.class
|
||||
);
|
||||
assertRegisteredListeners(DefaultListenersTestCase.class, expected);
|
||||
}
|
||||
|
||||
@@ -68,10 +74,16 @@ class TestExecutionListenersTests {
|
||||
*/
|
||||
@Test
|
||||
void defaultListenersMergedWithCustomListenerPrepended() {
|
||||
List<Class<?>> expected = asList(QuuxTestExecutionListener.class, ServletTestExecutionListener.class,
|
||||
DirtiesContextBeforeModesTestExecutionListener.class, DependencyInjectionTestExecutionListener.class,
|
||||
DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class,
|
||||
SqlScriptsTestExecutionListener.class, EventPublishingTestExecutionListener.class);
|
||||
List<Class<?>> expected = asList(QuuxTestExecutionListener.class,//
|
||||
ServletTestExecutionListener.class,//
|
||||
DirtiesContextBeforeModesTestExecutionListener.class,//
|
||||
ApplicationEventsTestExecutionListener.class,//
|
||||
DependencyInjectionTestExecutionListener.class,//
|
||||
DirtiesContextTestExecutionListener.class,//
|
||||
TransactionalTestExecutionListener.class,//
|
||||
SqlScriptsTestExecutionListener.class,//
|
||||
EventPublishingTestExecutionListener.class
|
||||
);
|
||||
assertRegisteredListeners(MergedDefaultListenersWithCustomListenerPrependedTestCase.class, expected);
|
||||
}
|
||||
|
||||
@@ -80,11 +92,16 @@ class TestExecutionListenersTests {
|
||||
*/
|
||||
@Test
|
||||
void defaultListenersMergedWithCustomListenerAppended() {
|
||||
List<Class<?>> expected = asList(ServletTestExecutionListener.class,
|
||||
DirtiesContextBeforeModesTestExecutionListener.class, DependencyInjectionTestExecutionListener.class,
|
||||
DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class,
|
||||
SqlScriptsTestExecutionListener.class, EventPublishingTestExecutionListener.class,
|
||||
BazTestExecutionListener.class);
|
||||
List<Class<?>> expected = asList(ServletTestExecutionListener.class,//
|
||||
DirtiesContextBeforeModesTestExecutionListener.class,//
|
||||
ApplicationEventsTestExecutionListener.class,//
|
||||
DependencyInjectionTestExecutionListener.class,//
|
||||
DirtiesContextTestExecutionListener.class,//
|
||||
TransactionalTestExecutionListener.class,
|
||||
SqlScriptsTestExecutionListener.class,//
|
||||
EventPublishingTestExecutionListener.class,//
|
||||
BazTestExecutionListener.class
|
||||
);
|
||||
assertRegisteredListeners(MergedDefaultListenersWithCustomListenerAppendedTestCase.class, expected);
|
||||
}
|
||||
|
||||
@@ -93,11 +110,16 @@ class TestExecutionListenersTests {
|
||||
*/
|
||||
@Test
|
||||
void defaultListenersMergedWithCustomListenerInserted() {
|
||||
List<Class<?>> expected = asList(ServletTestExecutionListener.class,
|
||||
DirtiesContextBeforeModesTestExecutionListener.class, DependencyInjectionTestExecutionListener.class,
|
||||
BarTestExecutionListener.class, DirtiesContextTestExecutionListener.class,
|
||||
TransactionalTestExecutionListener.class, SqlScriptsTestExecutionListener.class,
|
||||
EventPublishingTestExecutionListener.class);
|
||||
List<Class<?>> expected = asList(ServletTestExecutionListener.class,//
|
||||
DirtiesContextBeforeModesTestExecutionListener.class,//
|
||||
ApplicationEventsTestExecutionListener.class,//
|
||||
DependencyInjectionTestExecutionListener.class,//
|
||||
BarTestExecutionListener.class,//
|
||||
DirtiesContextTestExecutionListener.class,//
|
||||
TransactionalTestExecutionListener.class,//
|
||||
SqlScriptsTestExecutionListener.class,//
|
||||
EventPublishingTestExecutionListener.class
|
||||
);
|
||||
assertRegisteredListeners(MergedDefaultListenersWithCustomListenerInsertedTestCase.class, expected);
|
||||
}
|
||||
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author 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.junit.jupiter.event;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.test.context.event.ApplicationEventsHolder;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link PublishedEvents}.
|
||||
*
|
||||
* <p>Copied from the Moduliths project.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
* @author Sam Brannen
|
||||
* @since 5.3.3
|
||||
*/
|
||||
class DefaultPublishedEvents implements PublishedEvents {
|
||||
|
||||
@Override
|
||||
public <T> TypedPublishedEvents<T> ofType(Class<T> type) {
|
||||
return SimpleTypedPublishedEvents.of(ApplicationEventsHolder.getRequiredApplicationEvents().stream(type));
|
||||
}
|
||||
|
||||
|
||||
private static class SimpleTypedPublishedEvents<T> implements TypedPublishedEvents<T> {
|
||||
|
||||
private final List<T> events;
|
||||
|
||||
private SimpleTypedPublishedEvents(List<T> events) {
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
static <T> SimpleTypedPublishedEvents<T> of(Stream<T> stream) {
|
||||
return new SimpleTypedPublishedEvents<>(stream.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends T> TypedPublishedEvents<S> ofSubType(Class<S> subType) {
|
||||
return SimpleTypedPublishedEvents.of(getFilteredEvents(subType::isInstance)//
|
||||
.map(subType::cast));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedPublishedEvents<T> matching(Predicate<? super T> predicate) {
|
||||
return SimpleTypedPublishedEvents.of(getFilteredEvents(predicate));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> TypedPublishedEvents<T> matchingMapped(Function<T, S> mapper, Predicate<? super S> predicate) {
|
||||
return SimpleTypedPublishedEvents.of(this.events.stream().flatMap(it -> {
|
||||
S mapped = mapper.apply(it);
|
||||
return predicate.test(mapped) ? Stream.of(it) : Stream.empty();
|
||||
}));
|
||||
}
|
||||
|
||||
private Stream<T> getFilteredEvents(Predicate<? super T> predicate) {
|
||||
return this.events.stream().filter(predicate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return this.events.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.events.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+275
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author 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.junit.jupiter.event;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.event.ApplicationEvents;
|
||||
import org.springframework.test.context.event.RecordApplicationEvents;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
|
||||
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link ApplicationEvents} in conjunction with JUnit Jupiter.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 5.3.3
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
@RecordApplicationEvents
|
||||
class JUnitJupiterApplicationEventsIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
ApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
ApplicationEvents applicationEvents;
|
||||
|
||||
|
||||
@Nested
|
||||
@TestInstance(PER_METHOD)
|
||||
class TestInstancePerMethodTests {
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestMethodEvent");
|
||||
context.publishEvent(new CustomEvent("beforeEach"));
|
||||
assertCustomEvents(applicationEvents, "beforeEach");
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestMethodEvent", "CustomEvent");
|
||||
}
|
||||
|
||||
@Test
|
||||
void test1(ApplicationEvents events, TestInfo testInfo) {
|
||||
assertTestExpectations(events, testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test2(@Autowired ApplicationEvents events, TestInfo testInfo) {
|
||||
assertTestExpectations(events, testInfo);
|
||||
}
|
||||
|
||||
private void assertTestExpectations(ApplicationEvents events, TestInfo testInfo) {
|
||||
String testName = testInfo.getTestMethod().get().getName();
|
||||
|
||||
assertEventTypes(events, "PrepareTestInstanceEvent", "BeforeTestMethodEvent", "CustomEvent",
|
||||
"BeforeTestExecutionEvent");
|
||||
context.publishEvent(new CustomEvent(testName));
|
||||
context.publishEvent("payload1");
|
||||
context.publishEvent("payload2");
|
||||
assertCustomEvents(events, "beforeEach", testName);
|
||||
assertPayloads(events.stream(String.class), "payload1", "payload2");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void afterEach(@Autowired ApplicationEvents events, TestInfo testInfo) {
|
||||
String testName = testInfo.getTestMethod().get().getName();
|
||||
|
||||
assertEventTypes(events, "PrepareTestInstanceEvent", "BeforeTestMethodEvent", "CustomEvent",
|
||||
"BeforeTestExecutionEvent", "CustomEvent", "PayloadApplicationEvent", "PayloadApplicationEvent",
|
||||
"AfterTestExecutionEvent");
|
||||
context.publishEvent(new CustomEvent("afterEach"));
|
||||
assertCustomEvents(events, "beforeEach", testName, "afterEach");
|
||||
assertEventTypes(events, "PrepareTestInstanceEvent", "BeforeTestMethodEvent", "CustomEvent",
|
||||
"BeforeTestExecutionEvent", "CustomEvent", "PayloadApplicationEvent", "PayloadApplicationEvent",
|
||||
"AfterTestExecutionEvent", "CustomEvent");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(PER_METHOD)
|
||||
class TestInstancePerMethodWithClearedEventsTests {
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestMethodEvent");
|
||||
context.publishEvent(new CustomEvent("beforeEach"));
|
||||
assertCustomEvents(applicationEvents, "beforeEach");
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestMethodEvent", "CustomEvent");
|
||||
applicationEvents.clear();
|
||||
assertThat(applicationEvents.stream()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void test1(ApplicationEvents events, TestInfo testInfo) {
|
||||
assertTestExpectations(events, testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test2(@Autowired ApplicationEvents events, TestInfo testInfo) {
|
||||
assertTestExpectations(events, testInfo);
|
||||
}
|
||||
|
||||
private void assertTestExpectations(ApplicationEvents events, TestInfo testInfo) {
|
||||
String testName = testInfo.getTestMethod().get().getName();
|
||||
|
||||
assertEventTypes(events, "BeforeTestExecutionEvent");
|
||||
context.publishEvent(new CustomEvent(testName));
|
||||
assertCustomEvents(events, testName);
|
||||
assertEventTypes(events, "BeforeTestExecutionEvent", "CustomEvent");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void afterEach(@Autowired ApplicationEvents events, TestInfo testInfo) {
|
||||
events.clear();
|
||||
context.publishEvent(new CustomEvent("afterEach"));
|
||||
assertCustomEvents(events, "afterEach");
|
||||
assertEventTypes(events, "CustomEvent");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(PER_CLASS)
|
||||
class TestInstancePerClassTests {
|
||||
|
||||
private boolean testAlreadyExecuted = false;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach(TestInfo testInfo) {
|
||||
if (!testAlreadyExecuted) {
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestClassEvent",
|
||||
"BeforeTestMethodEvent");
|
||||
}
|
||||
else {
|
||||
assertEventTypes(applicationEvents, "BeforeTestMethodEvent");
|
||||
}
|
||||
|
||||
context.publishEvent(new CustomEvent("beforeEach"));
|
||||
assertCustomEvents(applicationEvents, "beforeEach");
|
||||
|
||||
if (!testAlreadyExecuted) {
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestClassEvent",
|
||||
"BeforeTestMethodEvent", "CustomEvent");
|
||||
}
|
||||
else {
|
||||
assertEventTypes(applicationEvents, "BeforeTestMethodEvent", "CustomEvent");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void test1(ApplicationEvents events, TestInfo testInfo) {
|
||||
assertTestExpectations(events, testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test2(@Autowired ApplicationEvents events, TestInfo testInfo) {
|
||||
assertTestExpectations(events, testInfo);
|
||||
}
|
||||
|
||||
private void assertTestExpectations(ApplicationEvents events, TestInfo testInfo) {
|
||||
String testName = testInfo.getTestMethod().get().getName();
|
||||
|
||||
if (!testAlreadyExecuted) {
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestClassEvent",
|
||||
"BeforeTestMethodEvent", "CustomEvent", "BeforeTestExecutionEvent");
|
||||
}
|
||||
else {
|
||||
assertEventTypes(applicationEvents, "BeforeTestMethodEvent", "CustomEvent", "BeforeTestExecutionEvent");
|
||||
}
|
||||
|
||||
context.publishEvent(new CustomEvent(testName));
|
||||
assertCustomEvents(events, "beforeEach", testName);
|
||||
|
||||
if (!testAlreadyExecuted) {
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestClassEvent",
|
||||
"BeforeTestMethodEvent", "CustomEvent", "BeforeTestExecutionEvent", "CustomEvent");
|
||||
}
|
||||
else {
|
||||
assertEventTypes(applicationEvents, "BeforeTestMethodEvent", "CustomEvent", "BeforeTestExecutionEvent",
|
||||
"CustomEvent");
|
||||
}
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void afterEach(@Autowired ApplicationEvents events, TestInfo testInfo) {
|
||||
String testName = testInfo.getTestMethod().get().getName();
|
||||
|
||||
if (!testAlreadyExecuted) {
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestClassEvent",
|
||||
"BeforeTestMethodEvent", "CustomEvent", "BeforeTestExecutionEvent", "CustomEvent",
|
||||
"AfterTestExecutionEvent");
|
||||
}
|
||||
else {
|
||||
assertEventTypes(applicationEvents, "BeforeTestMethodEvent", "CustomEvent", "BeforeTestExecutionEvent",
|
||||
"CustomEvent", "AfterTestExecutionEvent");
|
||||
}
|
||||
|
||||
context.publishEvent(new CustomEvent("afterEach"));
|
||||
assertCustomEvents(events, "beforeEach", testName, "afterEach");
|
||||
|
||||
if (!testAlreadyExecuted) {
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestClassEvent",
|
||||
"BeforeTestMethodEvent", "CustomEvent", "BeforeTestExecutionEvent", "CustomEvent",
|
||||
"AfterTestExecutionEvent", "CustomEvent");
|
||||
testAlreadyExecuted = true;
|
||||
}
|
||||
else {
|
||||
assertEventTypes(applicationEvents, "BeforeTestMethodEvent", "CustomEvent", "BeforeTestExecutionEvent",
|
||||
"CustomEvent", "AfterTestExecutionEvent", "CustomEvent");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void assertEventTypes(ApplicationEvents applicationEvents, String... types) {
|
||||
assertThat(applicationEvents.stream().map(event -> event.getClass().getSimpleName()))
|
||||
.containsExactly(types);
|
||||
}
|
||||
|
||||
private static void assertPayloads(Stream<String> events, String... values) {
|
||||
assertThat(events).extracting(Object::toString).containsExactly(values);
|
||||
}
|
||||
|
||||
private static void assertCustomEvents(ApplicationEvents events, String... messages) {
|
||||
assertThat(events.stream(CustomEvent.class)).extracting(CustomEvent::getMessage).containsExactly(messages);
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
static class CustomEvent extends ApplicationEvent {
|
||||
|
||||
private final String message;
|
||||
|
||||
|
||||
CustomEvent(String message) {
|
||||
super(message);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author 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.junit.jupiter.event;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
||||
import org.junit.jupiter.api.parallel.Execution;
|
||||
import org.junit.jupiter.api.parallel.ExecutionMode;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.junit.platform.testkit.engine.EngineTestKit;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.event.ApplicationEvents;
|
||||
import org.springframework.test.context.event.RecordApplicationEvents;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
|
||||
|
||||
/**
|
||||
* Integration tests that verify parallel execution support for {@link ApplicationEvents}
|
||||
* in conjunction with JUnit Jupiter.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 5.3.3
|
||||
*/
|
||||
class ParallelApplicationEventsIntegrationTests {
|
||||
|
||||
private static final Set<String> payloads = ConcurrentHashMap.newKeySet();
|
||||
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {TestInstancePerMethodTestCase.class, TestInstancePerClassTestCase.class})
|
||||
void executeTestsInParallel(Class<?> testClass) {
|
||||
EngineTestKit.engine("junit-jupiter")//
|
||||
.selectors(selectClass(testClass))//
|
||||
.configurationParameter("junit.jupiter.execution.parallel.enabled", "true")//
|
||||
.configurationParameter("junit.jupiter.execution.parallel.config.dynamic.factor", "10")//
|
||||
.execute()//
|
||||
.testEvents()//
|
||||
.assertStatistics(stats -> stats.started(10).succeeded(10).failed(0));
|
||||
|
||||
Set<String> testNames = payloads.stream()//
|
||||
.map(payload -> payload.substring(0, payload.indexOf("-")))//
|
||||
.collect(Collectors.toSet());
|
||||
Set<String> threadNames = payloads.stream()//
|
||||
.map(payload -> payload.substring(payload.indexOf("-")))//
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@AfterEach
|
||||
void resetPayloads() {
|
||||
payloads.clear();
|
||||
}
|
||||
|
||||
|
||||
@SpringJUnitConfig
|
||||
@RecordApplicationEvents
|
||||
@Execution(ExecutionMode.CONCURRENT)
|
||||
@TestInstance(Lifecycle.PER_METHOD)
|
||||
static class TestInstancePerMethodTestCase {
|
||||
|
||||
@Autowired
|
||||
ApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
ApplicationEvents events;
|
||||
|
||||
@Test
|
||||
void test1(TestInfo testInfo) {
|
||||
assertTestExpectations(this.events, testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test2(ApplicationEvents events, TestInfo testInfo) {
|
||||
assertTestExpectations(events, testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test3(TestInfo testInfo) {
|
||||
assertTestExpectations(this.events, testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test4(ApplicationEvents events, TestInfo testInfo) {
|
||||
assertTestExpectations(events, testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test5(TestInfo testInfo) {
|
||||
assertTestExpectations(this.events, testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test6(ApplicationEvents events, TestInfo testInfo) {
|
||||
assertTestExpectations(events, testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test7(TestInfo testInfo) {
|
||||
assertTestExpectations(this.events, testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test8(ApplicationEvents events, TestInfo testInfo) {
|
||||
assertTestExpectations(events, testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test9(TestInfo testInfo) {
|
||||
assertTestExpectations(this.events, testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test10(ApplicationEvents events, TestInfo testInfo) {
|
||||
assertTestExpectations(events, testInfo);
|
||||
}
|
||||
|
||||
private void assertTestExpectations(ApplicationEvents events, TestInfo testInfo) {
|
||||
String testName = testInfo.getTestMethod().get().getName();
|
||||
String threadName = Thread.currentThread().getName();
|
||||
String localPayload = testName + "-" + threadName;
|
||||
context.publishEvent(localPayload);
|
||||
assertPayloads(events.stream(String.class), localPayload);
|
||||
}
|
||||
|
||||
private static void assertPayloads(Stream<String> events, String... values) {
|
||||
assertThat(events.peek(payloads::add)).extracting(Object::toString).containsExactly(values);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@TestInstance(Lifecycle.PER_CLASS)
|
||||
static class TestInstancePerClassTestCase extends TestInstancePerMethodTestCase {
|
||||
}
|
||||
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author 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.junit.jupiter.event;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* All Spring application events fired during the test execution.
|
||||
*
|
||||
* <p>Copied from the Moduliths project.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
* @since 5.3.3
|
||||
*/
|
||||
public interface PublishedEvents {
|
||||
|
||||
/**
|
||||
* Creates a new {@link PublishedEvents} instance for the given events.
|
||||
*
|
||||
* @param events must not be {@literal null}
|
||||
* @return will never be {@literal null}
|
||||
*/
|
||||
public static PublishedEvents of(Object... events) {
|
||||
return of(Arrays.asList(events));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all application events of the given type that were fired during the test execution.
|
||||
*
|
||||
* @param <T> the event type
|
||||
* @param type must not be {@literal null}
|
||||
*/
|
||||
<T> TypedPublishedEvents<T> ofType(Class<T> type);
|
||||
|
||||
/**
|
||||
* All application events of a given type that were fired during a test execution.
|
||||
*
|
||||
* @param <T> the event type
|
||||
*/
|
||||
interface TypedPublishedEvents<T> extends Iterable<T> {
|
||||
|
||||
/**
|
||||
* Further constrain the event type for downstream assertions.
|
||||
*
|
||||
* @param subType the sub type
|
||||
* @return will never be {@literal null}
|
||||
*/
|
||||
<S extends T> TypedPublishedEvents<S> ofSubType(Class<S> subType);
|
||||
|
||||
/**
|
||||
* Returns all {@link TypedPublishedEvents} that match the given predicate.
|
||||
*
|
||||
* @param predicate must not be {@literal null}
|
||||
* @return will never be {@literal null}
|
||||
*/
|
||||
TypedPublishedEvents<T> matching(Predicate<? super T> predicate);
|
||||
|
||||
/**
|
||||
* Returns all {@link TypedPublishedEvents} that match the given predicate
|
||||
* after applying the given mapping step.
|
||||
*
|
||||
* @param <S> the intermediate type to apply the {@link Predicate} on
|
||||
* @param mapper the mapping step to extract a part of the original event
|
||||
* subject to test for the {@link Predicate}
|
||||
* @param predicate the {@link Predicate} to apply on the value extracted
|
||||
* @return will never be {@literal null}
|
||||
*/
|
||||
<S> TypedPublishedEvents<T> matchingMapped(Function<T, S> mapper, Predicate<? super S> predicate);
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author 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.junit.jupiter.event;
|
||||
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
import org.junit.jupiter.api.extension.ParameterContext;
|
||||
import org.junit.jupiter.api.extension.ParameterResolver;
|
||||
|
||||
/**
|
||||
* @author Sam Brannen
|
||||
* @since 5.3.3
|
||||
*/
|
||||
public class PublishedEventsExtension implements ParameterResolver {
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
|
||||
return PublishedEvents.class.isAssignableFrom(parameterContext.getParameter().getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublishedEvents resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
|
||||
return new DefaultPublishedEvents();
|
||||
}
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author 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.junit.jupiter.event;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.event.ApplicationEvents;
|
||||
import org.springframework.test.context.event.BeforeTestExecutionEvent;
|
||||
import org.springframework.test.context.event.BeforeTestMethodEvent;
|
||||
import org.springframework.test.context.event.PrepareTestInstanceEvent;
|
||||
import org.springframework.test.context.event.RecordApplicationEvents;
|
||||
import org.springframework.test.context.event.TestContextEvent;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for the example {@link PublishedEvents} extension to the
|
||||
* {@link ApplicationEvents} feature.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 5.3.3
|
||||
*/
|
||||
@SpringJUnitConfig
|
||||
@RecordApplicationEvents
|
||||
@ExtendWith(PublishedEventsExtension.class)
|
||||
class PublishedEventsIntegrationTests {
|
||||
|
||||
@Test
|
||||
void test(PublishedEvents publishedEvents) {
|
||||
assertThat(publishedEvents).isNotNull();
|
||||
assertThat(publishedEvents.ofType(TestContextEvent.class)).hasSize(3);
|
||||
assertThat(publishedEvents.ofType(PrepareTestInstanceEvent.class)).hasSize(1);
|
||||
assertThat(publishedEvents.ofType(BeforeTestMethodEvent.class)).hasSize(1);
|
||||
assertThat(publishedEvents.ofType(BeforeTestExecutionEvent.class)).hasSize(1);
|
||||
assertThat(publishedEvents.ofType(TestContextEvent.class).ofSubType(BeforeTestExecutionEvent.class)).hasSize(1);
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
}
|
||||
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author 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.junit4.event;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestName;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.event.ApplicationEvents;
|
||||
import org.springframework.test.context.event.RecordApplicationEvents;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link ApplicationEvents} in conjunction with JUnit 4.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 5.3.3
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@RecordApplicationEvents
|
||||
public class JUnit4ApplicationEventsIntegrationTests {
|
||||
|
||||
@Rule
|
||||
public final TestName testName = new TestName();
|
||||
|
||||
@Autowired
|
||||
ApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
ApplicationEvents applicationEvents;
|
||||
|
||||
|
||||
@Before
|
||||
public void beforeEach() {
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestMethodEvent");
|
||||
context.publishEvent(new CustomEvent("beforeEach"));
|
||||
assertThat(applicationEvents.stream(CustomEvent.class)).extracting(CustomEvent::getMessage)//
|
||||
.containsExactly("beforeEach");
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestMethodEvent", "CustomEvent");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test1() {
|
||||
assertTestExpectations("test1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test2() {
|
||||
assertTestExpectations("test2");
|
||||
}
|
||||
|
||||
private void assertTestExpectations(String testName) {
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestMethodEvent", "CustomEvent",
|
||||
"BeforeTestExecutionEvent");
|
||||
context.publishEvent(new CustomEvent(testName));
|
||||
assertThat(applicationEvents.stream(CustomEvent.class)).extracting(CustomEvent::getMessage)//
|
||||
.containsExactly("beforeEach", testName);
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestMethodEvent", "CustomEvent",
|
||||
"BeforeTestExecutionEvent", "CustomEvent");
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterEach() {
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestMethodEvent", "CustomEvent",
|
||||
"BeforeTestExecutionEvent", "CustomEvent", "AfterTestExecutionEvent");
|
||||
context.publishEvent(new CustomEvent("afterEach"));
|
||||
assertThat(applicationEvents.stream(CustomEvent.class)).extracting(CustomEvent::getMessage)//
|
||||
.containsExactly("beforeEach", this.testName.getMethodName(), "afterEach");
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestMethodEvent", "CustomEvent",
|
||||
"BeforeTestExecutionEvent", "CustomEvent", "AfterTestExecutionEvent", "CustomEvent");
|
||||
}
|
||||
|
||||
|
||||
private static void assertEventTypes(ApplicationEvents applicationEvents, String... types) {
|
||||
assertThat(applicationEvents.stream().map(event -> event.getClass().getSimpleName()))
|
||||
.containsExactly(types);
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
static class CustomEvent extends ApplicationEvent {
|
||||
|
||||
private final String message;
|
||||
|
||||
|
||||
CustomEvent(String message) {
|
||||
super(message);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author 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.testng.event;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.testng.annotations.AfterMethod;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.event.ApplicationEvents;
|
||||
import org.springframework.test.context.event.RecordApplicationEvents;
|
||||
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link ApplicationEvents} in conjunction with TestNG.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 5.3.3
|
||||
*/
|
||||
@RecordApplicationEvents
|
||||
class TestNGApplicationEventsIntegrationTests extends AbstractTestNGSpringContextTests {
|
||||
|
||||
@Autowired
|
||||
ApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
ApplicationEvents applicationEvents;
|
||||
|
||||
private boolean testAlreadyExecuted = false;
|
||||
|
||||
|
||||
@BeforeMethod
|
||||
void beforeEach() {
|
||||
if (!testAlreadyExecuted) {
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestMethodEvent");
|
||||
}
|
||||
else {
|
||||
assertEventTypes(applicationEvents, "BeforeTestMethodEvent");
|
||||
}
|
||||
|
||||
context.publishEvent(new CustomEvent("beforeEach"));
|
||||
assertThat(applicationEvents.stream(CustomEvent.class)).extracting(CustomEvent::getMessage)//
|
||||
.containsExactly("beforeEach");
|
||||
|
||||
if (!testAlreadyExecuted) {
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestMethodEvent", "CustomEvent");
|
||||
}
|
||||
else {
|
||||
assertEventTypes(applicationEvents, "BeforeTestMethodEvent", "CustomEvent");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void test1() {
|
||||
assertTestExpectations("test1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void test2() {
|
||||
assertTestExpectations("test2");
|
||||
}
|
||||
|
||||
private void assertTestExpectations(String testName) {
|
||||
if (!testAlreadyExecuted) {
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestMethodEvent", "CustomEvent",
|
||||
"BeforeTestExecutionEvent");
|
||||
}
|
||||
else {
|
||||
assertEventTypes(applicationEvents, "BeforeTestMethodEvent", "CustomEvent", "BeforeTestExecutionEvent");
|
||||
}
|
||||
|
||||
context.publishEvent(new CustomEvent(testName));
|
||||
assertThat(applicationEvents.stream(CustomEvent.class)).extracting(CustomEvent::getMessage)//
|
||||
.containsExactly("beforeEach", testName);
|
||||
|
||||
if (!testAlreadyExecuted) {
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestMethodEvent", "CustomEvent",
|
||||
"BeforeTestExecutionEvent", "CustomEvent");
|
||||
}
|
||||
else {
|
||||
assertEventTypes(applicationEvents, "BeforeTestMethodEvent", "CustomEvent", "BeforeTestExecutionEvent",
|
||||
"CustomEvent");
|
||||
}
|
||||
}
|
||||
|
||||
@AfterMethod
|
||||
void afterEach(Method testMethod) {
|
||||
if (!testAlreadyExecuted) {
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestMethodEvent", "CustomEvent",
|
||||
"BeforeTestExecutionEvent", "CustomEvent", "AfterTestExecutionEvent");
|
||||
}
|
||||
else {
|
||||
assertEventTypes(applicationEvents, "BeforeTestMethodEvent", "CustomEvent", "BeforeTestExecutionEvent",
|
||||
"CustomEvent", "AfterTestExecutionEvent");
|
||||
}
|
||||
|
||||
context.publishEvent(new CustomEvent("afterEach"));
|
||||
assertThat(applicationEvents.stream(CustomEvent.class)).extracting(CustomEvent::getMessage)//
|
||||
.containsExactly("beforeEach", testMethod.getName(), "afterEach");
|
||||
|
||||
if (!testAlreadyExecuted) {
|
||||
assertEventTypes(applicationEvents, "PrepareTestInstanceEvent", "BeforeTestMethodEvent", "CustomEvent",
|
||||
"BeforeTestExecutionEvent", "CustomEvent", "AfterTestExecutionEvent", "CustomEvent");
|
||||
testAlreadyExecuted = true;
|
||||
}
|
||||
else {
|
||||
assertEventTypes(applicationEvents, "BeforeTestMethodEvent", "CustomEvent", "BeforeTestExecutionEvent",
|
||||
"CustomEvent", "AfterTestExecutionEvent", "CustomEvent");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void assertEventTypes(ApplicationEvents applicationEvents, String... types) {
|
||||
assertThat(applicationEvents.stream().map(event -> event.getClass().getSimpleName()))
|
||||
.containsExactly(types);
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
static class CustomEvent extends ApplicationEvent {
|
||||
|
||||
private final String message;
|
||||
|
||||
|
||||
CustomEvent(String message) {
|
||||
super(message);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+6
-7
@@ -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.
|
||||
@@ -100,7 +100,7 @@ class HeaderAssertionTests {
|
||||
// Wrong pattern
|
||||
assertThatExceptionOfType(AssertionError.class)
|
||||
.isThrownBy(() -> assertions.valueMatches("Content-Type", ".*ISO-8859-1.*"))
|
||||
.satisfies(ex -> assertThat(ex.getCause()).hasMessage("Response header " +
|
||||
.satisfies(ex -> assertThat(ex).hasMessage("Response header " +
|
||||
"'Content-Type'=[application/json;charset=UTF-8] does not match " +
|
||||
"[.*ISO-8859-1.*]"));
|
||||
}
|
||||
@@ -117,13 +117,13 @@ class HeaderAssertionTests {
|
||||
|
||||
assertThatExceptionOfType(AssertionError.class)
|
||||
.isThrownBy(() -> assertions.valuesMatch("foo", ".*", "val.*5"))
|
||||
.satisfies(ex -> assertThat(ex.getCause()).hasMessage(
|
||||
.satisfies(ex -> assertThat(ex).hasMessage(
|
||||
"Response header 'foo' has fewer or more values [value1, value2, value3] " +
|
||||
"than number of patterns to match with [.*, val.*5]"));
|
||||
|
||||
assertThatExceptionOfType(AssertionError.class)
|
||||
.isThrownBy(() -> assertions.valuesMatch("foo", ".*", "val.*5", ".*"))
|
||||
.satisfies(ex -> assertThat(ex.getCause()).hasMessage(
|
||||
.satisfies(ex -> assertThat(ex).hasMessage(
|
||||
"Response header 'foo'[1]='value2' does not match 'val.*5'"));
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ class HeaderAssertionTests {
|
||||
// Header should not exist
|
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
|
||||
assertions.exists("Framework"))
|
||||
.satisfies(ex -> assertThat(ex.getCause()).hasMessage("Response header 'Framework' does not exist"));
|
||||
.satisfies(ex -> assertThat(ex).hasMessage("Response header 'Framework' does not exist"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -173,7 +173,7 @@ class HeaderAssertionTests {
|
||||
// Existing header
|
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
|
||||
assertions.doesNotExist("Content-Type"))
|
||||
.satisfies(ex -> assertThat(ex.getCause()).hasMessage("Response header " +
|
||||
.satisfies(ex -> assertThat(ex).hasMessage("Response header " +
|
||||
"'Content-Type' exists with value=[application/json;charset=UTF-8]"));
|
||||
}
|
||||
|
||||
@@ -189,7 +189,6 @@ class HeaderAssertionTests {
|
||||
// MediaTypes not compatible
|
||||
assertThatExceptionOfType(AssertionError.class)
|
||||
.isThrownBy(() -> assertions.contentTypeCompatibleWith(MediaType.TEXT_XML))
|
||||
.havingCause()
|
||||
.withMessage("Response header 'Content-Type'=[application/xml] is not compatible with [text/xml]");
|
||||
}
|
||||
|
||||
|
||||
+19
-26
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author 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,17 +16,14 @@
|
||||
|
||||
package org.springframework.test.web.servlet;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
@@ -47,29 +44,25 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppC
|
||||
* @author Rob Winch
|
||||
* @since 4.2
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class MockMvcReuseTests {
|
||||
@SpringJUnitWebConfig
|
||||
@TestInstance(Lifecycle.PER_CLASS)
|
||||
class MockMvcReuseTests {
|
||||
|
||||
private static final String HELLO = "hello";
|
||||
private static final String ENIGMA = "enigma";
|
||||
private static final String FOO = "foo";
|
||||
private static final String BAR = "bar";
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext wac;
|
||||
|
||||
private MockMvc mvc;
|
||||
private final MockMvc mvc;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
this.mvc = webAppContextSetup(this.wac).build();
|
||||
MockMvcReuseTests(WebApplicationContext wac) {
|
||||
this.mvc = webAppContextSetup(wac).build();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void sessionAttributesAreClearedBetweenInvocations() throws Exception {
|
||||
void sessionAttributesAreClearedBetweenInvocations() throws Exception {
|
||||
|
||||
this.mvc.perform(get("/"))
|
||||
.andExpect(content().string(HELLO))
|
||||
@@ -85,7 +78,7 @@ public class MockMvcReuseTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestParametersAreClearedBetweenInvocations() throws Exception {
|
||||
void requestParametersAreClearedBetweenInvocations() throws Exception {
|
||||
this.mvc.perform(get("/"))
|
||||
.andExpect(content().string(HELLO));
|
||||
|
||||
@@ -102,7 +95,7 @@ public class MockMvcReuseTests {
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public MyController myController() {
|
||||
MyController myController() {
|
||||
return new MyController();
|
||||
}
|
||||
}
|
||||
@@ -110,13 +103,13 @@ public class MockMvcReuseTests {
|
||||
@RestController
|
||||
static class MyController {
|
||||
|
||||
@RequestMapping("/")
|
||||
public String hello() {
|
||||
@GetMapping("/")
|
||||
String hello() {
|
||||
return HELLO;
|
||||
}
|
||||
|
||||
@RequestMapping(path = "/", params = ENIGMA)
|
||||
public String enigma() {
|
||||
@GetMapping(path = "/", params = ENIGMA)
|
||||
String enigma() {
|
||||
return ENIGMA;
|
||||
}
|
||||
}
|
||||
|
||||
+27
-12
@@ -16,19 +16,19 @@
|
||||
|
||||
package org.springframework.test.web.servlet.request;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.mock.web.MockMultipartHttpServletRequest;
|
||||
import org.springframework.mock.web.MockPart;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
@@ -38,17 +38,32 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
public class MockMultipartHttpServletRequestBuilderTests {
|
||||
|
||||
@Test // gh-26166
|
||||
void addFilesAndParts() throws Exception {
|
||||
MockHttpServletRequest mockRequest = new MockMultipartHttpServletRequestBuilder("/upload")
|
||||
.file(new MockMultipartFile("file", "test.txt", "text/plain", "Test".getBytes(StandardCharsets.UTF_8)))
|
||||
.part(new MockPart("data", "{\"node\":\"node\"}".getBytes(StandardCharsets.UTF_8)))
|
||||
.buildRequest(new MockServletContext());
|
||||
void addFileAndParts() throws Exception {
|
||||
MockMultipartHttpServletRequest mockRequest =
|
||||
(MockMultipartHttpServletRequest) new MockMultipartHttpServletRequestBuilder("/upload")
|
||||
.file(new MockMultipartFile("file", "test.txt", "text/plain", "Test".getBytes(UTF_8)))
|
||||
.part(new MockPart("name", "value".getBytes(UTF_8)))
|
||||
.buildRequest(new MockServletContext());
|
||||
|
||||
StandardMultipartHttpServletRequest parsedRequest = new StandardMultipartHttpServletRequest(mockRequest);
|
||||
assertThat(mockRequest.getFileMap()).containsOnlyKeys("file");
|
||||
assertThat(mockRequest.getParameterMap()).containsOnlyKeys("name");
|
||||
assertThat(mockRequest.getParts()).extracting(Part::getName).containsExactly("name");
|
||||
}
|
||||
|
||||
assertThat(parsedRequest.getParameterMap()).containsOnlyKeys("data");
|
||||
assertThat(parsedRequest.getFileMap()).containsOnlyKeys("file");
|
||||
assertThat(parsedRequest.getParts()).extracting(Part::getName).containsExactly("file", "data");
|
||||
@Test // gh-26261
|
||||
void addFileWithoutFilename() throws Exception {
|
||||
MockPart jsonPart = new MockPart("data", "{\"node\":\"node\"}".getBytes(UTF_8));
|
||||
jsonPart.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
MockMultipartHttpServletRequest mockRequest =
|
||||
(MockMultipartHttpServletRequest) new MockMultipartHttpServletRequestBuilder("/upload")
|
||||
.file(new MockMultipartFile("file", "Test".getBytes(UTF_8)))
|
||||
.part(jsonPart)
|
||||
.buildRequest(new MockServletContext());
|
||||
|
||||
assertThat(mockRequest.getFileMap()).containsOnlyKeys("file");
|
||||
assertThat(mockRequest.getParameterMap()).isEmpty();
|
||||
assertThat(mockRequest.getParts()).extracting(Part::getName).containsExactly("data");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
-1
@@ -123,7 +123,7 @@ public class JettyClientHttpConnector implements ClientHttpConnector {
|
||||
Request request = this.httpClient.newRequest(uri).method(method.toString());
|
||||
|
||||
return requestCallback.apply(new JettyClientHttpRequest(request, this.bufferFactory))
|
||||
.then(Mono.fromDirect(ReactiveRequest.newBuilder(request).build()
|
||||
.then(Mono.fromDirect(ReactiveRequest.newBuilder(request).abortOnCancel(true).build()
|
||||
.response((reactiveResponse, chunkPublisher) -> {
|
||||
Flux<DataBuffer> content = Flux.from(chunkPublisher).map(this::toDataBuffer);
|
||||
return Mono.just(new JettyClientHttpResponse(reactiveResponse, content));
|
||||
|
||||
+50
-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.
|
||||
@@ -154,7 +154,7 @@ class JettyHeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return this.headers.getFieldNamesCollection();
|
||||
return new HeaderNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -227,4 +227,52 @@ class JettyHeadersAdapter implements MultiValueMap<String, String> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class HeaderNames extends AbstractSet<String> {
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return new HeaderNamesIterator(headers.getFieldNamesCollection().iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return headers.getFieldNamesCollection().size();
|
||||
}
|
||||
}
|
||||
|
||||
private final class HeaderNamesIterator implements Iterator<String> {
|
||||
|
||||
private final Iterator<String> iterator;
|
||||
|
||||
@Nullable
|
||||
private String currentName;
|
||||
|
||||
private HeaderNamesIterator(Iterator<String> iterator) {
|
||||
this.iterator = iterator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
this.currentName = this.iterator.next();
|
||||
return this.currentName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (this.currentName == null) {
|
||||
throw new IllegalStateException("No current Header in iterator");
|
||||
}
|
||||
if (!headers.containsKey(this.currentName)) {
|
||||
throw new IllegalStateException("Header not present: " + this.currentName);
|
||||
}
|
||||
headers.remove(this.currentName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+56
-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.
|
||||
@@ -56,7 +56,9 @@ class NettyHeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
@Override
|
||||
public void add(String key, @Nullable String value) {
|
||||
this.headers.add(key, value);
|
||||
if (value != null) {
|
||||
this.headers.add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -71,7 +73,9 @@ class NettyHeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
@Override
|
||||
public void set(String key, @Nullable String value) {
|
||||
this.headers.set(key, value);
|
||||
if (value != null) {
|
||||
this.headers.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -153,7 +157,7 @@ class NettyHeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return this.headers.names();
|
||||
return new HeaderNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -226,4 +230,52 @@ class NettyHeadersAdapter implements MultiValueMap<String, String> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class HeaderNames extends AbstractSet<String> {
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return new HeaderNamesIterator(headers.names().iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return headers.names().size();
|
||||
}
|
||||
}
|
||||
|
||||
private final class HeaderNamesIterator implements Iterator<String> {
|
||||
|
||||
private final Iterator<String> iterator;
|
||||
|
||||
@Nullable
|
||||
private String currentName;
|
||||
|
||||
private HeaderNamesIterator(Iterator<String> iterator) {
|
||||
this.iterator = iterator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
this.currentName = this.iterator.next();
|
||||
return this.currentName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (this.currentName == null) {
|
||||
throw new IllegalStateException("No current Header in iterator");
|
||||
}
|
||||
if (!headers.contains(this.currentName)) {
|
||||
throw new IllegalStateException("Header not present: " + this.currentName);
|
||||
}
|
||||
headers.remove(this.currentName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-1
@@ -21,6 +21,7 @@ import java.util.Map;
|
||||
|
||||
import kotlinx.serialization.KSerializer;
|
||||
import kotlinx.serialization.SerializersKt;
|
||||
import kotlinx.serialization.descriptors.PolymorphicKind;
|
||||
import kotlinx.serialization.json.Json;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -39,7 +40,9 @@ import org.springframework.util.MimeType;
|
||||
* Decode a byte stream into JSON and convert to Object's with
|
||||
* <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>.
|
||||
*
|
||||
* <p>This decoder can be used to bind {@code @Serializable} Kotlin classes.
|
||||
* <p>This decoder can be used to bind {@code @Serializable} Kotlin classes,
|
||||
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphic serialization</a>
|
||||
* is not supported.
|
||||
* It supports {@code application/json} and {@code application/*+json} with
|
||||
* various character sets, {@code UTF-8} being the default.
|
||||
*
|
||||
@@ -132,6 +135,9 @@ public class KotlinSerializationJsonDecoder extends AbstractDecoder<Object> {
|
||||
KSerializer<Object> serializer = serializerCache.get(type);
|
||||
if (serializer == null) {
|
||||
serializer = SerializersKt.serializer(type);
|
||||
if (serializer.getDescriptor().getKind().equals(PolymorphicKind.OPEN.INSTANCE)) {
|
||||
throw new UnsupportedOperationException("Open polymorphic serialization is not supported yet");
|
||||
}
|
||||
serializerCache.put(type, serializer);
|
||||
}
|
||||
return serializer;
|
||||
|
||||
+7
-1
@@ -22,6 +22,7 @@ import java.util.Map;
|
||||
|
||||
import kotlinx.serialization.KSerializer;
|
||||
import kotlinx.serialization.SerializersKt;
|
||||
import kotlinx.serialization.descriptors.PolymorphicKind;
|
||||
import kotlinx.serialization.json.Json;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -42,7 +43,9 @@ import org.springframework.util.MimeType;
|
||||
* Encode from an {@code Object} stream to a byte stream of JSON objects using
|
||||
* <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>.
|
||||
*
|
||||
* <p>This encoder can be used to bind {@code @Serializable} Kotlin classes.
|
||||
* <p>This encoder can be used to bind {@code @Serializable} Kotlin classes,
|
||||
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphic serialization</a>
|
||||
* is not supported.
|
||||
* It supports {@code application/json} and {@code application/*+json} with
|
||||
* various character sets, {@code UTF-8} being the default.
|
||||
*
|
||||
@@ -120,6 +123,9 @@ public class KotlinSerializationJsonEncoder extends AbstractEncoder<Object> {
|
||||
KSerializer<Object> serializer = serializerCache.get(type);
|
||||
if (serializer == null) {
|
||||
serializer = SerializersKt.serializer(type);
|
||||
if (serializer.getDescriptor().getKind().equals(PolymorphicKind.OPEN.INSTANCE)) {
|
||||
throw new UnsupportedOperationException("Open polymorphic serialization is not supported yet");
|
||||
}
|
||||
serializerCache.put(type, serializer);
|
||||
}
|
||||
return serializer;
|
||||
|
||||
+4
-4
@@ -74,8 +74,7 @@ public class DefaultPartHttpMessageReader extends LoggingCodecSupport implements
|
||||
|
||||
private boolean streaming;
|
||||
|
||||
private Scheduler blockingOperationScheduler = Schedulers.newBoundedElastic(Schedulers.DEFAULT_BOUNDED_ELASTIC_SIZE,
|
||||
Schedulers.DEFAULT_BOUNDED_ELASTIC_QUEUESIZE, IDENTIFIER, 60, true);
|
||||
private Scheduler blockingOperationScheduler = Schedulers.boundedElastic();
|
||||
|
||||
private Mono<Path> fileStorageDirectory = Mono.defer(this::defaultFileStorageDirectory).cache();
|
||||
|
||||
@@ -153,8 +152,9 @@ public class DefaultPartHttpMessageReader extends LoggingCodecSupport implements
|
||||
|
||||
/**
|
||||
* Sets the Reactor {@link Scheduler} to be used for creating files and
|
||||
* directories, and writing to files. By default, a bounded scheduler is
|
||||
* created with default properties.
|
||||
* directories, and writing to files. By default,
|
||||
* {@link Schedulers#boundedElastic()} is used, but this property allows for
|
||||
* changing it to an externally managed scheduler.
|
||||
* <p>Note that this property is ignored when
|
||||
* {@linkplain #setStreaming(boolean) streaming} is enabled, or when
|
||||
* {@link #setMaxInMemorySize(int) maxInMemorySize} is set to -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.
|
||||
@@ -33,6 +33,14 @@ public interface FilePart extends Part {
|
||||
|
||||
/**
|
||||
* Return the original filename in the client's filesystem.
|
||||
* <p><strong>Note:</strong> Please keep in mind this filename is supplied
|
||||
* by the client and should not be used blindly. In addition to not using
|
||||
* the directory portion, the file name could also contain characters such
|
||||
* as ".." and others that can be used maliciously.
|
||||
* @return the original filename, or the empty String if no file has been chosen
|
||||
* in the multipart form, or {@code null} if not defined or not available
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7578#section-4.2">RFC 7578, Section 4.2</a>
|
||||
* @see <a href="https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload">Unrestricted File Upload</a>
|
||||
*/
|
||||
String filename();
|
||||
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@ public class MultipartWriterSupport extends LoggingCodecSupport {
|
||||
Charset charset = getCharset();
|
||||
if (!charset.equals(StandardCharsets.UTF_8) &&
|
||||
!charset.equals(StandardCharsets.US_ASCII) ) {
|
||||
params.put("charset", getCharset().name());
|
||||
params.put("charset", charset.name());
|
||||
}
|
||||
|
||||
mediaType = (mediaType != null ? mediaType : MediaType.MULTIPART_FORM_DATA);
|
||||
|
||||
+124
-40
@@ -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.http.codec.xml.Jaxb2XmlDecoder;
|
||||
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link CodecConfigurer.DefaultCodecs} that serves
|
||||
@@ -147,7 +148,38 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
|
||||
private boolean registerDefaults = true;
|
||||
|
||||
|
||||
// The default reader and writer instances to use
|
||||
|
||||
private final List<HttpMessageReader<?>> typedReaders = new ArrayList<>();
|
||||
|
||||
private final List<HttpMessageReader<?>> objectReaders = new ArrayList<>();
|
||||
|
||||
private final List<HttpMessageWriter<?>> typedWriters = new ArrayList<>();
|
||||
|
||||
private final List<HttpMessageWriter<?>> objectWriters = new ArrayList<>();
|
||||
|
||||
|
||||
BaseDefaultCodecs() {
|
||||
initReaders();
|
||||
initWriters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset and initialize typed readers and object readers.
|
||||
* @since 5.3.3
|
||||
*/
|
||||
protected void initReaders() {
|
||||
initTypedReaders();
|
||||
initObjectReaders();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset and initialize typed writers and object writers.
|
||||
* @since 5.3.3
|
||||
*/
|
||||
protected void initWriters() {
|
||||
initTypedWriters();
|
||||
initObjectWriters();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,61 +199,78 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
|
||||
this.maxInMemorySize = other.maxInMemorySize;
|
||||
this.enableLoggingRequestDetails = other.enableLoggingRequestDetails;
|
||||
this.registerDefaults = other.registerDefaults;
|
||||
this.typedReaders.addAll(other.typedReaders);
|
||||
this.objectReaders.addAll(other.objectReaders);
|
||||
this.typedWriters.addAll(other.typedWriters);
|
||||
this.objectWriters.addAll(other.objectWriters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jackson2JsonDecoder(Decoder<?> decoder) {
|
||||
this.jackson2JsonDecoder = decoder;
|
||||
initObjectReaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jackson2JsonEncoder(Encoder<?> encoder) {
|
||||
this.jackson2JsonEncoder = encoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void protobufDecoder(Decoder<?> decoder) {
|
||||
this.protobufDecoder = decoder;
|
||||
initObjectWriters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jackson2SmileDecoder(Decoder<?> decoder) {
|
||||
this.jackson2SmileDecoder = decoder;
|
||||
initObjectReaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jackson2SmileEncoder(Encoder<?> encoder) {
|
||||
this.jackson2SmileEncoder = encoder;
|
||||
initObjectWriters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void protobufDecoder(Decoder<?> decoder) {
|
||||
this.protobufDecoder = decoder;
|
||||
initTypedReaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void protobufEncoder(Encoder<?> encoder) {
|
||||
this.protobufEncoder = encoder;
|
||||
initTypedWriters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jaxb2Decoder(Decoder<?> decoder) {
|
||||
this.jaxb2Decoder = decoder;
|
||||
initObjectReaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jaxb2Encoder(Encoder<?> encoder) {
|
||||
this.jaxb2Encoder = encoder;
|
||||
initObjectWriters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kotlinSerializationJsonDecoder(Decoder<?> decoder) {
|
||||
this.kotlinSerializationJsonDecoder = decoder;
|
||||
initObjectReaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kotlinSerializationJsonEncoder(Encoder<?> encoder) {
|
||||
this.kotlinSerializationJsonEncoder = encoder;
|
||||
initObjectWriters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maxInMemorySize(int byteCount) {
|
||||
this.maxInMemorySize = byteCount;
|
||||
if (!ObjectUtils.nullSafeEquals(this.maxInMemorySize, byteCount)) {
|
||||
this.maxInMemorySize = byteCount;
|
||||
initReaders();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -232,7 +281,11 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
|
||||
|
||||
@Override
|
||||
public void enableLoggingRequestDetails(boolean enable) {
|
||||
this.enableLoggingRequestDetails = enable;
|
||||
if (!ObjectUtils.nullSafeEquals(this.enableLoggingRequestDetails, enable)) {
|
||||
this.enableLoggingRequestDetails = enable;
|
||||
initReaders();
|
||||
initWriters();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -245,7 +298,11 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
|
||||
* Delegate method used from {@link BaseCodecConfigurer#registerDefaults}.
|
||||
*/
|
||||
void registerDefaults(boolean registerDefaults) {
|
||||
this.registerDefaults = registerDefaults;
|
||||
if (this.registerDefaults != registerDefaults) {
|
||||
this.registerDefaults = registerDefaults;
|
||||
initReaders();
|
||||
initWriters();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -253,28 +310,34 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
|
||||
* Return readers that support specific types.
|
||||
*/
|
||||
final List<HttpMessageReader<?>> getTypedReaders() {
|
||||
return this.typedReaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset and initialize typed readers.
|
||||
* @since 5.3.3
|
||||
*/
|
||||
protected void initTypedReaders() {
|
||||
this.typedReaders.clear();
|
||||
if (!this.registerDefaults) {
|
||||
return Collections.emptyList();
|
||||
return;
|
||||
}
|
||||
List<HttpMessageReader<?>> readers = new ArrayList<>();
|
||||
addCodec(readers, new DecoderHttpMessageReader<>(new ByteArrayDecoder()));
|
||||
addCodec(readers, new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
|
||||
addCodec(readers, new DecoderHttpMessageReader<>(new DataBufferDecoder()));
|
||||
addCodec(this.typedReaders, new DecoderHttpMessageReader<>(new ByteArrayDecoder()));
|
||||
addCodec(this.typedReaders, new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
|
||||
addCodec(this.typedReaders, new DecoderHttpMessageReader<>(new DataBufferDecoder()));
|
||||
if (nettyByteBufPresent) {
|
||||
addCodec(readers, new DecoderHttpMessageReader<>(new NettyByteBufDecoder()));
|
||||
addCodec(this.typedReaders, new DecoderHttpMessageReader<>(new NettyByteBufDecoder()));
|
||||
}
|
||||
addCodec(readers, new ResourceHttpMessageReader(new ResourceDecoder()));
|
||||
addCodec(readers, new DecoderHttpMessageReader<>(StringDecoder.textPlainOnly()));
|
||||
addCodec(this.typedReaders, new ResourceHttpMessageReader(new ResourceDecoder()));
|
||||
addCodec(this.typedReaders, new DecoderHttpMessageReader<>(StringDecoder.textPlainOnly()));
|
||||
if (protobufPresent) {
|
||||
addCodec(readers, new DecoderHttpMessageReader<>(this.protobufDecoder != null ?
|
||||
addCodec(this.typedReaders, new DecoderHttpMessageReader<>(this.protobufDecoder != null ?
|
||||
(ProtobufDecoder) this.protobufDecoder : new ProtobufDecoder()));
|
||||
}
|
||||
addCodec(readers, new FormHttpMessageReader());
|
||||
addCodec(this.typedReaders, new FormHttpMessageReader());
|
||||
|
||||
// client vs server..
|
||||
extendTypedReaders(readers);
|
||||
|
||||
return readers;
|
||||
extendTypedReaders(this.typedReaders);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -293,7 +356,6 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
private void initCodec(@Nullable Object codec) {
|
||||
|
||||
if (codec instanceof DecoderHttpMessageReader) {
|
||||
codec = ((DecoderHttpMessageReader) codec).getDecoder();
|
||||
}
|
||||
@@ -386,29 +448,35 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
|
||||
* Return Object readers (JSON, XML, SSE).
|
||||
*/
|
||||
final List<HttpMessageReader<?>> getObjectReaders() {
|
||||
return this.objectReaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset and initialize object readers.
|
||||
* @since 5.3.3
|
||||
*/
|
||||
protected void initObjectReaders() {
|
||||
this.objectReaders.clear();
|
||||
if (!this.registerDefaults) {
|
||||
return Collections.emptyList();
|
||||
return;
|
||||
}
|
||||
List<HttpMessageReader<?>> readers = new ArrayList<>();
|
||||
if (kotlinSerializationJsonPresent) {
|
||||
addCodec(readers, new DecoderHttpMessageReader<>(getKotlinSerializationJsonDecoder()));
|
||||
addCodec(this.objectReaders, new DecoderHttpMessageReader<>(getKotlinSerializationJsonDecoder()));
|
||||
}
|
||||
if (jackson2Present) {
|
||||
addCodec(readers, new DecoderHttpMessageReader<>(getJackson2JsonDecoder()));
|
||||
addCodec(this.objectReaders, new DecoderHttpMessageReader<>(getJackson2JsonDecoder()));
|
||||
}
|
||||
if (jackson2SmilePresent) {
|
||||
addCodec(readers, new DecoderHttpMessageReader<>(this.jackson2SmileDecoder != null ?
|
||||
addCodec(this.objectReaders, new DecoderHttpMessageReader<>(this.jackson2SmileDecoder != null ?
|
||||
(Jackson2SmileDecoder) this.jackson2SmileDecoder : new Jackson2SmileDecoder()));
|
||||
}
|
||||
if (jaxb2Present && !shouldIgnoreXml) {
|
||||
addCodec(readers, new DecoderHttpMessageReader<>(this.jaxb2Decoder != null ?
|
||||
addCodec(this.objectReaders, new DecoderHttpMessageReader<>(this.jaxb2Decoder != null ?
|
||||
(Jaxb2XmlDecoder) this.jaxb2Decoder : new Jaxb2XmlDecoder()));
|
||||
}
|
||||
|
||||
// client vs server..
|
||||
extendObjectReaders(readers);
|
||||
|
||||
return readers;
|
||||
extendObjectReaders(this.objectReaders);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -434,12 +502,20 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes" })
|
||||
final List<HttpMessageWriter<?>> getTypedWriters() {
|
||||
return this.typedWriters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset and initialize typed writers.
|
||||
* @since 5.3.3
|
||||
*/
|
||||
protected void initTypedWriters() {
|
||||
this.typedWriters.clear();
|
||||
if (!this.registerDefaults) {
|
||||
return Collections.emptyList();
|
||||
return;
|
||||
}
|
||||
List<HttpMessageWriter<?>> writers = getBaseTypedWriters();
|
||||
extendTypedWriters(writers);
|
||||
return writers;
|
||||
this.typedWriters.addAll(getBaseTypedWriters());
|
||||
extendTypedWriters(this.typedWriters);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -476,12 +552,20 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
|
||||
* Return Object writers (JSON, XML, SSE).
|
||||
*/
|
||||
final List<HttpMessageWriter<?>> getObjectWriters() {
|
||||
return this.objectWriters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset and initialize object writers.
|
||||
* @since 5.3.3
|
||||
*/
|
||||
protected void initObjectWriters() {
|
||||
this.objectWriters.clear();
|
||||
if (!this.registerDefaults) {
|
||||
return Collections.emptyList();
|
||||
return;
|
||||
}
|
||||
List<HttpMessageWriter<?>> writers = getBaseObjectWriters();
|
||||
extendObjectWriters(writers);
|
||||
return writers;
|
||||
this.objectWriters.addAll(getBaseObjectWriters());
|
||||
extendObjectWriters(this.objectWriters);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+6
-11
@@ -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.
|
||||
@@ -68,6 +68,7 @@ class ClientDefaultCodecsImpl extends BaseDefaultCodecs implements ClientCodecCo
|
||||
*/
|
||||
void setPartWritersSupplier(Supplier<List<HttpMessageWriter<?>>> supplier) {
|
||||
this.partWritersSupplier = supplier;
|
||||
initTypedWriters();
|
||||
}
|
||||
|
||||
|
||||
@@ -82,15 +83,7 @@ class ClientDefaultCodecsImpl extends BaseDefaultCodecs implements ClientCodecCo
|
||||
@Override
|
||||
public void serverSentEventDecoder(Decoder<?> decoder) {
|
||||
this.sseDecoder = decoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientDefaultCodecsImpl clone() {
|
||||
ClientDefaultCodecsImpl codecs = new ClientDefaultCodecsImpl();
|
||||
codecs.multipartCodecs = this.multipartCodecs;
|
||||
codecs.sseDecoder = this.sseDecoder;
|
||||
codecs.partWritersSupplier = this.partWritersSupplier;
|
||||
return codecs;
|
||||
initObjectReaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -125,7 +118,7 @@ class ClientDefaultCodecsImpl extends BaseDefaultCodecs implements ClientCodecCo
|
||||
/**
|
||||
* Default implementation of {@link ClientCodecConfigurer.MultipartCodecs}.
|
||||
*/
|
||||
private static class DefaultMultipartCodecs implements ClientCodecConfigurer.MultipartCodecs {
|
||||
private class DefaultMultipartCodecs implements ClientCodecConfigurer.MultipartCodecs {
|
||||
|
||||
private final List<HttpMessageWriter<?>> writers = new ArrayList<>();
|
||||
|
||||
@@ -141,12 +134,14 @@ class ClientDefaultCodecsImpl extends BaseDefaultCodecs implements ClientCodecCo
|
||||
@Override
|
||||
public ClientCodecConfigurer.MultipartCodecs encoder(Encoder<?> encoder) {
|
||||
writer(new EncoderHttpMessageWriter<>(encoder));
|
||||
initTypedWriters();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientCodecConfigurer.MultipartCodecs writer(HttpMessageWriter<?> writer) {
|
||||
this.writers.add(writer);
|
||||
initTypedWriters();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
+3
-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.
|
||||
@@ -55,11 +55,13 @@ class ServerDefaultCodecsImpl extends BaseDefaultCodecs implements ServerCodecCo
|
||||
@Override
|
||||
public void multipartReader(HttpMessageReader<?> reader) {
|
||||
this.multipartReader = reader;
|
||||
initTypedReaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverSentEventEncoder(Encoder<?> encoder) {
|
||||
this.sseEncoder = encoder;
|
||||
initObjectWriters();
|
||||
}
|
||||
|
||||
|
||||
|
||||
+4
-1
@@ -475,7 +475,10 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
|
||||
|
||||
byte[] boundary = generateMultipartBoundary();
|
||||
if (!isFilenameCharsetSet()) {
|
||||
parameters.put("charset", this.charset.name());
|
||||
if (!this.charset.equals(StandardCharsets.UTF_8) &&
|
||||
!this.charset.equals(StandardCharsets.US_ASCII)) {
|
||||
parameters.put("charset", this.charset.name());
|
||||
}
|
||||
}
|
||||
parameters.put("boundary", new String(boundary, StandardCharsets.US_ASCII));
|
||||
|
||||
|
||||
+8
-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.
|
||||
@@ -25,6 +25,7 @@ import java.util.Map;
|
||||
import kotlinx.serialization.KSerializer;
|
||||
import kotlinx.serialization.SerializationException;
|
||||
import kotlinx.serialization.SerializersKt;
|
||||
import kotlinx.serialization.descriptors.PolymorphicKind;
|
||||
import kotlinx.serialization.json.Json;
|
||||
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
@@ -43,7 +44,9 @@ import org.springframework.util.StreamUtils;
|
||||
* that can read and write JSON using
|
||||
* <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>.
|
||||
*
|
||||
* <p>This converter can be used to bind {@code @Serializable} Kotlin classes.
|
||||
* <p>This converter can be used to bind {@code @Serializable} Kotlin classes,
|
||||
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphic serialization</a>
|
||||
* is not supported.
|
||||
* It supports {@code application/json} and {@code application/*+json} with
|
||||
* various character sets, {@code UTF-8} being the default.
|
||||
*
|
||||
@@ -182,6 +185,9 @@ public class KotlinSerializationJsonHttpMessageConverter extends AbstractGeneric
|
||||
KSerializer<Object> serializer = serializerCache.get(type);
|
||||
if (serializer == null) {
|
||||
serializer = SerializersKt.serializer(type);
|
||||
if (serializer.getDescriptor().getKind().equals(PolymorphicKind.OPEN.INSTANCE)) {
|
||||
throw new UnsupportedOperationException("Open polymorphic serialization is not supported yet");
|
||||
}
|
||||
serializerCache.put(type, serializer);
|
||||
}
|
||||
return serializer;
|
||||
|
||||
+4
@@ -125,6 +125,10 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
|
||||
this.headers.getContentType().getCharset() != null) {
|
||||
this.servletResponse.setCharacterEncoding(this.headers.getContentType().getCharset().name());
|
||||
}
|
||||
long contentLength = getHeaders().getContentLength();
|
||||
if (contentLength != -1) {
|
||||
this.servletResponse.setContentLengthLong(contentLength);
|
||||
}
|
||||
this.headersWritten = true;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -18,6 +18,7 @@ package org.springframework.http.server.reactive;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -213,7 +214,7 @@ public abstract class AbstractServerHttpResponse implements ServerHttpResponse {
|
||||
return ((Mono<? extends DataBuffer>) body)
|
||||
.flatMap(buffer -> {
|
||||
touchDataBuffer(buffer);
|
||||
AtomicReference<Boolean> subscribed = new AtomicReference<>(false);
|
||||
AtomicBoolean subscribed = new AtomicBoolean();
|
||||
return doCommit(
|
||||
() -> {
|
||||
try {
|
||||
|
||||
+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.
|
||||
@@ -236,7 +236,7 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T getNativeRequest() {
|
||||
return (T) this.originalRequest;
|
||||
return ServerHttpRequestDecorator.getNativeRequest(this.originalRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+50
-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.
|
||||
@@ -154,7 +154,7 @@ class JettyHeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return this.headers.getFieldNamesCollection();
|
||||
return new HeaderNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -227,4 +227,52 @@ class JettyHeadersAdapter implements MultiValueMap<String, String> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class HeaderNames extends AbstractSet<String> {
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return new HeaderNamesIterator(headers.getFieldNamesCollection().iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return headers.getFieldNamesCollection().size();
|
||||
}
|
||||
}
|
||||
|
||||
private final class HeaderNamesIterator implements Iterator<String> {
|
||||
|
||||
private final Iterator<String> iterator;
|
||||
|
||||
@Nullable
|
||||
private String currentName;
|
||||
|
||||
private HeaderNamesIterator(Iterator<String> iterator) {
|
||||
this.iterator = iterator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
this.currentName = this.iterator.next();
|
||||
return this.currentName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (this.currentName == null) {
|
||||
throw new IllegalStateException("No current Header in iterator");
|
||||
}
|
||||
if (!headers.containsKey(this.currentName)) {
|
||||
throw new IllegalStateException("Header not present: " + this.currentName);
|
||||
}
|
||||
headers.remove(this.currentName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+55
-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.
|
||||
@@ -56,7 +56,9 @@ class NettyHeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
@Override
|
||||
public void add(String key, @Nullable String value) {
|
||||
this.headers.add(key, value);
|
||||
if (value != null) {
|
||||
this.headers.add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -71,7 +73,9 @@ class NettyHeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
@Override
|
||||
public void set(String key, @Nullable String value) {
|
||||
this.headers.set(key, value);
|
||||
if (value != null) {
|
||||
this.headers.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -153,7 +157,7 @@ class NettyHeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return this.headers.names();
|
||||
return new HeaderNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -226,4 +230,51 @@ class NettyHeadersAdapter implements MultiValueMap<String, String> {
|
||||
}
|
||||
}
|
||||
|
||||
private class HeaderNames extends AbstractSet<String> {
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return new HeaderNamesIterator(headers.names().iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return headers.names().size();
|
||||
}
|
||||
}
|
||||
|
||||
private final class HeaderNamesIterator implements Iterator<String> {
|
||||
|
||||
private final Iterator<String> iterator;
|
||||
|
||||
@Nullable
|
||||
private String currentName;
|
||||
|
||||
private HeaderNamesIterator(Iterator<String> iterator) {
|
||||
this.iterator = iterator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
this.currentName = this.iterator.next();
|
||||
return this.currentName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (this.currentName == null) {
|
||||
throw new IllegalStateException("No current Header in iterator");
|
||||
}
|
||||
if (!headers.contains(this.currentName)) {
|
||||
throw new IllegalStateException("Header not present: " + this.currentName);
|
||||
}
|
||||
headers.remove(this.currentName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+23
-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.
|
||||
@@ -120,6 +120,28 @@ public class ServerHttpRequestDecorator implements ServerHttpRequest {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the native request of the underlying server API, if possible,
|
||||
* also unwrapping {@link ServerHttpRequestDecorator} if necessary.
|
||||
* @param request the request to check
|
||||
* @param <T> the expected native request type
|
||||
* @throws IllegalArgumentException if the native request can't be obtained
|
||||
* @since 5.3.3
|
||||
*/
|
||||
public static <T> T getNativeRequest(ServerHttpRequest request) {
|
||||
if (request instanceof AbstractServerHttpRequest) {
|
||||
return ((AbstractServerHttpRequest) request).getNativeRequest();
|
||||
}
|
||||
else if (request instanceof ServerHttpRequestDecorator) {
|
||||
return getNativeRequest(((ServerHttpRequestDecorator) request).getDelegate());
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException(
|
||||
"Can't find native request in " + request.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + " [delegate=" + getDelegate() + "]";
|
||||
|
||||
+23
-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.
|
||||
@@ -110,6 +110,28 @@ public class ServerHttpResponseDecorator implements ServerHttpResponse {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the native response of the underlying server API, if possible,
|
||||
* also unwrapping {@link ServerHttpResponseDecorator} if necessary.
|
||||
* @param response the response to check
|
||||
* @param <T> the expected native response type
|
||||
* @throws IllegalArgumentException if the native response can't be obtained
|
||||
* @since 5.3.3
|
||||
*/
|
||||
public static <T> T getNativeResponse(ServerHttpResponse response) {
|
||||
if (response instanceof AbstractServerHttpResponse) {
|
||||
return ((AbstractServerHttpResponse) response).getNativeResponse();
|
||||
}
|
||||
else if (response instanceof ServerHttpResponseDecorator) {
|
||||
return getNativeResponse(((ServerHttpResponseDecorator) response).getDelegate());
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException(
|
||||
"Can't find native response in " + response.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + " [delegate=" + getDelegate() + "]";
|
||||
|
||||
+57
-8
@@ -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.
|
||||
@@ -20,7 +20,6 @@ import java.util.AbstractSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -167,12 +166,7 @@ class TomcatHeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
Set<String> result = new HashSet<>(8);
|
||||
Enumeration<String> names = this.headers.names();
|
||||
while (names.hasMoreElements()) {
|
||||
result.add(names.nextElement());
|
||||
}
|
||||
return result;
|
||||
return new HeaderNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -247,4 +241,59 @@ class TomcatHeadersAdapter implements MultiValueMap<String, String> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class HeaderNames extends AbstractSet<String> {
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return new HeaderNamesIterator(headers.names());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
Enumeration<String> names = headers.names();
|
||||
int size = 0;
|
||||
while (names.hasMoreElements()) {
|
||||
names.nextElement();
|
||||
size++;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
private final class HeaderNamesIterator implements Iterator<String> {
|
||||
|
||||
private final Enumeration<String> enumeration;
|
||||
|
||||
@Nullable
|
||||
private String currentName;
|
||||
|
||||
private HeaderNamesIterator(Enumeration<String> enumeration) {
|
||||
this.enumeration = enumeration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.enumeration.hasMoreElements();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
this.currentName = this.enumeration.nextElement();
|
||||
return this.currentName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (this.currentName == null) {
|
||||
throw new IllegalStateException("No current Header in iterator");
|
||||
}
|
||||
int index = headers.findHeader(this.currentName, 0);
|
||||
if (index == -1) {
|
||||
throw new IllegalStateException("Header not present: " + this.currentName);
|
||||
}
|
||||
headers.removeHeader(index);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+50
-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.
|
||||
@@ -149,9 +149,7 @@ class UndertowHeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
return this.headers.getHeaderNames().stream()
|
||||
.map(HttpString::toString)
|
||||
.collect(Collectors.toSet());
|
||||
return new HeaderNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -225,4 +223,52 @@ class UndertowHeadersAdapter implements MultiValueMap<String, String> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class HeaderNames extends AbstractSet<String> {
|
||||
|
||||
@Override
|
||||
public Iterator<String> iterator() {
|
||||
return new HeaderNamesIterator(headers.getHeaderNames().iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return headers.getHeaderNames().size();
|
||||
}
|
||||
}
|
||||
|
||||
private final class HeaderNamesIterator implements Iterator<String> {
|
||||
|
||||
private final Iterator<HttpString> iterator;
|
||||
|
||||
@Nullable
|
||||
private String currentName;
|
||||
|
||||
private HeaderNamesIterator(Iterator<HttpString> iterator) {
|
||||
this.iterator = iterator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
this.currentName = this.iterator.next().toString();
|
||||
return this.currentName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (this.currentName == null) {
|
||||
throw new IllegalStateException("No current Header in iterator");
|
||||
}
|
||||
if (!headers.contains(this.currentName)) {
|
||||
throw new IllegalStateException("Header not present: " + this.currentName);
|
||||
}
|
||||
headers.remove(this.currentName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-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.
|
||||
@@ -33,6 +33,9 @@ import java.lang.annotation.Target;
|
||||
* <li>An exception argument: declared as a general Exception or as a more
|
||||
* specific exception. This also serves as a mapping hint if the annotation
|
||||
* itself does not narrow the exception types through its {@link #value()}.
|
||||
* You may refer to a top-level exception being propagated or to a nested
|
||||
* cause within a wrapper exception. As of 5.3, any cause level is being
|
||||
* exposed, whereas previously only an immediate cause was considered.
|
||||
* <li>Request and/or response objects (typically from the Servlet API).
|
||||
* You may choose any specific request/response type, e.g.
|
||||
* {@link javax.servlet.ServletRequest} / {@link javax.servlet.http.HttpServletRequest}.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -145,7 +145,7 @@ public abstract class OncePerRequestFilter extends GenericFilterBean {
|
||||
* @see WebAsyncManager#hasConcurrentResult()
|
||||
*/
|
||||
protected boolean isAsyncDispatch(HttpServletRequest request) {
|
||||
return WebAsyncUtils.getAsyncManager(request).hasConcurrentResult();
|
||||
return request.getDispatcherType().equals(DispatcherType.ASYNC);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+4
-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.
|
||||
@@ -24,6 +24,7 @@ import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.http.server.RequestPath;
|
||||
import org.springframework.web.util.ServletRequestPathUtils;
|
||||
|
||||
/**
|
||||
@@ -48,12 +49,13 @@ public class ServletRequestPathFilter implements Filter {
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
|
||||
RequestPath previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
|
||||
ServletRequestPathUtils.parseAndCache((HttpServletRequest) request);
|
||||
try {
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
finally {
|
||||
ServletRequestPathUtils.clearParsedRequestPath(request);
|
||||
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user