mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
84 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3540029931 | |||
| eafe3afe11 | |||
| 26e0343c16 | |||
| 4d5e7207f2 | |||
| 430fc25aca | |||
| 0882ca57d4 | |||
| 94bbf85c0e | |||
| 2c2ef12f68 | |||
| 120d512ff6 | |||
| 3ddf183922 | |||
| 4c69bfd32f | |||
| 41d71e9a7f | |||
| 36682b7ad2 | |||
| 30601a5014 | |||
| f2371f5e7d | |||
| f0da099b12 | |||
| d00fd4c502 | |||
| 44a6d13cc0 | |||
| 284657355a | |||
| 6a81ed3a50 | |||
| 0a053cfccb | |||
| 22bb76d326 | |||
| 4dc45d551c | |||
| e5d05ddfc3 | |||
| 44a5f8ec06 | |||
| 0a8bda40f4 | |||
| 8a879c6fed | |||
| 28d11aaf64 | |||
| 02941127e1 | |||
| b1b24458c9 | |||
| 854b625be2 | |||
| a2b7a907ec | |||
| 4f0a8911ca | |||
| 40fef7b232 | |||
| 423134f64c | |||
| 574c10d219 | |||
| 2b4b947050 | |||
| 4993b1090a | |||
| 3adabf391f | |||
| 0026338c00 | |||
| cdea667e58 | |||
| 66b1c0b4b0 | |||
| 6e42d36614 | |||
| 7f2c93fa1f | |||
| 4c351e811a | |||
| 5b67dea506 | |||
| b9fe095f60 | |||
| 6b17014b5a | |||
| 878246b09b | |||
| 9067ccab2d | |||
| 4e00aece7a | |||
| c9841f37b6 | |||
| 92d513e9fb | |||
| 782ee34cb7 | |||
| 80c10fad92 | |||
| 498a0d286f | |||
| e9d45d1b49 | |||
| 0851b0f72d | |||
| 91d991e86c | |||
| 960f6fb936 | |||
| 4d3d34528e | |||
| 78ba946266 | |||
| 42e7318cbb | |||
| 000383fbff | |||
| 37cbdc2cf4 | |||
| 6d95e7f9d9 | |||
| 45a7917a36 | |||
| 25b95b8b98 | |||
| 6bd7a7321e | |||
| 11aaba9877 | |||
| 07fd7606e7 | |||
| 40d2466334 | |||
| 3d6d853bbc | |||
| 90ea39cc6b | |||
| 21c3d4f4a9 | |||
| de53d77344 | |||
| e4e90bbec0 | |||
| d0828be0cd | |||
| 4a4b332709 | |||
| 33f1c9b614 | |||
| 53ac812fb0 | |||
| 60c89dd2df | |||
| 4bd2531774 | |||
| 4fd5630700 |
@@ -28,6 +28,7 @@ buildSrc/build
|
||||
/integration-tests/build
|
||||
/src/asciidoc/build
|
||||
target/
|
||||
/target/
|
||||
|
||||
# Eclipse artifacts, including WTP generated manifests
|
||||
.classpath
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=8.0.352-librca
|
||||
java=8.0.362-librca
|
||||
|
||||
+15
-15
@@ -28,18 +28,18 @@ configure(allprojects) { project ->
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.7"
|
||||
mavenBom "io.netty:netty-bom:4.1.86.Final"
|
||||
mavenBom "io.projectreactor:reactor-bom:2020.0.27"
|
||||
mavenBom "io.netty:netty-bom:4.1.90.Final"
|
||||
mavenBom "io.projectreactor:reactor-bom:2020.0.30"
|
||||
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR13"
|
||||
mavenBom "io.rsocket:rsocket-bom:1.1.3"
|
||||
mavenBom "org.eclipse.jetty:jetty-bom:9.4.50.v20221201"
|
||||
mavenBom "org.eclipse.jetty:jetty-bom:9.4.51.v20230217"
|
||||
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.5.32"
|
||||
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.2"
|
||||
mavenBom "org.jetbrains.kotlinx:kotlinx-serialization-bom:1.2.2"
|
||||
mavenBom "org.junit:junit-bom:5.8.2"
|
||||
}
|
||||
dependencies {
|
||||
dependencySet(group: 'org.apache.logging.log4j', version: '2.19.0') {
|
||||
dependencySet(group: 'org.apache.logging.log4j', version: '2.20.0') {
|
||||
entry 'log4j-api'
|
||||
entry 'log4j-core'
|
||||
entry 'log4j-jul'
|
||||
@@ -92,7 +92,7 @@ configure(allprojects) { project ->
|
||||
entry 'jibx-run'
|
||||
}
|
||||
dependency "org.ogce:xpp3:1.1.6"
|
||||
dependency "org.yaml:snakeyaml:1.30"
|
||||
dependency "org.yaml:snakeyaml:1.33"
|
||||
|
||||
dependency "com.h2database:h2:2.1.214"
|
||||
dependency "com.github.ben-manes.caffeine:caffeine:2.9.3"
|
||||
@@ -128,18 +128,18 @@ configure(allprojects) { project ->
|
||||
dependency "org.webjars:webjars-locator-core:0.48"
|
||||
dependency "org.webjars:underscorejs:1.8.3"
|
||||
|
||||
dependencySet(group: 'org.apache.tomcat', version: '9.0.70') {
|
||||
dependencySet(group: 'org.apache.tomcat', version: '9.0.73') {
|
||||
entry 'tomcat-util'
|
||||
entry('tomcat-websocket') {
|
||||
exclude group: "org.apache.tomcat", name: "tomcat-servlet-api"
|
||||
exclude group: "org.apache.tomcat", name: "tomcat-websocket-api"
|
||||
}
|
||||
}
|
||||
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.70') {
|
||||
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.73') {
|
||||
entry 'tomcat-embed-core'
|
||||
entry 'tomcat-embed-websocket'
|
||||
}
|
||||
dependencySet(group: 'io.undertow', version: '2.2.22.Final') {
|
||||
dependencySet(group: 'io.undertow', version: '2.2.23.Final') {
|
||||
entry 'undertow-core'
|
||||
entry('undertow-servlet') {
|
||||
exclude group: "org.jboss.spec.javax.servlet", name: "jboss-servlet-api_4.0_spec"
|
||||
@@ -191,7 +191,7 @@ configure(allprojects) { project ->
|
||||
dependency "org.junit.support:testng-engine:1.0.4"
|
||||
dependency "org.hamcrest:hamcrest:2.2"
|
||||
dependency "org.awaitility:awaitility:3.1.6"
|
||||
dependency "org.assertj:assertj-core:3.24.1"
|
||||
dependency "org.assertj:assertj-core:3.24.2"
|
||||
dependencySet(group: 'org.xmlunit', version: '2.9.0') {
|
||||
entry 'xmlunit-assertj'
|
||||
entry('xmlunit-matchers') {
|
||||
@@ -206,10 +206,10 @@ configure(allprojects) { project ->
|
||||
}
|
||||
dependency "io.mockk:mockk:1.12.1"
|
||||
|
||||
dependency("net.sourceforge.htmlunit:htmlunit:2.69.0") {
|
||||
dependency("net.sourceforge.htmlunit:htmlunit:2.70.0") {
|
||||
exclude group: "commons-logging", name: "commons-logging"
|
||||
}
|
||||
dependency("org.seleniumhq.selenium:htmlunit-driver:2.67.0") {
|
||||
dependency("org.seleniumhq.selenium:htmlunit-driver:2.70.0") {
|
||||
exclude group: "commons-logging", name: "commons-logging"
|
||||
}
|
||||
dependency("org.seleniumhq.selenium:selenium-java:3.141.59") {
|
||||
@@ -340,7 +340,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "10.6.0"
|
||||
toolVersion = "10.9.1"
|
||||
configDirectory.set(rootProject.file("src/checkstyle"))
|
||||
}
|
||||
|
||||
@@ -377,9 +377,9 @@ configure([rootProject] + javaProjects) { project ->
|
||||
"https://www.eclipse.org/aspectj/doc/released/aspectj5rt-api/",
|
||||
"https://www.ehcache.org/apidocs/2.10.4/",
|
||||
"https://www.quartz-scheduler.org/api/2.3.0/",
|
||||
"https://fasterxml.github.io/jackson-core/javadoc/2.10/",
|
||||
"https://fasterxml.github.io/jackson-databind/javadoc/2.10/",
|
||||
"https://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.10/",
|
||||
"https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-core/2.12.7/",
|
||||
"https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/2.12.7/",
|
||||
"https://www.javadoc.io/doc/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.12.7/",
|
||||
"https://hc.apache.org/httpcomponents-client-5.1.x/current/httpclient5/apidocs/",
|
||||
"https://projectreactor.io/docs/test/release/api/",
|
||||
"https://junit.org/junit4/javadoc/4.13.2/",
|
||||
|
||||
@@ -8,7 +8,4 @@ milestone: "5.3.x"
|
||||
build-name: "spring-framework"
|
||||
pipeline-name: "spring-framework"
|
||||
concourse-url: "https://ci.spring.io"
|
||||
registry-mirror-host: docker.repo.spring.io
|
||||
registry-mirror-username: ((artifactory-username))
|
||||
registry-mirror-password: ((artifactory-password))
|
||||
task-timeout: 1h00m
|
||||
|
||||
+11
-9
@@ -23,11 +23,6 @@ anchors:
|
||||
docker-resource-source: &docker-resource-source
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
tag: ((milestone))
|
||||
registry-mirror-vars: ®istry-mirror-vars
|
||||
registry-mirror-host: ((registry-mirror-host))
|
||||
registry-mirror-username: ((registry-mirror-username))
|
||||
registry-mirror-password: ((registry-mirror-password))
|
||||
slack-fail-params: &slack-fail-params
|
||||
text: >
|
||||
:concourse-failed: <https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}|${BUILD_PIPELINE_NAME} ${BUILD_JOB_NAME} failed!>
|
||||
@@ -48,31 +43,37 @@ resource_types:
|
||||
- name: registry-image
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: concourse/registry-image-resource
|
||||
tag: 1.5.0
|
||||
tag: 1.7.1
|
||||
- name: artifactory-resource
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: springio/artifactory-resource
|
||||
tag: 0.0.17
|
||||
tag: 0.0.18
|
||||
- name: github-release
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: concourse/github-release-resource
|
||||
tag: 1.5.5
|
||||
tag: 1.8.0
|
||||
- name: github-status-resource
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: dpb587/github-status-resource
|
||||
tag: master
|
||||
- name: pull-request
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: teliaoss/github-pr-resource
|
||||
tag: v0.23.0
|
||||
- name: slack-notification
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: cfcommunity/slack-notification-resource
|
||||
tag: latest
|
||||
resources:
|
||||
@@ -101,6 +102,7 @@ resources:
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: ((docker-hub-organization))/spring-framework-ci
|
||||
tag: ((milestone))
|
||||
- name: artifactory-repo
|
||||
type: artifactory-resource
|
||||
icon: package-variant
|
||||
@@ -168,7 +170,7 @@ jobs:
|
||||
image: ci-image
|
||||
vars:
|
||||
ci-image-name: ci-image
|
||||
<<: *registry-mirror-vars
|
||||
<<: *docker-resource-source
|
||||
- put: ci-image
|
||||
params:
|
||||
image: ci-image/image.tar
|
||||
|
||||
@@ -4,11 +4,9 @@ image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: concourse/oci-build-task
|
||||
tag: 0.9.1
|
||||
registry_mirror:
|
||||
host: ((registry-mirror-host))
|
||||
username: ((registry-mirror-username))
|
||||
password: ((registry-mirror-password))
|
||||
tag: 0.10.0
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
- name: ci-images-git-repo
|
||||
outputs:
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=5.3.25-SNAPSHOT
|
||||
version=5.3.26
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@ pluginManagement {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "com.gradle.enterprise" version "3.11.1"
|
||||
id "io.spring.ge.conventions" version "0.0.11"
|
||||
id "com.gradle.enterprise" version "3.12.1"
|
||||
id "io.spring.ge.conventions" version "0.0.13"
|
||||
}
|
||||
|
||||
include "spring-aop"
|
||||
|
||||
+3
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,10 +23,6 @@ import org.springframework.lang.Nullable;
|
||||
* Subinterface of {@link org.springframework.aop.aspectj.AspectInstanceFactory}
|
||||
* that returns {@link AspectMetadata} associated with AspectJ-annotated classes.
|
||||
*
|
||||
* <p>Ideally, AspectInstanceFactory would include this method itself, but because
|
||||
* AspectMetadata uses Java-5-only {@link org.aspectj.lang.reflect.AjType},
|
||||
* we need to split out this subinterface.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @since 2.0
|
||||
* @see AspectMetadata
|
||||
@@ -35,13 +31,13 @@ import org.springframework.lang.Nullable;
|
||||
public interface MetadataAwareAspectInstanceFactory extends AspectInstanceFactory {
|
||||
|
||||
/**
|
||||
* Return the AspectJ AspectMetadata for this factory's aspect.
|
||||
* Get the AspectJ AspectMetadata for this factory's aspect.
|
||||
* @return the aspect metadata
|
||||
*/
|
||||
AspectMetadata getAspectMetadata();
|
||||
|
||||
/**
|
||||
* Return the best possible creation mutex for this factory.
|
||||
* Get the best possible creation mutex for this factory.
|
||||
* @return the mutex object (may be {@code null} for no mutex to use)
|
||||
* @since 4.3
|
||||
*/
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Various {@link org.springframework.aop.framework.autoproxy.TargetSourceCreator}
|
||||
* implementations for use with Spring's AOP auto-proxying support.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.aop.framework.autoproxy.target;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -247,7 +247,8 @@ public abstract class BeanUtils {
|
||||
// A single public constructor
|
||||
return (Constructor<T>) ctors[0];
|
||||
}
|
||||
else if (ctors.length == 0){
|
||||
else if (ctors.length == 0) {
|
||||
// No public constructors -> check non-public
|
||||
ctors = clazz.getDeclaredConstructors();
|
||||
if (ctors.length == 1) {
|
||||
// A single non-public constructor, e.g. from a non-public record type
|
||||
|
||||
+5
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,6 +25,7 @@ import java.util.Set;
|
||||
|
||||
import org.springframework.beans.SimpleTypeConverter;
|
||||
import org.springframework.beans.TypeConverter;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
@@ -240,10 +241,11 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
|
||||
}
|
||||
}
|
||||
if (targetAnnotation == null) {
|
||||
BeanFactory beanFactory = getBeanFactory();
|
||||
// Look for matching annotation on the target class
|
||||
if (getBeanFactory() != null) {
|
||||
if (beanFactory != null) {
|
||||
try {
|
||||
Class<?> beanType = getBeanFactory().getType(bdHolder.getBeanName());
|
||||
Class<?> beanType = beanFactory.getType(bdHolder.getBeanName());
|
||||
if (beanType != null) {
|
||||
targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(beanType), type);
|
||||
}
|
||||
|
||||
+4
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -184,8 +184,9 @@ public abstract class YamlProcessor {
|
||||
protected Yaml createYaml() {
|
||||
LoaderOptions loaderOptions = new LoaderOptions();
|
||||
loaderOptions.setAllowDuplicateKeys(false);
|
||||
return new Yaml(new FilteringConstructor(loaderOptions), new Representer(),
|
||||
new DumperOptions(), loaderOptions);
|
||||
DumperOptions dumperOptions = new DumperOptions();
|
||||
return new Yaml(new FilteringConstructor(loaderOptions), new Representer(dumperOptions),
|
||||
dumperOptions, loaderOptions);
|
||||
}
|
||||
|
||||
private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
|
||||
|
||||
+33
-22
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -214,12 +214,15 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'";
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.warn(msg, ex);
|
||||
}
|
||||
else {
|
||||
logger.warn(msg + ": " + ex);
|
||||
if (logger.isWarnEnabled()) {
|
||||
String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'";
|
||||
if (logger.isDebugEnabled()) {
|
||||
// Log at warn level like below but add the exception stacktrace only with debug level
|
||||
logger.warn(msg, ex);
|
||||
}
|
||||
else {
|
||||
logger.warn(msg + ": " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,12 +243,15 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
String msg = "Invocation of close method failed on bean with name '" + this.beanName + "'";
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.warn(msg, ex);
|
||||
}
|
||||
else {
|
||||
logger.warn(msg + ": " + ex);
|
||||
if (logger.isWarnEnabled()) {
|
||||
String msg = "Invocation of close method failed on bean with name '" + this.beanName + "'";
|
||||
if (logger.isDebugEnabled()) {
|
||||
// Log at warn level like below but add the exception stacktrace only with debug level
|
||||
logger.warn(msg, ex);
|
||||
}
|
||||
else {
|
||||
logger.warn(msg + ": " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -320,18 +326,23 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
|
||||
}
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
String msg = "Custom destroy method '" + this.destroyMethodName + "' on bean with name '" +
|
||||
this.beanName + "' threw an exception";
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.warn(msg, ex.getTargetException());
|
||||
}
|
||||
else {
|
||||
logger.warn(msg + ": " + ex.getTargetException());
|
||||
if (logger.isWarnEnabled()) {
|
||||
String msg = "Custom destroy method '" + this.destroyMethodName + "' on bean with name '" +
|
||||
this.beanName + "' threw an exception";
|
||||
if (logger.isDebugEnabled()) {
|
||||
// Log at warn level like below but add the exception stacktrace only with debug level
|
||||
logger.warn(msg, ex.getTargetException());
|
||||
}
|
||||
else {
|
||||
logger.warn(msg + ": " + ex.getTargetException());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
logger.warn("Failed to invoke custom destroy method '" + this.destroyMethodName +
|
||||
"' on bean with name '" + this.beanName + "'", ex);
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("Failed to invoke custom destroy method '" + this.destroyMethodName +
|
||||
"' on bean with name '" + this.beanName + "'", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -547,7 +547,8 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
|
||||
* @see DefaultNamespaceHandlerResolver#DefaultNamespaceHandlerResolver(ClassLoader)
|
||||
*/
|
||||
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
|
||||
ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
|
||||
ResourceLoader resourceLoader = getResourceLoader();
|
||||
ClassLoader cl = (resourceLoader != null ? resourceLoader.getClassLoader() : getBeanClassLoader());
|
||||
return new DefaultNamespaceHandlerResolver(cl);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -395,6 +395,7 @@ class BeanWrapperTests extends AbstractPropertyAccessorTests {
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("try")
|
||||
public static class ActiveResource implements AutoCloseable {
|
||||
|
||||
public ActiveResource getResource() {
|
||||
|
||||
+1
-17
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -436,7 +436,6 @@ class DefaultListableBeanFactoryTests {
|
||||
|
||||
@Test
|
||||
void empty() {
|
||||
ListableBeanFactory lbf = new DefaultListableBeanFactory();
|
||||
assertThat(lbf.getBeanDefinitionNames() != null).as("No beans defined --> array != null").isTrue();
|
||||
assertThat(lbf.getBeanDefinitionNames().length == 0).as("No beans defined after no arg constructor").isTrue();
|
||||
assertThat(lbf.getBeanDefinitionCount() == 0).as("No beans defined after no arg constructor").isTrue();
|
||||
@@ -778,21 +777,6 @@ class DefaultListableBeanFactoryTests {
|
||||
assertThat(factory.getType("child")).isEqualTo(DerivedTestBean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nameAlreadyBound() {
|
||||
Properties p = new Properties();
|
||||
p.setProperty("kerry.(class)", TestBean.class.getName());
|
||||
p.setProperty("kerry.age", "35");
|
||||
registerBeanDefinitions(p);
|
||||
try {
|
||||
registerBeanDefinitions(p);
|
||||
}
|
||||
catch (BeanDefinitionStoreException ex) {
|
||||
assertThat(ex.getBeanName()).isEqualTo("kerry");
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
private void singleTestBean(ListableBeanFactory lbf) {
|
||||
assertThat(lbf.getBeanDefinitionCount() == 1).as("1 beans defined").isTrue();
|
||||
String[] names = lbf.getBeanDefinitionNames();
|
||||
|
||||
Vendored
+9
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -51,6 +51,7 @@ import org.springframework.util.ObjectUtils;
|
||||
* @author Juergen Hoeller
|
||||
* @author Stephane Nicoll
|
||||
* @author Sam Brannen
|
||||
* @author Brian Clozel
|
||||
* @since 4.3
|
||||
* @see CaffeineCache
|
||||
*/
|
||||
@@ -188,8 +189,13 @@ public class CaffeineCacheManager implements CacheManager {
|
||||
@Override
|
||||
@Nullable
|
||||
public Cache getCache(String name) {
|
||||
return this.cacheMap.computeIfAbsent(name, cacheName ->
|
||||
this.dynamic ? createCaffeineCache(cacheName) : null);
|
||||
if (this.dynamic) {
|
||||
Cache cache = this.cacheMap.get(name);
|
||||
return (cache != null) ? cache : this.cacheMap.computeIfAbsent(name, this::createCaffeineCache);
|
||||
}
|
||||
else {
|
||||
return this.cacheMap.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -132,10 +132,10 @@ public class JavaMailSenderImpl implements JavaMailSender {
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow {code Map} access to the JavaMail properties of this sender,
|
||||
* Allow {@code Map} access to the JavaMail properties of this sender,
|
||||
* with the option to add or override specific entries.
|
||||
* <p>Useful for specifying entries directly, for example via
|
||||
* {code javaMailProperties[mail.smtp.auth]}.
|
||||
* {@code javaMailProperties[mail.smtp.auth]}.
|
||||
*/
|
||||
public Properties getJavaMailProperties() {
|
||||
return this.javaMailProperties;
|
||||
|
||||
+6
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -729,9 +729,11 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
|
||||
/**
|
||||
* Modify the application context's internal bean factory after its standard
|
||||
* initialization. All bean definitions will have been loaded, but no beans
|
||||
* will have been instantiated yet. This allows for registering special
|
||||
* BeanPostProcessors etc in certain ApplicationContext implementations.
|
||||
* initialization. The initial definition resources will have been loaded but no
|
||||
* post-processors will have run and no derived bean definitions will have been
|
||||
* registered, and most importantly, no beans will have been instantiated yet.
|
||||
* <p>This template method allows for registering special BeanPostProcessors
|
||||
* etc in certain AbstractApplicationContext subclasses.
|
||||
* @param beanFactory the bean factory used by the application context
|
||||
*/
|
||||
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
|
||||
|
||||
@@ -82,6 +82,17 @@ public abstract class AnnotationVisitor {
|
||||
this.av = annotationVisitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* The annotation visitor to which this visitor must delegate method calls. May be {@literal
|
||||
* null}.
|
||||
*
|
||||
* @return the annotation visitor to which this visitor must delegate method calls, or {@literal
|
||||
* null}.
|
||||
*/
|
||||
public AnnotationVisitor getDelegate() {
|
||||
return av;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits a primitive value of the annotation.
|
||||
*
|
||||
|
||||
@@ -194,7 +194,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.V19) {
|
||||
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V21) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unsupported class file major version " + readShort(classFileOffset + 6));
|
||||
}
|
||||
@@ -308,6 +308,7 @@ public class ClassReader {
|
||||
* @return the content of the given input stream.
|
||||
* @throws IOException if a problem occurs during reading.
|
||||
*/
|
||||
@SuppressWarnings("PMD.UseTryWithResources")
|
||||
private static byte[] readStream(final InputStream inputStream, final boolean close)
|
||||
throws IOException {
|
||||
if (inputStream == null) {
|
||||
@@ -376,7 +377,7 @@ public class ClassReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the internal of name of the super class (see {@link Type#getInternalName()}). For
|
||||
* Returns the internal name of the super class (see {@link Type#getInternalName()}). For
|
||||
* interfaces, the super class is {@link Object}.
|
||||
*
|
||||
* @return the internal name of the super class, or {@literal null} for {@link Object} class.
|
||||
|
||||
@@ -42,7 +42,8 @@ public final class ClassTooLargeException extends IndexOutOfBoundsException {
|
||||
/**
|
||||
* Constructs a new {@link ClassTooLargeException}.
|
||||
*
|
||||
* @param className the internal name of the class.
|
||||
* @param className the internal name of the class (see {@link
|
||||
* org.objectweb.asm.Type#getInternalName()}).
|
||||
* @param constantPoolCount the number of constant pool items of the class.
|
||||
*/
|
||||
public ClassTooLargeException(final String className, final int constantPoolCount) {
|
||||
@@ -52,7 +53,7 @@ public final class ClassTooLargeException extends IndexOutOfBoundsException {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the internal name of the class.
|
||||
* Returns the internal name of the class (see {@link org.objectweb.asm.Type#getInternalName()}).
|
||||
*
|
||||
* @return the internal name of the class.
|
||||
*/
|
||||
|
||||
@@ -81,6 +81,15 @@ public abstract class ClassVisitor {
|
||||
this.cv = classVisitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* The class visitor to which this visitor must delegate method calls. May be {@literal null}.
|
||||
*
|
||||
* @return the class visitor to which this visitor must delegate method calls, or {@literal null}.
|
||||
*/
|
||||
public ClassVisitor getDelegate() {
|
||||
return cv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits the header of the class.
|
||||
*
|
||||
@@ -155,7 +164,8 @@ public abstract class ClassVisitor {
|
||||
* implicitly its own nest, so it's invalid to call this method with the visited class name as
|
||||
* argument.
|
||||
*
|
||||
* @param nestHost the internal name of the host class of the nest.
|
||||
* @param nestHost the internal name of the host class of the nest (see {@link
|
||||
* Type#getInternalName()}).
|
||||
*/
|
||||
public void visitNestHost(final String nestHost) {
|
||||
if (api < Opcodes.ASM7) {
|
||||
@@ -167,14 +177,19 @@ public abstract class ClassVisitor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits the enclosing class of the class. This method must be called only if the class has an
|
||||
* enclosing class.
|
||||
* Visits the enclosing class of the class. This method must be called only if this class is a
|
||||
* local or anonymous class. See the JVMS 4.7.7 section for more details.
|
||||
*
|
||||
* @param owner internal name of the enclosing class of the class.
|
||||
* @param owner internal name of the enclosing class of the class (see {@link
|
||||
* Type#getInternalName()}).
|
||||
* @param name the name of the method that contains the class, or {@literal null} if the class is
|
||||
* not enclosed in a method of its enclosing class.
|
||||
* not enclosed in a method or constructor of its enclosing class (e.g. if it is enclosed in
|
||||
* an instance initializer, static initializer, instance variable initializer, or class
|
||||
* variable initializer).
|
||||
* @param descriptor the descriptor of the method that contains the class, or {@literal null} if
|
||||
* the class is not enclosed in a method of its enclosing class.
|
||||
* the class is not enclosed in a method or constructor of its enclosing class (e.g. if it is
|
||||
* enclosed in an instance initializer, static initializer, instance variable initializer, or
|
||||
* class variable initializer).
|
||||
*/
|
||||
public void visitOuterClass(final String owner, final String name, final String descriptor) {
|
||||
if (cv != null) {
|
||||
@@ -241,7 +256,7 @@ public abstract class ClassVisitor {
|
||||
* the visited class is the host of a nest. A nest host is implicitly a member of its own nest, so
|
||||
* it's invalid to call this method with the visited class name as argument.
|
||||
*
|
||||
* @param nestMember the internal name of a nest member.
|
||||
* @param nestMember the internal name of a nest member (see {@link Type#getInternalName()}).
|
||||
*/
|
||||
public void visitNestMember(final String nestMember) {
|
||||
if (api < Opcodes.ASM7) {
|
||||
@@ -256,7 +271,8 @@ public abstract class ClassVisitor {
|
||||
* Visits a permitted subclasses. A permitted subclass is one of the allowed subclasses of the
|
||||
* current class.
|
||||
*
|
||||
* @param permittedSubclass the internal name of a permitted subclass.
|
||||
* @param permittedSubclass the internal name of a permitted subclass (see {@link
|
||||
* Type#getInternalName()}).
|
||||
*/
|
||||
public void visitPermittedSubclass(final String permittedSubclass) {
|
||||
if (api < Opcodes.ASM9) {
|
||||
@@ -269,15 +285,18 @@ public abstract class ClassVisitor {
|
||||
|
||||
/**
|
||||
* Visits information about an inner class. This inner class is not necessarily a member of the
|
||||
* class being visited.
|
||||
* class being visited. More precisely, every class or interface C which is referenced by this
|
||||
* class and which is not a package member must be visited with this method. This class must
|
||||
* reference its nested class or interface members, and its enclosing class, if any. See the JVMS
|
||||
* 4.7.6 section for more details.
|
||||
*
|
||||
* @param name the internal name of an inner class (see {@link Type#getInternalName()}).
|
||||
* @param outerName the internal name of the class to which the inner class belongs (see {@link
|
||||
* Type#getInternalName()}). May be {@literal null} for not member classes.
|
||||
* @param innerName the (simple) name of the inner class inside its enclosing class. May be
|
||||
* {@literal null} for anonymous inner classes.
|
||||
* @param access the access flags of the inner class as originally declared in the enclosing
|
||||
* class.
|
||||
* @param name the internal name of C (see {@link Type#getInternalName()}).
|
||||
* @param outerName the internal name of the class or interface C is a member of (see {@link
|
||||
* Type#getInternalName()}). Must be {@literal null} if C is not the member of a class or
|
||||
* interface (e.g. for local or anonymous classes).
|
||||
* @param innerName the (simple) name of C. Must be {@literal null} for anonymous inner classes.
|
||||
* @param access the access flags of C originally declared in the source code from which this
|
||||
* class was compiled.
|
||||
*/
|
||||
public void visitInnerClass(
|
||||
final String name, final String outerName, final String innerName, final int access) {
|
||||
|
||||
@@ -842,7 +842,7 @@ public class ClassWriter extends ClassVisitor {
|
||||
* constant pool already contains a similar item. <i>This method is intended for {@link Attribute}
|
||||
* sub classes, and is normally not needed by class generators or adapters.</i>
|
||||
*
|
||||
* @param value the internal name of the class.
|
||||
* @param value the internal name of the class (see {@link Type#getInternalName()}).
|
||||
* @return the index of a new or already existing class reference item.
|
||||
*/
|
||||
public int newClass(final String value) {
|
||||
@@ -894,7 +894,8 @@ public class ClassWriter extends ClassVisitor {
|
||||
* Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link
|
||||
* Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL},
|
||||
* {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}.
|
||||
* @param owner the internal name of the field or method owner class.
|
||||
* @param owner the internal name of the field or method owner class (see {@link
|
||||
* Type#getInternalName()}).
|
||||
* @param name the name of the field or method.
|
||||
* @param descriptor the descriptor of the field or method.
|
||||
* @return the index of a new or already existing method type reference item.
|
||||
@@ -916,7 +917,8 @@ public class ClassWriter extends ClassVisitor {
|
||||
* Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link
|
||||
* Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL},
|
||||
* {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}.
|
||||
* @param owner the internal name of the field or method owner class.
|
||||
* @param owner the internal name of the field or method owner class (see {@link
|
||||
* Type#getInternalName()}).
|
||||
* @param name the name of the field or method.
|
||||
* @param descriptor the descriptor of the field or method.
|
||||
* @param isInterface true if the owner is an interface.
|
||||
@@ -978,7 +980,7 @@ public class ClassWriter extends ClassVisitor {
|
||||
* constant pool already contains a similar item. <i>This method is intended for {@link Attribute}
|
||||
* sub classes, and is normally not needed by class generators or adapters.</i>
|
||||
*
|
||||
* @param owner the internal name of the field's owner class.
|
||||
* @param owner the internal name of the field's owner class (see {@link Type#getInternalName()}).
|
||||
* @param name the field's name.
|
||||
* @param descriptor the field's descriptor.
|
||||
* @return the index of a new or already existing field reference item.
|
||||
@@ -992,7 +994,8 @@ public class ClassWriter extends ClassVisitor {
|
||||
* constant pool already contains a similar item. <i>This method is intended for {@link Attribute}
|
||||
* sub classes, and is normally not needed by class generators or adapters.</i>
|
||||
*
|
||||
* @param owner the internal name of the method's owner class.
|
||||
* @param owner the internal name of the method's owner class (see {@link
|
||||
* Type#getInternalName()}).
|
||||
* @param name the method's name.
|
||||
* @param descriptor the method's descriptor.
|
||||
* @param isInterface {@literal true} if {@code owner} is an interface.
|
||||
@@ -1028,9 +1031,10 @@ public class ClassWriter extends ClassVisitor {
|
||||
* currently being generated by this ClassWriter, which can of course not be loaded since it is
|
||||
* under construction.
|
||||
*
|
||||
* @param type1 the internal name of a class.
|
||||
* @param type2 the internal name of another class.
|
||||
* @return the internal name of the common super class of the two given classes.
|
||||
* @param type1 the internal name of a class (see {@link Type#getInternalName()}).
|
||||
* @param type2 the internal name of another class (see {@link Type#getInternalName()}).
|
||||
* @return the internal name of the common super class of the two given classes (see {@link
|
||||
* Type#getInternalName()}).
|
||||
*/
|
||||
protected String getCommonSuperClass(final String type1, final String type2) {
|
||||
ClassLoader classLoader = getClassLoader();
|
||||
|
||||
@@ -78,6 +78,15 @@ public abstract class FieldVisitor {
|
||||
this.fv = fieldVisitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* The field visitor to which this visitor must delegate method calls. May be {@literal null}.
|
||||
*
|
||||
* @return the field visitor to which this visitor must delegate method calls, or {@literal null}.
|
||||
*/
|
||||
public FieldVisitor getDelegate() {
|
||||
return fv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits an annotation of the field.
|
||||
*
|
||||
|
||||
@@ -367,11 +367,12 @@ class Frame {
|
||||
typeValue = REFERENCE_KIND | symbolTable.addType(internalName);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid descriptor fragment: " + buffer.substring(elementDescriptorOffset));
|
||||
}
|
||||
return ((elementDescriptorOffset - offset) << DIM_SHIFT) | typeValue;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
throw new IllegalArgumentException("Invalid descriptor: " + buffer.substring(offset));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ public final class Handle {
|
||||
* {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or {@link
|
||||
* Opcodes#H_INVOKEINTERFACE}.
|
||||
* @param owner the internal name of the class that owns the field or method designated by this
|
||||
* handle.
|
||||
* handle (see {@link Type#getInternalName()}).
|
||||
* @param name the name of the field or method designated by this handle.
|
||||
* @param descriptor the descriptor of the field or method designated by this handle.
|
||||
* @deprecated this constructor has been superseded by {@link #Handle(int, String, String, String,
|
||||
@@ -85,7 +85,7 @@ public final class Handle {
|
||||
* {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or {@link
|
||||
* Opcodes#H_INVOKEINTERFACE}.
|
||||
* @param owner the internal name of the class that owns the field or method designated by this
|
||||
* handle.
|
||||
* handle (see {@link Type#getInternalName()}).
|
||||
* @param name the name of the field or method designated by this handle.
|
||||
* @param descriptor the descriptor of the field or method designated by this handle.
|
||||
* @param isInterface whether the owner is an interface or not.
|
||||
@@ -118,7 +118,8 @@ public final class Handle {
|
||||
/**
|
||||
* Returns the internal name of the class that owns the field or method designated by this handle.
|
||||
*
|
||||
* @return the internal name of the class that owns the field or method designated by this handle.
|
||||
* @return the internal name of the class that owns the field or method designated by this handle
|
||||
* (see {@link Type#getInternalName()}).
|
||||
*/
|
||||
public String getOwner() {
|
||||
return owner;
|
||||
|
||||
@@ -44,7 +44,7 @@ public final class MethodTooLargeException extends IndexOutOfBoundsException {
|
||||
/**
|
||||
* Constructs a new {@link MethodTooLargeException}.
|
||||
*
|
||||
* @param className the internal name of the owner class.
|
||||
* @param className the internal name of the owner class (see {@link Type#getInternalName()}).
|
||||
* @param methodName the name of the method.
|
||||
* @param descriptor the descriptor of the method.
|
||||
* @param codeSize the size of the method's Code attribute, in bytes.
|
||||
@@ -64,7 +64,7 @@ public final class MethodTooLargeException extends IndexOutOfBoundsException {
|
||||
/**
|
||||
* Returns the internal name of the owner class.
|
||||
*
|
||||
* @return the internal name of the owner class.
|
||||
* @return the internal name of the owner class (see {@link Type#getInternalName()}).
|
||||
*/
|
||||
public String getClassName() {
|
||||
return className;
|
||||
|
||||
@@ -94,6 +94,16 @@ public abstract class MethodVisitor {
|
||||
this.mv = methodVisitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method visitor to which this visitor must delegate method calls. May be {@literal null}.
|
||||
*
|
||||
* @return the method visitor to which this visitor must delegate method calls, or {@literal
|
||||
* null}.
|
||||
*/
|
||||
public MethodVisitor getDelegate() {
|
||||
return mv;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// Parameters, annotations and non standard attributes
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
@@ -120,7 +130,7 @@ public abstract class MethodVisitor {
|
||||
* @return a visitor to the visit the actual default value of this annotation interface method, or
|
||||
* {@literal null} if this visitor is not interested in visiting this default value. The
|
||||
* 'name' parameters passed to the methods of this annotation visitor are ignored. Moreover,
|
||||
* exacly one visit method must be called on this annotation visitor, followed by visitEnd.
|
||||
* exactly one visit method must be called on this annotation visitor, followed by visitEnd.
|
||||
*/
|
||||
public AnnotationVisitor visitAnnotationDefault() {
|
||||
if (mv != null) {
|
||||
@@ -273,15 +283,17 @@ public abstract class MethodVisitor {
|
||||
* @param type the type of this stack map frame. Must be {@link Opcodes#F_NEW} for expanded
|
||||
* frames, or {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, {@link Opcodes#F_CHOP}, {@link
|
||||
* Opcodes#F_SAME} or {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for compressed frames.
|
||||
* @param numLocal the number of local variables in the visited frame.
|
||||
* @param numLocal the number of local variables in the visited frame. Long and double values
|
||||
* count for one variable.
|
||||
* @param local the local variable types in this frame. This array must not be modified. Primitive
|
||||
* types are represented by {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link
|
||||
* Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL} or
|
||||
* {@link Opcodes#UNINITIALIZED_THIS} (long and double are represented by a single element).
|
||||
* Reference types are represented by String objects (representing internal names), and
|
||||
* uninitialized types by Label objects (this label designates the NEW instruction that
|
||||
* created this uninitialized value).
|
||||
* @param numStack the number of operand stack elements in the visited frame.
|
||||
* Reference types are represented by String objects (representing internal names, see {@link
|
||||
* Type#getInternalName()}), and uninitialized types by Label objects (this label designates
|
||||
* the NEW instruction that created this uninitialized value).
|
||||
* @param numStack the number of operand stack elements in the visited frame. Long and double
|
||||
* values count for one stack element.
|
||||
* @param stack the operand stack types in this frame. This array must not be modified. Its
|
||||
* content has the same format as the "local" array.
|
||||
* @throws IllegalStateException if a frame is visited just after another one, without any
|
||||
@@ -360,7 +372,7 @@ public abstract class MethodVisitor {
|
||||
|
||||
/**
|
||||
* Visits a type instruction. A type instruction is an instruction that takes the internal name of
|
||||
* a class as parameter.
|
||||
* a class as parameter (see {@link Type#getInternalName()}).
|
||||
*
|
||||
* @param opcode the opcode of the type instruction to be visited. This opcode is either NEW,
|
||||
* ANEWARRAY, CHECKCAST or INSTANCEOF.
|
||||
@@ -552,12 +564,12 @@ public abstract class MethodVisitor {
|
||||
/**
|
||||
* Visits an IINC instruction.
|
||||
*
|
||||
* @param var index of the local variable to be incremented.
|
||||
* @param varIndex index of the local variable to be incremented.
|
||||
* @param increment amount to increment the local variable by.
|
||||
*/
|
||||
public void visitIincInsn(final int var, final int increment) {
|
||||
public void visitIincInsn(final int varIndex, final int increment) {
|
||||
if (mv != null) {
|
||||
mv.visitIincInsn(var, increment);
|
||||
mv.visitIincInsn(varIndex, increment);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -643,8 +655,9 @@ public abstract class MethodVisitor {
|
||||
* @param start the beginning of the exception handler's scope (inclusive).
|
||||
* @param end the end of the exception handler's scope (exclusive).
|
||||
* @param handler the beginning of the exception handler's code.
|
||||
* @param type the internal name of the type of exceptions handled by the handler, or {@literal
|
||||
* null} to catch any exceptions (for "finally" blocks).
|
||||
* @param type the internal name of the type of exceptions handled by the handler (see {@link
|
||||
* Type#getInternalName()}), or {@literal null} to catch any exceptions (for "finally"
|
||||
* blocks).
|
||||
* @throws IllegalArgumentException if one of the labels has already been visited by this visitor
|
||||
* (by the {@link #visitLabel} method).
|
||||
*/
|
||||
|
||||
@@ -284,6 +284,8 @@ public interface Opcodes {
|
||||
int V17 = 0 << 16 | 61;
|
||||
int V18 = 0 << 16 | 62;
|
||||
int V19 = 0 << 16 | 63;
|
||||
int V20 = 0 << 16 | 64;
|
||||
int V21 = 0 << 16 | 65;
|
||||
|
||||
/**
|
||||
* Version flag indicating that the class is using 'preview' features.
|
||||
|
||||
@@ -45,7 +45,7 @@ public abstract class RecordComponentVisitor {
|
||||
/**
|
||||
* The record visitor to which this visitor must delegate method calls. May be {@literal null}.
|
||||
*/
|
||||
/*package-private*/ RecordComponentVisitor delegate;
|
||||
protected RecordComponentVisitor delegate;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link RecordComponentVisitor}.
|
||||
@@ -83,7 +83,8 @@ public abstract class RecordComponentVisitor {
|
||||
/**
|
||||
* The record visitor to which this visitor must delegate method calls. May be {@literal null}.
|
||||
*
|
||||
* @return the record visitor to which this visitor must delegate method calls or {@literal null}.
|
||||
* @return the record visitor to which this visitor must delegate method calls, or {@literal
|
||||
* null}.
|
||||
*/
|
||||
public RecordComponentVisitor getDelegate() {
|
||||
return delegate;
|
||||
|
||||
@@ -37,7 +37,7 @@ final class RecordComponentWriter extends RecordComponentVisitor {
|
||||
/** The name_index field of the Record attribute. */
|
||||
private final int nameIndex;
|
||||
|
||||
/** The descriptor_index field of the the Record attribute. */
|
||||
/** The descriptor_index field of the Record attribute. */
|
||||
private final int descriptorIndex;
|
||||
|
||||
/**
|
||||
|
||||
@@ -245,7 +245,7 @@ public final class Type {
|
||||
/**
|
||||
* Returns the {@link Type} corresponding to the given internal name.
|
||||
*
|
||||
* @param internalName an internal name.
|
||||
* @param internalName an internal name (see {@link Type#getInternalName()}).
|
||||
* @return the {@link Type} corresponding to the given internal name.
|
||||
*/
|
||||
public static Type getObjectType(final String internalName) {
|
||||
@@ -708,8 +708,8 @@ public final class Type {
|
||||
*
|
||||
* @return the size of the arguments of the method (plus one for the implicit this argument),
|
||||
* argumentsSize, and the size of its return value, returnSize, packed into a single int i =
|
||||
* {@code (argumentsSize << 2) | returnSize} (argumentsSize is therefore equal to {@code
|
||||
* i >> 2}, and returnSize to {@code i & 0x03}).
|
||||
* {@code (argumentsSize << 2) | returnSize} (argumentsSize is therefore equal to {@code
|
||||
* i >> 2}, and returnSize to {@code i & 0x03}).
|
||||
*/
|
||||
public int getArgumentsAndReturnSizes() {
|
||||
return getArgumentsAndReturnSizes(getDescriptor());
|
||||
@@ -721,8 +721,8 @@ public final class Type {
|
||||
* @param methodDescriptor a method descriptor.
|
||||
* @return the size of the arguments of the method (plus one for the implicit this argument),
|
||||
* argumentsSize, and the size of its return value, returnSize, packed into a single int i =
|
||||
* {@code (argumentsSize << 2) | returnSize} (argumentsSize is therefore equal to {@code
|
||||
* i >> 2}, and returnSize to {@code i & 0x03}).
|
||||
* {@code (argumentsSize << 2) | returnSize} (argumentsSize is therefore equal to {@code
|
||||
* i >> 2}, and returnSize to {@code i & 0x03}).
|
||||
*/
|
||||
public static int getArgumentsAndReturnSizes(final String methodDescriptor) {
|
||||
int argumentsSize = 1;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,12 +22,13 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A simple log message type for use with Commons Logging, allowing
|
||||
* for convenient lazy resolution of a given {@link Supplier} instance
|
||||
* (typically bound to a Java 8 lambda expression) or a printf-style
|
||||
* format string ({@link String#format}) in its {@link #toString()}.
|
||||
* A simple log message type for use with Commons Logging, allowing for convenient
|
||||
* lazy resolution of a given {@link Supplier} instance (typically bound to a lambda
|
||||
* expression) or a printf-style format string ({@link String#format}) in its
|
||||
* {@link #toString()}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.2
|
||||
* @see #of(Supplier)
|
||||
* @see #format(String, Object)
|
||||
@@ -77,7 +78,7 @@ public abstract class LogMessage implements CharSequence {
|
||||
|
||||
/**
|
||||
* Build a lazily resolving message from the given supplier.
|
||||
* @param supplier the supplier (typically bound to a Java 8 lambda expression)
|
||||
* @param supplier the supplier (typically bound to a lambda expression)
|
||||
* @see #toString()
|
||||
*/
|
||||
public static LogMessage of(Supplier<? extends CharSequence> supplier) {
|
||||
@@ -87,63 +88,68 @@ public abstract class LogMessage implements CharSequence {
|
||||
/**
|
||||
* Build a lazily formatted message from the given format string and argument.
|
||||
* @param format the format string (following {@link String#format} rules)
|
||||
* @param arg1 the argument
|
||||
* @param arg1 the argument (can be {@code null})
|
||||
* @see String#format(String, Object...)
|
||||
*/
|
||||
public static LogMessage format(String format, Object arg1) {
|
||||
public static LogMessage format(String format, @Nullable Object arg1) {
|
||||
return new FormatMessage1(format, arg1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a lazily formatted message from the given format string and arguments.
|
||||
* @param format the format string (following {@link String#format} rules)
|
||||
* @param arg1 the first argument
|
||||
* @param arg2 the second argument
|
||||
* @param arg1 the first argument (can be {@code null})
|
||||
* @param arg2 the second argument (can be {@code null})
|
||||
* @see String#format(String, Object...)
|
||||
*/
|
||||
public static LogMessage format(String format, Object arg1, Object arg2) {
|
||||
public static LogMessage format(String format, @Nullable Object arg1, @Nullable Object arg2) {
|
||||
return new FormatMessage2(format, arg1, arg2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a lazily formatted message from the given format string and arguments.
|
||||
* @param format the format string (following {@link String#format} rules)
|
||||
* @param arg1 the first argument
|
||||
* @param arg2 the second argument
|
||||
* @param arg3 the third argument
|
||||
* @param arg1 the first argument (can be {@code null})
|
||||
* @param arg2 the second argument (can be {@code null})
|
||||
* @param arg3 the third argument (can be {@code null})
|
||||
* @see String#format(String, Object...)
|
||||
*/
|
||||
public static LogMessage format(String format, Object arg1, Object arg2, Object arg3) {
|
||||
public static LogMessage format(String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3) {
|
||||
return new FormatMessage3(format, arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a lazily formatted message from the given format string and arguments.
|
||||
* @param format the format string (following {@link String#format} rules)
|
||||
* @param arg1 the first argument
|
||||
* @param arg2 the second argument
|
||||
* @param arg3 the third argument
|
||||
* @param arg4 the fourth argument
|
||||
* @param arg1 the first argument (can be {@code null})
|
||||
* @param arg2 the second argument (can be {@code null})
|
||||
* @param arg3 the third argument (can be {@code null})
|
||||
* @param arg4 the fourth argument (can be {@code null})
|
||||
* @see String#format(String, Object...)
|
||||
*/
|
||||
public static LogMessage format(String format, Object arg1, Object arg2, Object arg3, Object arg4) {
|
||||
public static LogMessage format(String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3,
|
||||
@Nullable Object arg4) {
|
||||
return new FormatMessage4(format, arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a lazily formatted message from the given format string and varargs.
|
||||
* <p>This varargs {@code format()} variant may be costly. You should therefore
|
||||
* use the individual argument variants whenever possible:
|
||||
* {@link #format(String, Object)}, {@link #format(String, Object, Object)}, etc.
|
||||
* @param format the format string (following {@link String#format} rules)
|
||||
* @param args the varargs array (costly, prefer individual arguments)
|
||||
* @param args the varargs array (can be {@code null} and can contain {@code null}
|
||||
* elements)
|
||||
* @see String#format(String, Object...)
|
||||
*/
|
||||
public static LogMessage format(String format, Object... args) {
|
||||
public static LogMessage format(String format, @Nullable Object... args) {
|
||||
return new FormatMessageX(format, args);
|
||||
}
|
||||
|
||||
|
||||
private static final class SupplierMessage extends LogMessage {
|
||||
|
||||
private Supplier<? extends CharSequence> supplier;
|
||||
private final Supplier<? extends CharSequence> supplier;
|
||||
|
||||
SupplierMessage(Supplier<? extends CharSequence> supplier) {
|
||||
Assert.notNull(supplier, "Supplier must not be null");
|
||||
@@ -170,9 +176,10 @@ public abstract class LogMessage implements CharSequence {
|
||||
|
||||
private static final class FormatMessage1 extends FormatMessage {
|
||||
|
||||
@Nullable
|
||||
private final Object arg1;
|
||||
|
||||
FormatMessage1(String format, Object arg1) {
|
||||
FormatMessage1(String format, @Nullable Object arg1) {
|
||||
super(format);
|
||||
this.arg1 = arg1;
|
||||
}
|
||||
@@ -186,11 +193,13 @@ public abstract class LogMessage implements CharSequence {
|
||||
|
||||
private static final class FormatMessage2 extends FormatMessage {
|
||||
|
||||
@Nullable
|
||||
private final Object arg1;
|
||||
|
||||
@Nullable
|
||||
private final Object arg2;
|
||||
|
||||
FormatMessage2(String format, Object arg1, Object arg2) {
|
||||
FormatMessage2(String format, @Nullable Object arg1, @Nullable Object arg2) {
|
||||
super(format);
|
||||
this.arg1 = arg1;
|
||||
this.arg2 = arg2;
|
||||
@@ -205,13 +214,16 @@ public abstract class LogMessage implements CharSequence {
|
||||
|
||||
private static final class FormatMessage3 extends FormatMessage {
|
||||
|
||||
@Nullable
|
||||
private final Object arg1;
|
||||
|
||||
@Nullable
|
||||
private final Object arg2;
|
||||
|
||||
@Nullable
|
||||
private final Object arg3;
|
||||
|
||||
FormatMessage3(String format, Object arg1, Object arg2, Object arg3) {
|
||||
FormatMessage3(String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3) {
|
||||
super(format);
|
||||
this.arg1 = arg1;
|
||||
this.arg2 = arg2;
|
||||
@@ -227,15 +239,20 @@ public abstract class LogMessage implements CharSequence {
|
||||
|
||||
private static final class FormatMessage4 extends FormatMessage {
|
||||
|
||||
@Nullable
|
||||
private final Object arg1;
|
||||
|
||||
@Nullable
|
||||
private final Object arg2;
|
||||
|
||||
@Nullable
|
||||
private final Object arg3;
|
||||
|
||||
@Nullable
|
||||
private final Object arg4;
|
||||
|
||||
FormatMessage4(String format, Object arg1, Object arg2, Object arg3, Object arg4) {
|
||||
FormatMessage4(String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3,
|
||||
@Nullable Object arg4) {
|
||||
super(format);
|
||||
this.arg1 = arg1;
|
||||
this.arg2 = arg2;
|
||||
@@ -252,9 +269,10 @@ public abstract class LogMessage implements CharSequence {
|
||||
|
||||
private static final class FormatMessageX extends FormatMessage {
|
||||
|
||||
@Nullable
|
||||
private final Object[] args;
|
||||
|
||||
FormatMessageX(String format, Object... args) {
|
||||
FormatMessageX(String format, @Nullable Object... args) {
|
||||
super(format);
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
+6
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -90,4 +90,9 @@ public class ExecutorServiceAdapter extends AbstractExecutorService {
|
||||
return false;
|
||||
}
|
||||
|
||||
// @Override on JDK 19
|
||||
public void close() {
|
||||
// no-op in order to avoid container-triggered shutdown call which would lead to exception logging
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -64,7 +64,7 @@ public class InstanceFilter<T> {
|
||||
|
||||
|
||||
/**
|
||||
* Determine if the specified {code instance} matches this filter.
|
||||
* Determine if the specified {@code instance} matches this filter.
|
||||
*/
|
||||
public boolean match(T instance) {
|
||||
Assert.notNull(instance, "Instance to match must not be null");
|
||||
|
||||
+10
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -264,7 +264,15 @@ public enum SpelMessage {
|
||||
|
||||
/** @since 5.3.17 */
|
||||
MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED(Kind.ERROR, 1075,
|
||||
"Array declares too many elements, exceeding the threshold of ''{0}''");
|
||||
"Array declares too many elements, exceeding the threshold of ''{0}''"),
|
||||
|
||||
/** @since 5.3.26 */
|
||||
MAX_REPEATED_TEXT_SIZE_EXCEEDED(Kind.ERROR, 1076,
|
||||
"Repeated text results in too many characters, exceeding the threshold of ''{0}''"),
|
||||
|
||||
/** @since 5.3.26 */
|
||||
MAX_REGEX_LENGTH_EXCEEDED(Kind.ERROR, 1077,
|
||||
"Regular expression contains too many characters, exceeding the threshold of ''{0}''");
|
||||
|
||||
|
||||
private final Kind kind;
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -144,7 +144,7 @@ public class FunctionReference extends SpelNodeImpl {
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
sj.add(getChild(i).toStringAST());
|
||||
}
|
||||
return '#' + this.name + sj.toString();
|
||||
return '#' + this.name + sj;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+4
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -263,7 +263,7 @@ public class MethodReference extends SpelNodeImpl {
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
sj.add(getChild(i).toStringAST());
|
||||
}
|
||||
return this.name + sj.toString();
|
||||
return this.name + sj;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -288,12 +288,9 @@ public class MethodReference extends SpelNodeImpl {
|
||||
if (executor.didArgumentConversionOccur()) {
|
||||
return false;
|
||||
}
|
||||
Class<?> clazz = executor.getMethod().getDeclaringClass();
|
||||
if (!Modifier.isPublic(clazz.getModifiers()) && executor.getPublicDeclaringClass() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
Class<?> clazz = executor.getMethod().getDeclaringClass();
|
||||
return (Modifier.isPublic(clazz.getModifiers()) || executor.getPublicDeclaringClass() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+24
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,6 +25,8 @@ import org.springframework.expression.Operation;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.expression.spel.CodeFlow;
|
||||
import org.springframework.expression.spel.ExpressionState;
|
||||
import org.springframework.expression.spel.SpelEvaluationException;
|
||||
import org.springframework.expression.spel.SpelMessage;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.NumberUtils;
|
||||
|
||||
@@ -52,6 +54,13 @@ import org.springframework.util.NumberUtils;
|
||||
*/
|
||||
public class OpMultiply extends Operator {
|
||||
|
||||
/**
|
||||
* Maximum number of characters permitted in repeated text.
|
||||
* @since 5.3.26
|
||||
*/
|
||||
private static final int MAX_REPEATED_TEXT_SIZE = 256;
|
||||
|
||||
|
||||
public OpMultiply(int startPos, int endPos, SpelNodeImpl... operands) {
|
||||
super("*", startPos, endPos, operands);
|
||||
}
|
||||
@@ -109,10 +118,13 @@ public class OpMultiply extends Operator {
|
||||
}
|
||||
|
||||
if (leftOperand instanceof String && rightOperand instanceof Integer) {
|
||||
int repeats = (Integer) rightOperand;
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < repeats; i++) {
|
||||
result.append(leftOperand);
|
||||
String text = (String) leftOperand;
|
||||
int count = (Integer) rightOperand;
|
||||
int requestedSize = text.length() * count;
|
||||
checkRepeatedTextSize(requestedSize);
|
||||
StringBuilder result = new StringBuilder(requestedSize);
|
||||
for (int i = 0; i < count; i++) {
|
||||
result.append(text);
|
||||
}
|
||||
return new TypedValue(result.toString());
|
||||
}
|
||||
@@ -120,6 +132,13 @@ public class OpMultiply extends Operator {
|
||||
return state.operate(Operation.MULTIPLY, leftOperand, rightOperand);
|
||||
}
|
||||
|
||||
private void checkRepeatedTextSize(int requestedSize) {
|
||||
if (requestedSize > MAX_REPEATED_TEXT_SIZE) {
|
||||
throw new SpelEvaluationException(getStartPosition(),
|
||||
SpelMessage.MAX_REPEATED_TEXT_SIZE_EXCEEDED, MAX_REPEATED_TEXT_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompilable() {
|
||||
if (!getLeftOperand().isCompilable()) {
|
||||
|
||||
+43
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -36,19 +36,41 @@ import org.springframework.expression.spel.support.BooleanTypedValue;
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
*/
|
||||
public class OperatorMatches extends Operator {
|
||||
|
||||
private static final int PATTERN_ACCESS_THRESHOLD = 1000000;
|
||||
|
||||
private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* Maximum number of characters permitted in a regular expression.
|
||||
* @since 5.3.26
|
||||
*/
|
||||
private static final int MAX_REGEX_LENGTH = 256;
|
||||
|
||||
private final ConcurrentMap<String, Pattern> patternCache;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link OperatorMatches} instance.
|
||||
* @deprecated as of Spring Framework 5.3.26 in favor of invoking
|
||||
* {@link #OperatorMatches(ConcurrentMap, int, int, SpelNodeImpl...)}
|
||||
* with a shared pattern cache instead
|
||||
*/
|
||||
@Deprecated
|
||||
public OperatorMatches(int startPos, int endPos, SpelNodeImpl... operands) {
|
||||
super("matches", startPos, endPos, operands);
|
||||
this(new ConcurrentHashMap<>(), startPos, endPos, operands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link OperatorMatches} instance with a shared pattern cache.
|
||||
* @since 5.3.26
|
||||
*/
|
||||
public OperatorMatches(ConcurrentMap<String, Pattern> patternCache, int startPos, int endPos, SpelNodeImpl... operands) {
|
||||
super("matches", startPos, endPos, operands);
|
||||
this.patternCache = patternCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the first operand matches the regex specified as the second operand.
|
||||
@@ -62,26 +84,28 @@ public class OperatorMatches extends Operator {
|
||||
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
||||
SpelNodeImpl leftOp = getLeftOperand();
|
||||
SpelNodeImpl rightOp = getRightOperand();
|
||||
String left = leftOp.getValue(state, String.class);
|
||||
Object right = getRightOperand().getValue(state);
|
||||
|
||||
if (left == null) {
|
||||
String input = leftOp.getValue(state, String.class);
|
||||
if (input == null) {
|
||||
throw new SpelEvaluationException(leftOp.getStartPosition(),
|
||||
SpelMessage.INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR, (Object) null);
|
||||
}
|
||||
|
||||
Object right = rightOp.getValue(state);
|
||||
if (!(right instanceof String)) {
|
||||
throw new SpelEvaluationException(rightOp.getStartPosition(),
|
||||
SpelMessage.INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR, right);
|
||||
}
|
||||
String regex = (String) right;
|
||||
|
||||
try {
|
||||
String rightString = (String) right;
|
||||
Pattern pattern = this.patternCache.get(rightString);
|
||||
Pattern pattern = this.patternCache.get(regex);
|
||||
if (pattern == null) {
|
||||
pattern = Pattern.compile(rightString);
|
||||
this.patternCache.putIfAbsent(rightString, pattern);
|
||||
checkRegexLength(regex);
|
||||
pattern = Pattern.compile(regex);
|
||||
this.patternCache.putIfAbsent(regex, pattern);
|
||||
}
|
||||
Matcher matcher = pattern.matcher(new MatcherInput(left, new AccessCount()));
|
||||
Matcher matcher = pattern.matcher(new MatcherInput(input, new AccessCount()));
|
||||
return BooleanTypedValue.forValue(matcher.matches());
|
||||
}
|
||||
catch (PatternSyntaxException ex) {
|
||||
@@ -94,6 +118,13 @@ public class OperatorMatches extends Operator {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkRegexLength(String regex) {
|
||||
if (regex.length() > MAX_REGEX_LENGTH) {
|
||||
throw new SpelEvaluationException(getStartPosition(),
|
||||
SpelMessage.MAX_REGEX_LENGTH_EXCEEDED, MAX_REGEX_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class AccessCount {
|
||||
|
||||
@@ -111,7 +142,7 @@ public class OperatorMatches extends Operator {
|
||||
|
||||
private final CharSequence value;
|
||||
|
||||
private AccessCount access;
|
||||
private final AccessCount access;
|
||||
|
||||
public MatcherInput(CharSequence value, AccessCount access) {
|
||||
this.value = value;
|
||||
|
||||
+8
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,6 +21,8 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.expression.ParseException;
|
||||
@@ -83,6 +85,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @author Phillip Webb
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
*/
|
||||
class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
@@ -95,6 +98,9 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
// For rules that build nodes, they are stacked here for return
|
||||
private final Deque<SpelNodeImpl> constructedNodes = new ArrayDeque<>();
|
||||
|
||||
// Shared cache for compiled regex patterns
|
||||
private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>();
|
||||
|
||||
// The expression being parsed
|
||||
private String expressionString = "";
|
||||
|
||||
@@ -248,7 +254,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
}
|
||||
|
||||
if (tk == TokenKind.MATCHES) {
|
||||
return new OperatorMatches(t.startPos, t.endPos, expr, rhExpr);
|
||||
return new OperatorMatches(this.patternCache, t.startPos, t.endPos, expr, rhExpr);
|
||||
}
|
||||
|
||||
Assert.isTrue(tk == TokenKind.BETWEEN, "Between token expected");
|
||||
|
||||
+12
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -48,6 +48,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
* @see StandardEvaluationContext#addMethodResolver(MethodResolver)
|
||||
*/
|
||||
@@ -225,8 +226,7 @@ public class ReflectiveMethodResolver implements MethodResolver {
|
||||
if (targetObject instanceof Class) {
|
||||
Set<Method> result = new LinkedHashSet<>();
|
||||
// Add these so that static methods are invocable on the type: e.g. Float.valueOf(..)
|
||||
Method[] methods = getMethods(type);
|
||||
for (Method method : methods) {
|
||||
for (Method method : getMethods(type)) {
|
||||
if (Modifier.isStatic(method.getModifiers())) {
|
||||
result.add(method);
|
||||
}
|
||||
@@ -239,19 +239,23 @@ public class ReflectiveMethodResolver implements MethodResolver {
|
||||
Set<Method> result = new LinkedHashSet<>();
|
||||
// Expose interface methods (not proxy-declared overrides) for proper vararg introspection
|
||||
for (Class<?> ifc : type.getInterfaces()) {
|
||||
Method[] methods = getMethods(ifc);
|
||||
for (Method method : methods) {
|
||||
for (Method method : getMethods(ifc)) {
|
||||
if (isCandidateForInvocation(method, type)) {
|
||||
result.add(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ensure methods defined in java.lang.Object are exposed for JDK proxies.
|
||||
for (Method method : getMethods(Object.class)) {
|
||||
if (isCandidateForInvocation(method, type)) {
|
||||
result.add(method);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
Set<Method> result = new LinkedHashSet<>();
|
||||
Method[] methods = getMethods(type);
|
||||
for (Method method : methods) {
|
||||
for (Method method : getMethods(type)) {
|
||||
if (isCandidateForInvocation(method, type)) {
|
||||
result.add(method);
|
||||
}
|
||||
@@ -276,7 +280,7 @@ public class ReflectiveMethodResolver implements MethodResolver {
|
||||
* Determine whether the given {@code Method} is a candidate for method resolution
|
||||
* on an instance of the given target class.
|
||||
* <p>The default implementation considers any method as a candidate, even for
|
||||
* static methods sand non-user-declared methods on the {@link Object} base class.
|
||||
* static methods and non-user-declared methods on the {@link Object} base class.
|
||||
* @param method the Method to evaluate
|
||||
* @param targetClass the concrete target class that is being introspected
|
||||
* @since 4.3.15
|
||||
|
||||
+5
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,7 +35,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
*/
|
||||
public abstract class AbstractExpressionTests {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
protected static final boolean DEBUG = false;
|
||||
|
||||
protected static final boolean SHOULD_BE_WRITABLE = true;
|
||||
|
||||
@@ -202,7 +202,9 @@ public abstract class AbstractExpressionTests {
|
||||
protected void parseAndCheckError(String expression, SpelMessage expectedMessage, Object... otherProperties) {
|
||||
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> {
|
||||
Expression expr = parser.parseExpression(expression);
|
||||
SpelUtilities.printAbstractSyntaxTree(System.out, expr);
|
||||
if (DEBUG) {
|
||||
SpelUtilities.printAbstractSyntaxTree(System.out, expr);
|
||||
}
|
||||
}).satisfies(ex -> {
|
||||
assertThat(ex.getMessageCode()).isEqualTo(expectedMessage);
|
||||
if (otherProperties != null && otherProperties.length != 0) {
|
||||
|
||||
+35
-23
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -127,17 +127,6 @@ class EvaluationTests extends AbstractExpressionTests {
|
||||
evaluate("null?.null?.null", null, null);
|
||||
}
|
||||
|
||||
@Test // SPR-16731
|
||||
void matchesWithPatternAccessThreshold() {
|
||||
String pattern = "^(?=[a-z0-9-]{1,47})([a-z0-9]+[-]{0,1}){1,47}[a-z0-9]{1}$";
|
||||
String expression = "'abcde-fghijklmn-o42pasdfasdfasdf.qrstuvwxyz10x.xx.yyy.zasdfasfd' matches \'" + pattern + "\'";
|
||||
Expression expr = parser.parseExpression(expression);
|
||||
assertThatExceptionOfType(SpelEvaluationException.class)
|
||||
.isThrownBy(expr::getValue)
|
||||
.withCauseInstanceOf(IllegalStateException.class)
|
||||
.satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.FLAWED_PATTERN));
|
||||
}
|
||||
|
||||
// mixing operators
|
||||
@Test
|
||||
void mixingOperators() {
|
||||
@@ -460,28 +449,49 @@ class EvaluationTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void relOperatorsMatches01() {
|
||||
evaluate("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "false", Boolean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void relOperatorsMatches02() {
|
||||
void matchesTrue() {
|
||||
evaluate("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "true", Boolean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void relOperatorsMatches03() {
|
||||
void matchesFalse() {
|
||||
evaluate("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "false", Boolean.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWithInputConversion() {
|
||||
evaluate("27 matches '^.*2.*$'", true, Boolean.class); // conversion int --> string
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWithNullInput() {
|
||||
evaluateAndCheckError("null matches '^.*$'", SpelMessage.INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR, 0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void relOperatorsMatches04() {
|
||||
void matchesWithNullPattern() {
|
||||
evaluateAndCheckError("'abc' matches null", SpelMessage.INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR, 14, null);
|
||||
}
|
||||
|
||||
@Test // SPR-16731
|
||||
void matchesWithPatternAccessThreshold() {
|
||||
String pattern = "^(?=[a-z0-9-]{1,47})([a-z0-9]+[-]{0,1}){1,47}[a-z0-9]{1}$";
|
||||
String expression = "'abcde-fghijklmn-o42pasdfasdfasdf.qrstuvwxyz10x.xx.yyy.zasdfasfd' matches '" + pattern + "'";
|
||||
evaluateAndCheckError(expression, SpelMessage.FLAWED_PATTERN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void relOperatorsMatches05() {
|
||||
evaluate("27 matches '^.*2.*$'", true, Boolean.class); // conversion int>string
|
||||
void matchesWithPatternLengthThreshold() {
|
||||
String pattern = "(0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
|
||||
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
|
||||
"01234567890123456789012345678901234567890123456789|abc)";
|
||||
assertThat(pattern).hasSize(256);
|
||||
Expression expr = parser.parseExpression("'abc' matches '" + pattern + "'");
|
||||
assertThat(expr.getValue(context, Boolean.class)).isTrue();
|
||||
|
||||
pattern += "?";
|
||||
assertThat(pattern).hasSize(257);
|
||||
evaluateAndCheckError("'abc' matches '" + pattern + "'", Boolean.class, SpelMessage.MAX_REGEX_LENGTH_EXCEEDED);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1448,7 +1458,9 @@ class EvaluationTests extends AbstractExpressionTests {
|
||||
private void expectFail(ExpressionParser parser, EvaluationContext eContext, String expressionString, SpelMessage messageCode) {
|
||||
assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> {
|
||||
Expression e = parser.parseExpression(expressionString);
|
||||
SpelUtilities.printAbstractSyntaxTree(System.out, e);
|
||||
if (DEBUG) {
|
||||
SpelUtilities.printAbstractSyntaxTree(System.out, e);
|
||||
}
|
||||
e.getValue(eContext);
|
||||
}).satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(messageCode));
|
||||
}
|
||||
|
||||
+65
-54
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,22 +21,25 @@ import java.math.BigInteger;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.ast.Operator;
|
||||
import org.springframework.expression.spel.standard.SpelExpression;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.expression.spel.SpelMessage.MAX_REPEATED_TEXT_SIZE_EXCEEDED;
|
||||
|
||||
/**
|
||||
* Tests the evaluation of expressions using relational operators.
|
||||
* Tests the evaluation of expressions using various operators.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @author Giovanni Dall'Oglio Risso
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class OperatorTests extends AbstractExpressionTests {
|
||||
|
||||
@Test
|
||||
void testEqual() {
|
||||
void equal() {
|
||||
evaluate("3 == 5", false, Boolean.class);
|
||||
evaluate("5 == 3", false, Boolean.class);
|
||||
evaluate("6 == 6", true, Boolean.class);
|
||||
@@ -85,7 +88,7 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNotEqual() {
|
||||
void notEqual() {
|
||||
evaluate("3 != 5", true, Boolean.class);
|
||||
evaluate("5 != 3", true, Boolean.class);
|
||||
evaluate("6 != 6", false, Boolean.class);
|
||||
@@ -134,7 +137,7 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLessThan() {
|
||||
void lessThan() {
|
||||
evaluate("5 < 5", false, Boolean.class);
|
||||
evaluate("3 < 5", true, Boolean.class);
|
||||
evaluate("5 < 3", false, Boolean.class);
|
||||
@@ -176,7 +179,7 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLessThanOrEqual() {
|
||||
void lessThanOrEqual() {
|
||||
evaluate("3 <= 5", true, Boolean.class);
|
||||
evaluate("5 <= 3", false, Boolean.class);
|
||||
evaluate("6 <= 6", true, Boolean.class);
|
||||
@@ -225,7 +228,7 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGreaterThan() {
|
||||
void greaterThan() {
|
||||
evaluate("3 > 5", false, Boolean.class);
|
||||
evaluate("5 > 3", true, Boolean.class);
|
||||
evaluate("3L > 5L", false, Boolean.class);
|
||||
@@ -266,7 +269,7 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGreaterThanOrEqual() {
|
||||
void greaterThanOrEqual() {
|
||||
evaluate("3 >= 5", false, Boolean.class);
|
||||
evaluate("5 >= 3", true, Boolean.class);
|
||||
evaluate("6 >= 6", true, Boolean.class);
|
||||
@@ -315,27 +318,22 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIntegerLiteral() {
|
||||
void integerLiteral() {
|
||||
evaluate("3", 3, Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRealLiteral() {
|
||||
void realLiteral() {
|
||||
evaluate("3.5", 3.5d, Double.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultiplyStringInt() {
|
||||
evaluate("'a' * 5", "aaaaa", String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultiplyDoubleDoubleGivesDouble() {
|
||||
void multiplyDoubleDoubleGivesDouble() {
|
||||
evaluate("3.0d * 5.0d", 15.0d, Double.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMixedOperandsBigDecimal() {
|
||||
void mixedOperandsBigDecimal() {
|
||||
evaluate("3 * new java.math.BigDecimal('5')", new BigDecimal("15"), BigDecimal.class);
|
||||
evaluate("3L * new java.math.BigDecimal('5')", new BigDecimal("15"), BigDecimal.class);
|
||||
evaluate("3.0d * new java.math.BigDecimal('5')", new BigDecimal("15.0"), BigDecimal.class);
|
||||
@@ -361,19 +359,19 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMathOperatorAdd02() {
|
||||
void mathOperatorAdd02() {
|
||||
evaluate("'hello' + ' ' + 'world'", "hello world", String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMathOperatorsInChains() {
|
||||
void mathOperatorsInChains() {
|
||||
evaluate("1+2+3",6,Integer.class);
|
||||
evaluate("2*3*4",24,Integer.class);
|
||||
evaluate("12-1-2",9,Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIntegerArithmetic() {
|
||||
void integerArithmetic() {
|
||||
evaluate("2 + 4", "6", Integer.class);
|
||||
evaluate("5 - 4", "1", Integer.class);
|
||||
evaluate("3 * 5", 15, Integer.class);
|
||||
@@ -388,7 +386,7 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPlus() {
|
||||
void plus() {
|
||||
evaluate("7 + 2", "9", Integer.class);
|
||||
evaluate("3.0f + 5.0f", 8.0f, Float.class);
|
||||
evaluate("3.0d + 5.0d", 8.0d, Double.class);
|
||||
@@ -400,44 +398,44 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
evaluate("null + 'ab'", "nullab", String.class);
|
||||
|
||||
// AST:
|
||||
SpelExpression expr = (SpelExpression)parser.parseExpression("+3");
|
||||
SpelExpression expr = (SpelExpression) parser.parseExpression("+3");
|
||||
assertThat(expr.toStringAST()).isEqualTo("+3");
|
||||
expr = (SpelExpression)parser.parseExpression("2+3");
|
||||
expr = (SpelExpression) parser.parseExpression("2+3");
|
||||
assertThat(expr.toStringAST()).isEqualTo("(2 + 3)");
|
||||
|
||||
// use as a unary operator
|
||||
evaluate("+5d",5d,Double.class);
|
||||
evaluate("+5L",5L,Long.class);
|
||||
evaluate("+5",5,Integer.class);
|
||||
evaluate("+new java.math.BigDecimal('5')", new BigDecimal("5"),BigDecimal.class);
|
||||
evaluateAndCheckError("+'abc'",SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
|
||||
evaluate("+5d", 5d, Double.class);
|
||||
evaluate("+5L", 5L, Long.class);
|
||||
evaluate("+5", 5, Integer.class);
|
||||
evaluate("+new java.math.BigDecimal('5')", new BigDecimal("5"), BigDecimal.class);
|
||||
evaluateAndCheckError("+'abc'", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
|
||||
|
||||
// string concatenation
|
||||
evaluate("'abc'+'def'","abcdef",String.class);
|
||||
evaluate("'abc'+'def'", "abcdef", String.class);
|
||||
|
||||
evaluate("5 + new Integer('37')",42,Integer.class);
|
||||
evaluate("5 + new Integer('37')", 42, Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMinus() {
|
||||
void minus() {
|
||||
evaluate("'c' - 2", "a", String.class);
|
||||
evaluate("3.0f - 5.0f", -2.0f, Float.class);
|
||||
evaluateAndCheckError("'ab' - 2", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
|
||||
evaluateAndCheckError("2-'ab'", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
|
||||
SpelExpression expr = (SpelExpression)parser.parseExpression("-3");
|
||||
SpelExpression expr = (SpelExpression) parser.parseExpression("-3");
|
||||
assertThat(expr.toStringAST()).isEqualTo("-3");
|
||||
expr = (SpelExpression)parser.parseExpression("2-3");
|
||||
expr = (SpelExpression) parser.parseExpression("2-3");
|
||||
assertThat(expr.toStringAST()).isEqualTo("(2 - 3)");
|
||||
|
||||
evaluate("-5d",-5d,Double.class);
|
||||
evaluate("-5L",-5L,Long.class);
|
||||
evaluate("-5d", -5d, Double.class);
|
||||
evaluate("-5L", -5L, Long.class);
|
||||
evaluate("-5", -5, Integer.class);
|
||||
evaluate("-new java.math.BigDecimal('5')", new BigDecimal("-5"),BigDecimal.class);
|
||||
evaluate("-new java.math.BigDecimal('5')", new BigDecimal("-5"), BigDecimal.class);
|
||||
evaluateAndCheckError("-'abc'", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testModulus() {
|
||||
void modulus() {
|
||||
evaluate("3%2",1,Integer.class);
|
||||
evaluate("3L%2L",1L,Long.class);
|
||||
evaluate("3.0f%2.0f",1f,Float.class);
|
||||
@@ -448,7 +446,7 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDivide() {
|
||||
void divide() {
|
||||
evaluate("3.0f / 5.0f", 0.6f, Float.class);
|
||||
evaluate("4L/2L",2L,Long.class);
|
||||
evaluate("3.0f div 5.0f", 0.6f, Float.class);
|
||||
@@ -461,17 +459,17 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMathOperatorDivide_ConvertToDouble() {
|
||||
void mathOperatorDivide_ConvertToDouble() {
|
||||
evaluateAndAskForReturnType("8/4", 2.0, Double.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMathOperatorDivide04_ConvertToFloat() {
|
||||
void mathOperatorDivide04_ConvertToFloat() {
|
||||
evaluateAndAskForReturnType("8/4", 2.0F, Float.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDoubles() {
|
||||
void doubles() {
|
||||
evaluate("3.0d == 5.0d", false, Boolean.class);
|
||||
evaluate("3.0d == 3.0d", true, Boolean.class);
|
||||
evaluate("3.0d != 5.0d", true, Boolean.class);
|
||||
@@ -484,7 +482,7 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBigDecimals() {
|
||||
void bigDecimals() {
|
||||
evaluate("3 + new java.math.BigDecimal('5')", new BigDecimal("8"), BigDecimal.class);
|
||||
evaluate("3 - new java.math.BigDecimal('5')", new BigDecimal("-2"), BigDecimal.class);
|
||||
evaluate("3 * new java.math.BigDecimal('5')", new BigDecimal("15"), BigDecimal.class);
|
||||
@@ -495,7 +493,7 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOperatorNames() {
|
||||
void operatorNames() {
|
||||
Operator node = getOperatorNode((SpelExpression)parser.parseExpression("1==3"));
|
||||
assertThat(node.getOperatorName()).isEqualTo("==");
|
||||
|
||||
@@ -534,22 +532,22 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOperatorOverloading() {
|
||||
void operatorOverloading() {
|
||||
evaluateAndCheckError("'a' * '2'", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
|
||||
evaluateAndCheckError("'a' ^ '2'", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPower() {
|
||||
evaluate("3^2",9,Integer.class);
|
||||
evaluate("3.0d^2.0d",9.0d,Double.class);
|
||||
evaluate("3L^2L",9L,Long.class);
|
||||
void power() {
|
||||
evaluate("3^2", 9, Integer.class);
|
||||
evaluate("3.0d^2.0d", 9.0d, Double.class);
|
||||
evaluate("3L^2L", 9L, Long.class);
|
||||
evaluate("(2^32)^2", 9223372036854775807L, Long.class);
|
||||
evaluate("new java.math.BigDecimal('5') ^ 3", new BigDecimal("125"), BigDecimal.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMixedOperands_FloatsAndDoubles() {
|
||||
void mixedOperands_FloatsAndDoubles() {
|
||||
evaluate("3.0d + 5.0f", 8.0d, Double.class);
|
||||
evaluate("3.0D - 5.0f", -2.0d, Double.class);
|
||||
evaluate("3.0f * 5.0d", 15.0d, Double.class);
|
||||
@@ -558,7 +556,7 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMixedOperands_DoublesAndInts() {
|
||||
void mixedOperands_DoublesAndInts() {
|
||||
evaluate("3.0d + 5", 8.0d, Double.class);
|
||||
evaluate("3.0D - 5", -2.0d, Double.class);
|
||||
evaluate("3.0f * 5", 15.0f, Float.class);
|
||||
@@ -569,7 +567,7 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStrings() {
|
||||
void strings() {
|
||||
evaluate("'abc' == 'abc'", true, Boolean.class);
|
||||
evaluate("'abc' == 'def'", false, Boolean.class);
|
||||
evaluate("'abc' != 'abc'", false, Boolean.class);
|
||||
@@ -577,7 +575,20 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLongs() {
|
||||
void stringRepeat() {
|
||||
evaluate("'abc' * 0", "", String.class);
|
||||
evaluate("'abc' * 1", "abc", String.class);
|
||||
evaluate("'abc' * 2", "abcabc", String.class);
|
||||
|
||||
Expression expr = parser.parseExpression("'a' * 256");
|
||||
assertThat(expr.getValue(context, String.class)).hasSize(256);
|
||||
|
||||
// 4 is the position of the '*' (repeat operator)
|
||||
evaluateAndCheckError("'a' * 257", String.class, MAX_REPEATED_TEXT_SIZE_EXCEEDED, 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void longs() {
|
||||
evaluate("3L == 4L", false, Boolean.class);
|
||||
evaluate("3L == 3L", true, Boolean.class);
|
||||
evaluate("3L != 4L", true, Boolean.class);
|
||||
@@ -588,7 +599,7 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBigIntegers() {
|
||||
void bigIntegers() {
|
||||
evaluate("3 + new java.math.BigInteger('5')", new BigInteger("8"), BigInteger.class);
|
||||
evaluate("3 - new java.math.BigInteger('5')", new BigInteger("-2"), BigInteger.class);
|
||||
evaluate("3 * new java.math.BigInteger('5')", new BigInteger("15"), BigInteger.class);
|
||||
@@ -619,7 +630,7 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
|
||||
public static class BaseComparable implements Comparable<BaseComparable> {
|
||||
static class BaseComparable implements Comparable<BaseComparable> {
|
||||
|
||||
@Override
|
||||
public int compareTo(BaseComparable other) {
|
||||
|
||||
+1
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -1817,7 +1817,6 @@ class SpelReproTests extends AbstractExpressionTests {
|
||||
static class CCC {
|
||||
|
||||
public boolean method(Object o) {
|
||||
System.out.println(o);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1889,7 +1888,6 @@ class SpelReproTests extends AbstractExpressionTests {
|
||||
static class Foo2 {
|
||||
|
||||
public void execute(String str) {
|
||||
System.out.println("Value: " + str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1964,7 +1962,6 @@ class SpelReproTests extends AbstractExpressionTests {
|
||||
public static class ReflectionUtil<T extends Number> {
|
||||
|
||||
public Object methodToCall(T param) {
|
||||
System.out.println(param + " " + param.getClass());
|
||||
return "Object methodToCall(T param)";
|
||||
}
|
||||
|
||||
|
||||
+19
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,13 +19,17 @@ package org.springframework.expression.spel.support;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.MethodExecutor;
|
||||
import org.springframework.expression.MethodResolver;
|
||||
import org.springframework.expression.ParseException;
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.expression.TypedValue;
|
||||
@@ -364,6 +368,20 @@ public class ReflectionHelperTests extends AbstractExpressionTests {
|
||||
field.write(ctx, tester, "field", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void reflectiveMethodResolverForJdkProxies() throws Exception {
|
||||
Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] { Runnable.class }, (p, m, args) -> null);
|
||||
|
||||
MethodResolver resolver = new ReflectiveMethodResolver();
|
||||
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
|
||||
|
||||
MethodExecutor bogus = resolver.resolve(evaluationContext, proxy, "bogus", Collections.emptyList());
|
||||
assertThat(bogus).as("MethodExecutor for bogus()").isNull();
|
||||
MethodExecutor toString = resolver.resolve(evaluationContext, proxy, "toString", Collections.emptyList());
|
||||
assertThat(toString).as("MethodExecutor for toString()").isNotNull();
|
||||
MethodExecutor hashCode = resolver.resolve(evaluationContext, proxy, "hashCode", Collections.emptyList());
|
||||
assertThat(hashCode).as("MethodExecutor for hashCode()").isNotNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to validate the match returned from a compareArguments call.
|
||||
|
||||
+74
-50
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -48,31 +48,42 @@ import org.springframework.util.StringUtils;
|
||||
/**
|
||||
* {@link RowMapper} implementation that converts a row into a new instance
|
||||
* of the specified mapped target class. The mapped target class must be a
|
||||
* top-level class and it must have a default or no-arg constructor.
|
||||
* top-level class or {@code static} nested class, and it must have a default or
|
||||
* no-arg constructor.
|
||||
*
|
||||
* <p>Column values are mapped based on matching the column name as obtained from result set
|
||||
* meta-data to public setters for the corresponding properties. The names are matched either
|
||||
* directly or by transforming a name separating the parts with underscores to the same name
|
||||
* using "camel" case.
|
||||
* <p>Column values are mapped based on matching the column name (as obtained from
|
||||
* result set meta-data) to public setters in the target class for the corresponding
|
||||
* properties. The names are matched either directly or by transforming a name
|
||||
* separating the parts with underscores to the same name using "camel" case.
|
||||
*
|
||||
* <p>Mapping is provided for fields in the target class for many common types, e.g.:
|
||||
* String, boolean, Boolean, byte, Byte, short, Short, int, Integer, long, Long,
|
||||
* float, Float, double, Double, BigDecimal, {@code java.util.Date}, etc.
|
||||
* <p>Mapping is provided for properties in the target class for many common types —
|
||||
* for example: String, boolean, Boolean, byte, Byte, short, Short, int, Integer,
|
||||
* long, Long, float, Float, double, Double, BigDecimal, {@code java.util.Date}, etc.
|
||||
*
|
||||
* <p>To facilitate mapping between columns and fields that don't have matching names,
|
||||
* try using column aliases in the SQL statement like "select fname as first_name from customer".
|
||||
* <p>To facilitate mapping between columns and properties that don't have matching
|
||||
* names, try using column aliases in the SQL statement like
|
||||
* {@code "select fname as first_name from customer"}, where {@code first_name}
|
||||
* can be mapped to a {@code setFirstName(String)} method in the target class.
|
||||
*
|
||||
* <p>For 'null' values read from the database, we will attempt to call the setter, but in the case of
|
||||
* Java primitives, this causes a TypeMismatchException. This class can be configured (using the
|
||||
* primitivesDefaultedForNullValue property) to trap this exception and use the primitives default value.
|
||||
* Be aware that if you use the values from the generated bean to update the database the primitive value
|
||||
* will have been set to the primitive's default value instead of null.
|
||||
* <p>For a {@code NULL} value read from the database, an attempt will be made to
|
||||
* call the corresponding setter method with {@code null}, but in the case of
|
||||
* Java primitives this will result in a {@link TypeMismatchException} by default.
|
||||
* To ignore {@code NULL} database values for all primitive properties in the
|
||||
* target class, set the {@code primitivesDefaultedForNullValue} flag to
|
||||
* {@code true}. See {@link #setPrimitivesDefaultedForNullValue(boolean)} for
|
||||
* details.
|
||||
*
|
||||
* <p>Please note that this class is designed to provide convenience rather than high performance.
|
||||
* For best performance, consider using a custom {@link RowMapper} implementation.
|
||||
* <p>If you need to map to a target class which has a <em>data class</em> constructor
|
||||
* — for example, a Java {@code record} or a Kotlin {@code data} class —
|
||||
* use {@link DataClassRowMapper} instead.
|
||||
*
|
||||
* <p>Please note that this class is designed to provide convenience rather than
|
||||
* high performance. For best performance, consider using a custom {@code RowMapper}
|
||||
* implementation.
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
* @param <T> the result type
|
||||
* @see DataClassRowMapper
|
||||
@@ -89,20 +100,24 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
|
||||
/** Whether we're strictly validating. */
|
||||
private boolean checkFullyPopulated = false;
|
||||
|
||||
/** Whether we're defaulting primitives when mapping a null value. */
|
||||
/**
|
||||
* Whether {@code NULL} database values should be ignored for primitive
|
||||
* properties in the target class.
|
||||
* @see #setPrimitivesDefaultedForNullValue(boolean)
|
||||
*/
|
||||
private boolean primitivesDefaultedForNullValue = false;
|
||||
|
||||
/** ConversionService for binding JDBC values to bean properties. */
|
||||
@Nullable
|
||||
private ConversionService conversionService = DefaultConversionService.getSharedInstance();
|
||||
|
||||
/** Map of the fields we provide mapping for. */
|
||||
/** Map of the properties we provide mapping for. */
|
||||
@Nullable
|
||||
private Map<String, PropertyDescriptor> mappedFields;
|
||||
private Map<String, PropertyDescriptor> mappedProperties;
|
||||
|
||||
/** Set of bean properties we provide mapping for. */
|
||||
/** Set of bean property names we provide mapping for. */
|
||||
@Nullable
|
||||
private Set<String> mappedProperties;
|
||||
private Set<String> mappedPropertyNames;
|
||||
|
||||
|
||||
/**
|
||||
@@ -126,7 +141,7 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
|
||||
* Create a new {@code BeanPropertyRowMapper}.
|
||||
* @param mappedClass the class that each row should be mapped to
|
||||
* @param checkFullyPopulated whether we're strictly validating that
|
||||
* all bean properties have been mapped from corresponding database fields
|
||||
* all bean properties have been mapped from corresponding database columns
|
||||
*/
|
||||
public BeanPropertyRowMapper(Class<T> mappedClass, boolean checkFullyPopulated) {
|
||||
initialize(mappedClass);
|
||||
@@ -159,7 +174,7 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
|
||||
|
||||
/**
|
||||
* Set whether we're strictly validating that all bean properties have been mapped
|
||||
* from corresponding database fields.
|
||||
* from corresponding database columns.
|
||||
* <p>Default is {@code false}, accepting unpopulated properties in the target bean.
|
||||
*/
|
||||
public void setCheckFullyPopulated(boolean checkFullyPopulated) {
|
||||
@@ -168,24 +183,33 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
|
||||
|
||||
/**
|
||||
* Return whether we're strictly validating that all bean properties have been
|
||||
* mapped from corresponding database fields.
|
||||
* mapped from corresponding database columns.
|
||||
*/
|
||||
public boolean isCheckFullyPopulated() {
|
||||
return this.checkFullyPopulated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether we're defaulting Java primitives in the case of mapping a null value
|
||||
* from corresponding database fields.
|
||||
* <p>Default is {@code false}, throwing an exception when nulls are mapped to Java primitives.
|
||||
* Set whether a {@code NULL} database column value should be ignored when
|
||||
* mapping to a corresponding primitive property in the target class.
|
||||
* <p>Default is {@code false}, throwing an exception when nulls are mapped
|
||||
* to Java primitives.
|
||||
* <p>If this flag is set to {@code true} and you use an <em>ignored</em>
|
||||
* primitive property value from the mapped bean to update the database, the
|
||||
* value in the database will be changed from {@code NULL} to the current value
|
||||
* of that primitive property. That value may be the property's initial value
|
||||
* (potentially Java's default value for the respective primitive type), or
|
||||
* it may be some other value set for the property in the default constructor
|
||||
* (or initialization block) or as a side effect of setting some other property
|
||||
* in the mapped bean.
|
||||
*/
|
||||
public void setPrimitivesDefaultedForNullValue(boolean primitivesDefaultedForNullValue) {
|
||||
this.primitivesDefaultedForNullValue = primitivesDefaultedForNullValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether we're defaulting Java primitives in the case of mapping a null value
|
||||
* from corresponding database fields.
|
||||
* Get the value of the {@code primitivesDefaultedForNullValue} flag.
|
||||
* @see #setPrimitivesDefaultedForNullValue(boolean)
|
||||
*/
|
||||
public boolean isPrimitivesDefaultedForNullValue() {
|
||||
return this.primitivesDefaultedForNullValue;
|
||||
@@ -220,31 +244,31 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
|
||||
*/
|
||||
protected void initialize(Class<T> mappedClass) {
|
||||
this.mappedClass = mappedClass;
|
||||
this.mappedFields = new HashMap<>();
|
||||
this.mappedProperties = new HashSet<>();
|
||||
this.mappedProperties = new HashMap<>();
|
||||
this.mappedPropertyNames = new HashSet<>();
|
||||
|
||||
for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(mappedClass)) {
|
||||
if (pd.getWriteMethod() != null) {
|
||||
String lowerCaseName = lowerCaseName(pd.getName());
|
||||
this.mappedFields.put(lowerCaseName, pd);
|
||||
this.mappedProperties.put(lowerCaseName, pd);
|
||||
String underscoreName = underscoreName(pd.getName());
|
||||
if (!lowerCaseName.equals(underscoreName)) {
|
||||
this.mappedFields.put(underscoreName, pd);
|
||||
this.mappedProperties.put(underscoreName, pd);
|
||||
}
|
||||
this.mappedProperties.add(pd.getName());
|
||||
this.mappedPropertyNames.add(pd.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified property from the mapped fields.
|
||||
* Remove the specified property from the mapped properties.
|
||||
* @param propertyName the property name (as used by property descriptors)
|
||||
* @since 5.3.9
|
||||
*/
|
||||
protected void suppressProperty(String propertyName) {
|
||||
if (this.mappedFields != null) {
|
||||
this.mappedFields.remove(lowerCaseName(propertyName));
|
||||
this.mappedFields.remove(underscoreName(propertyName));
|
||||
if (this.mappedProperties != null) {
|
||||
this.mappedProperties.remove(lowerCaseName(propertyName));
|
||||
this.mappedProperties.remove(underscoreName(propertyName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,8 +330,8 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
|
||||
|
||||
for (int index = 1; index <= columnCount; index++) {
|
||||
String column = JdbcUtils.lookupColumnName(rsmd, index);
|
||||
String field = lowerCaseName(StringUtils.delete(column, " "));
|
||||
PropertyDescriptor pd = (this.mappedFields != null ? this.mappedFields.get(field) : null);
|
||||
String property = lowerCaseName(StringUtils.delete(column, " "));
|
||||
PropertyDescriptor pd = (this.mappedProperties != null ? this.mappedProperties.get(property) : null);
|
||||
if (pd != null) {
|
||||
try {
|
||||
Object value = getColumnValue(rs, index, pd);
|
||||
@@ -321,11 +345,11 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
|
||||
catch (TypeMismatchException ex) {
|
||||
if (value == null && this.primitivesDefaultedForNullValue) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Intercepted TypeMismatchException for row " + rowNumber +
|
||||
" and column '" + column + "' with null value when setting property '" +
|
||||
pd.getName() + "' of type '" +
|
||||
ClassUtils.getQualifiedName(pd.getPropertyType()) +
|
||||
"' on object: " + mappedObject, ex);
|
||||
String propertyType = ClassUtils.getQualifiedName(pd.getPropertyType());
|
||||
logger.debug(String.format(
|
||||
"Ignoring intercepted TypeMismatchException for row %d and column '%s' " +
|
||||
"with null value when setting property '%s' of type '%s' on object: %s",
|
||||
rowNumber, column, pd.getName(), propertyType, mappedObject), ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -343,9 +367,9 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
|
||||
}
|
||||
}
|
||||
|
||||
if (populatedProperties != null && !populatedProperties.equals(this.mappedProperties)) {
|
||||
throw new InvalidDataAccessApiUsageException("Given ResultSet does not contain all fields " +
|
||||
"necessary to populate object of " + this.mappedClass + ": " + this.mappedProperties);
|
||||
if (populatedProperties != null && !populatedProperties.equals(this.mappedPropertyNames)) {
|
||||
throw new InvalidDataAccessApiUsageException("Given ResultSet does not contain all properties " +
|
||||
"necessary to populate object of " + this.mappedClass + ": " + this.mappedPropertyNames);
|
||||
}
|
||||
|
||||
return mappedObject;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,15 +31,30 @@ import org.springframework.util.Assert;
|
||||
/**
|
||||
* {@link RowMapper} implementation that converts a row into a new instance
|
||||
* of the specified mapped target class. The mapped target class must be a
|
||||
* top-level class and may either expose a data class constructor with named
|
||||
* parameters corresponding to column names or classic bean property setters
|
||||
* (or even a combination of both).
|
||||
* top-level class or {@code static} nested class, and it may expose either a
|
||||
* <em>data class</em> constructor with named parameters corresponding to column
|
||||
* names or classic bean property setter methods with property names corresponding
|
||||
* to column names (or even a combination of both).
|
||||
*
|
||||
* <p>The term "data class" applies to Java <em>records</em>, Kotlin <em>data
|
||||
* classes</em>, and any class which has a constructor with named parameters
|
||||
* that are intended to be mapped to corresponding column names.
|
||||
*
|
||||
* <p>When combining a data class constructor with setter methods, any property
|
||||
* mapped successfully via a constructor argument will not be mapped additionally
|
||||
* via a corresponding setter method. This means that constructor arguments take
|
||||
* precedence over property setter methods.
|
||||
*
|
||||
* <p>Note that this class extends {@link BeanPropertyRowMapper} and can
|
||||
* therefore serve as a common choice for any mapped target class, flexibly
|
||||
* adapting to constructor style versus setter methods in the mapped class.
|
||||
*
|
||||
* <p>Please note that this class is designed to provide convenience rather than
|
||||
* high performance. For best performance, consider using a custom {@code RowMapper}
|
||||
* implementation.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 5.3
|
||||
* @param <T> the result type
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,21 +24,21 @@ import org.springframework.lang.Nullable;
|
||||
/**
|
||||
* An interface used by {@link JdbcTemplate} for mapping rows of a
|
||||
* {@link java.sql.ResultSet} on a per-row basis. Implementations of this
|
||||
* interface perform the actual work of mapping each row to a result object,
|
||||
* interface perform the actual work of mapping each row to a result object
|
||||
* but don't need to worry about exception handling.
|
||||
* {@link java.sql.SQLException SQLExceptions} will be caught and handled
|
||||
* by the calling JdbcTemplate.
|
||||
* by the calling {@code JdbcTemplate}.
|
||||
*
|
||||
* <p>Typically used either for {@link JdbcTemplate}'s query methods
|
||||
* or for out parameters of stored procedures. RowMapper objects are
|
||||
* <p>Typically used either for {@code JdbcTemplate}'s query methods or for
|
||||
* {@code out} parameters of stored procedures. {@code RowMapper} objects are
|
||||
* typically stateless and thus reusable; they are an ideal choice for
|
||||
* implementing row-mapping logic in a single place.
|
||||
*
|
||||
* <p>Alternatively, consider subclassing
|
||||
* {@link org.springframework.jdbc.object.MappingSqlQuery} from the
|
||||
* {@code jdbc.object} package: Instead of working with separate
|
||||
* JdbcTemplate and RowMapper objects, you can build executable query
|
||||
* objects (containing row-mapping logic) in that style.
|
||||
* {@code jdbc.object} package: instead of working with separate
|
||||
* {@code JdbcTemplate} and {@code RowMapper} objects, you can build executable
|
||||
* query objects (containing row-mapping logic) in that style.
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Juergen Hoeller
|
||||
@@ -52,13 +52,13 @@ import org.springframework.lang.Nullable;
|
||||
public interface RowMapper<T> {
|
||||
|
||||
/**
|
||||
* Implementations must implement this method to map each row of data
|
||||
* in the ResultSet. This method should not call {@code next()} on
|
||||
* the ResultSet; it is only supposed to map values of the current row.
|
||||
* @param rs the ResultSet to map (pre-initialized for the current row)
|
||||
* Implementations must implement this method to map each row of data in the
|
||||
* {@code ResultSet}. This method should not call {@code next()} on the
|
||||
* {@code ResultSet}; it is only supposed to map values of the current row.
|
||||
* @param rs the {@code ResultSet} to map (pre-initialized for the current row)
|
||||
* @param rowNum the number of the current row
|
||||
* @return the result object for the current row (may be {@code null})
|
||||
* @throws SQLException if an SQLException is encountered getting
|
||||
* @throws SQLException if an SQLException is encountered while getting
|
||||
* column values (that is, there's no need to catch SQLException)
|
||||
*/
|
||||
@Nullable
|
||||
|
||||
+37
-34
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.springframework.jdbc.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
@@ -44,12 +42,15 @@ import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
*/
|
||||
class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
|
||||
|
||||
private static final String SELECT_NULL_AS_AGE = "select null as age from people";
|
||||
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
void overridingDifferentClassDefinedForMapping() {
|
||||
BeanPropertyRowMapper mapper = new BeanPropertyRowMapper(Person.class);
|
||||
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() ->
|
||||
mapper.setMappedClass(Long.class));
|
||||
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
|
||||
.isThrownBy(() -> mapper.setMappedClass(Long.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -61,104 +62,106 @@ class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
|
||||
@Test
|
||||
void staticQueryWithRowMapper() throws Exception {
|
||||
Mock mock = new Mock();
|
||||
List<Person> result = mock.getJdbcTemplate().query(
|
||||
Person person = mock.getJdbcTemplate().queryForObject(
|
||||
"select name, age, birth_date, balance from people",
|
||||
new BeanPropertyRowMapper<>(Person.class));
|
||||
assertThat(result).hasSize(1);
|
||||
verifyPerson(result.get(0));
|
||||
verifyPerson(person);
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void mappingWithInheritance() throws Exception {
|
||||
Mock mock = new Mock();
|
||||
List<ConcretePerson> result = mock.getJdbcTemplate().query(
|
||||
ConcretePerson person = mock.getJdbcTemplate().queryForObject(
|
||||
"select name, age, birth_date, balance from people",
|
||||
new BeanPropertyRowMapper<>(ConcretePerson.class));
|
||||
assertThat(result).hasSize(1);
|
||||
verifyPerson(result.get(0));
|
||||
verifyPerson(person);
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void mappingWithNoUnpopulatedFieldsFound() throws Exception {
|
||||
Mock mock = new Mock();
|
||||
List<ConcretePerson> result = mock.getJdbcTemplate().query(
|
||||
ConcretePerson person = mock.getJdbcTemplate().queryForObject(
|
||||
"select name, age, birth_date, balance from people",
|
||||
new BeanPropertyRowMapper<>(ConcretePerson.class, true));
|
||||
assertThat(result).hasSize(1);
|
||||
verifyPerson(result.get(0));
|
||||
verifyPerson(person);
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void mappingWithUnpopulatedFieldsNotChecked() throws Exception {
|
||||
Mock mock = new Mock();
|
||||
List<ExtendedPerson> result = mock.getJdbcTemplate().query(
|
||||
ExtendedPerson person = mock.getJdbcTemplate().queryForObject(
|
||||
"select name, age, birth_date, balance from people",
|
||||
new BeanPropertyRowMapper<>(ExtendedPerson.class));
|
||||
assertThat(result).hasSize(1);
|
||||
verifyPerson(result.get(0));
|
||||
verifyPerson(person);
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void mappingWithUnpopulatedFieldsNotAccepted() throws Exception {
|
||||
BeanPropertyRowMapper<ExtendedPerson> mapper = new BeanPropertyRowMapper<>(ExtendedPerson.class, true);
|
||||
Mock mock = new Mock();
|
||||
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() ->
|
||||
mock.getJdbcTemplate().query("select name, age, birth_date, balance from people",
|
||||
new BeanPropertyRowMapper<>(ExtendedPerson.class, true)));
|
||||
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
|
||||
.isThrownBy(() -> mock.getJdbcTemplate().query("select name, age, birth_date, balance from people", mapper));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mappingNullValue() throws Exception {
|
||||
BeanPropertyRowMapper<Person> mapper = new BeanPropertyRowMapper<>(Person.class);
|
||||
Mock mock = new Mock(MockType.TWO);
|
||||
assertThatExceptionOfType(TypeMismatchException.class).isThrownBy(() ->
|
||||
mock.getJdbcTemplate().query("select name, null as age, birth_date, balance from people", mapper));
|
||||
assertThatExceptionOfType(TypeMismatchException.class)
|
||||
.isThrownBy(() -> mock.getJdbcTemplate().query(SELECT_NULL_AS_AGE, mapper));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mappingNullValueWithPrimitivesDefaultedForNullValue() throws Exception {
|
||||
BeanPropertyRowMapper<Person> mapper = new BeanPropertyRowMapper<>(Person.class);
|
||||
mapper.setPrimitivesDefaultedForNullValue(true);
|
||||
Mock mock = new Mock(MockType.TWO);
|
||||
Person person = mock.getJdbcTemplate().queryForObject(SELECT_NULL_AS_AGE, mapper);
|
||||
assertThat(person).extracting(Person::getAge).isEqualTo(42L);
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryWithSpaceInColumnNameAndLocalDateTime() throws Exception {
|
||||
Mock mock = new Mock(MockType.THREE);
|
||||
List<SpacePerson> result = mock.getJdbcTemplate().query(
|
||||
SpacePerson person = mock.getJdbcTemplate().queryForObject(
|
||||
"select last_name as \"Last Name\", age, birth_date, balance from people",
|
||||
new BeanPropertyRowMapper<>(SpacePerson.class));
|
||||
assertThat(result).hasSize(1);
|
||||
verifyPerson(result.get(0));
|
||||
verifyPerson(person);
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryWithSpaceInColumnNameAndLocalDate() throws Exception {
|
||||
Mock mock = new Mock(MockType.THREE);
|
||||
List<DatePerson> result = mock.getJdbcTemplate().query(
|
||||
DatePerson person = mock.getJdbcTemplate().queryForObject(
|
||||
"select last_name as \"Last Name\", age, birth_date, balance from people",
|
||||
new BeanPropertyRowMapper<>(DatePerson.class));
|
||||
assertThat(result).hasSize(1);
|
||||
verifyPerson(result.get(0));
|
||||
verifyPerson(person);
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryWithDirectNameMatchOnBirthDate() throws Exception {
|
||||
Mock mock = new Mock(MockType.FOUR);
|
||||
List<ConcretePerson> result = mock.getJdbcTemplate().query(
|
||||
ConcretePerson person = mock.getJdbcTemplate().queryForObject(
|
||||
"select name, age, birthdate, balance from people",
|
||||
new BeanPropertyRowMapper<>(ConcretePerson.class));
|
||||
assertThat(result).hasSize(1);
|
||||
verifyPerson(result.get(0));
|
||||
verifyPerson(person);
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryWithUnderscoreInColumnNameAndPersonWithMultipleAdjacentUppercaseLettersInPropertyName() throws Exception {
|
||||
Mock mock = new Mock();
|
||||
List<EmailPerson> result = mock.getJdbcTemplate().query(
|
||||
EmailPerson person = mock.getJdbcTemplate().queryForObject(
|
||||
"select name, age, birth_date, balance, e_mail from people",
|
||||
new BeanPropertyRowMapper<>(EmailPerson.class));
|
||||
assertThat(result).hasSize(1);
|
||||
verifyPerson(result.get(0));
|
||||
verifyPerson(person);
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
|
||||
+15
-19
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,9 +17,7 @@
|
||||
package org.springframework.jdbc.core;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -30,50 +28,48 @@ import org.springframework.jdbc.core.test.ConstructorPersonWithSetters;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link DataClassRowMapper}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 5.3
|
||||
*/
|
||||
public class DataClassRowMapperTests extends AbstractRowMapperTests {
|
||||
class DataClassRowMapperTests extends AbstractRowMapperTests {
|
||||
|
||||
@Test
|
||||
public void testStaticQueryWithDataClass() throws Exception {
|
||||
void staticQueryWithDataClass() throws Exception {
|
||||
Mock mock = new Mock();
|
||||
List<ConstructorPerson> result = mock.getJdbcTemplate().query(
|
||||
ConstructorPerson person = mock.getJdbcTemplate().queryForObject(
|
||||
"select name, age, birth_date, balance from people",
|
||||
new DataClassRowMapper<>(ConstructorPerson.class));
|
||||
assertThat(result.size()).isEqualTo(1);
|
||||
verifyPerson(result.get(0));
|
||||
verifyPerson(person);
|
||||
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaticQueryWithDataClassAndGenerics() throws Exception {
|
||||
void staticQueryWithDataClassAndGenerics() throws Exception {
|
||||
Mock mock = new Mock();
|
||||
List<ConstructorPersonWithGenerics> result = mock.getJdbcTemplate().query(
|
||||
ConstructorPersonWithGenerics person = mock.getJdbcTemplate().queryForObject(
|
||||
"select name, age, birth_date, balance from people",
|
||||
new DataClassRowMapper<>(ConstructorPersonWithGenerics.class));
|
||||
assertThat(result.size()).isEqualTo(1);
|
||||
ConstructorPersonWithGenerics person = result.get(0);
|
||||
assertThat(person.name()).isEqualTo("Bubba");
|
||||
assertThat(person.age()).isEqualTo(22L);
|
||||
assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
|
||||
assertThat(person.balance()).isEqualTo(Collections.singletonList(new BigDecimal("1234.56")));
|
||||
assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new Date(1221222L));
|
||||
assertThat(person.balance()).containsExactly(new BigDecimal("1234.56"));
|
||||
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStaticQueryWithDataClassAndSetters() throws Exception {
|
||||
void staticQueryWithDataClassAndSetters() throws Exception {
|
||||
Mock mock = new Mock(MockType.FOUR);
|
||||
List<ConstructorPersonWithSetters> result = mock.getJdbcTemplate().query(
|
||||
ConstructorPersonWithSetters person = mock.getJdbcTemplate().queryForObject(
|
||||
"select name, age, birthdate, balance from people",
|
||||
new DataClassRowMapper<>(ConstructorPersonWithSetters.class));
|
||||
assertThat(result.size()).isEqualTo(1);
|
||||
ConstructorPersonWithSetters person = result.get(0);
|
||||
assertThat(person.name()).isEqualTo("BUBBA");
|
||||
assertThat(person.age()).isEqualTo(22L);
|
||||
assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
|
||||
assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new Date(1221222L));
|
||||
assertThat(person.balance()).isEqualTo(new BigDecimal("1234.56"));
|
||||
|
||||
mock.verifyClosed();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,6 +31,9 @@ public class Person {
|
||||
|
||||
private BigDecimal balance;
|
||||
|
||||
public Person() {
|
||||
this.age = 42; // custom "default" value for a primitive
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -76,6 +76,7 @@ import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
* <em>composed annotations</em> with attribute overrides.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Sam Brannen
|
||||
* @since 4.1
|
||||
* @see EnableJms
|
||||
* @see JmsListenerAnnotationBeanPostProcessor
|
||||
@@ -110,6 +111,12 @@ public @interface JmsListener {
|
||||
|
||||
/**
|
||||
* The name for the durable subscription, if any.
|
||||
* <p>As of Spring Framework 5.3.26, if an explicit subscription name is not
|
||||
* specified, a default subscription name will be generated based on the fully
|
||||
* qualified name of the annotated listener method — for example,
|
||||
* {@code "org.example.jms.ProductListener.processRequest"} for a
|
||||
* {@code processRequest(...)} listener method in the
|
||||
* {@code org.example.jms.ProductListener} class.
|
||||
*/
|
||||
String subscription() default "";
|
||||
|
||||
|
||||
+21
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -347,8 +347,26 @@ public class SingleConnectionFactory implements ConnectionFactory, QueueConnecti
|
||||
if (this.connection != null) {
|
||||
closeConnection(this.connection);
|
||||
}
|
||||
this.connection = doCreateConnection();
|
||||
prepareConnection(this.connection);
|
||||
// Create new (method local) connection, which is later assigned to instance connection
|
||||
// - prevention to hold instance connection without exception listener, in case when
|
||||
// some subsequent methods (after creation of connection) throws JMSException
|
||||
Connection con = doCreateConnection();
|
||||
try {
|
||||
prepareConnection(con);
|
||||
this.connection = con;
|
||||
}
|
||||
catch (JMSException ex) {
|
||||
// Attempt to close new (not used) connection to release possible resources
|
||||
try {
|
||||
con.close();
|
||||
}
|
||||
catch(Throwable th) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Could not close newly obtained JMS Connection that failed to prepare", th);
|
||||
}
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
if (this.startedCount > 0) {
|
||||
this.connection.start();
|
||||
}
|
||||
|
||||
+46
-15
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,6 +20,7 @@ import javax.jms.JMSException;
|
||||
import javax.jms.Session;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.jms.listener.SubscriptionNameProvider;
|
||||
import org.springframework.jms.support.JmsHeaderMapper;
|
||||
import org.springframework.jms.support.converter.MessageConversionException;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -34,7 +35,7 @@ import org.springframework.util.Assert;
|
||||
* A {@link javax.jms.MessageListener} adapter that invokes a configurable
|
||||
* {@link InvocableHandlerMethod}.
|
||||
*
|
||||
* <p>Wraps the incoming {@link javax.jms.Message} to Spring's {@link Message}
|
||||
* <p>Wraps the incoming {@link javax.jms.Message} in Spring's {@link Message}
|
||||
* abstraction, copying the JMS standard headers using a configurable
|
||||
* {@link JmsHeaderMapper}.
|
||||
*
|
||||
@@ -42,13 +43,19 @@ import org.springframework.util.Assert;
|
||||
* are provided as additional arguments so that these can be injected as
|
||||
* method arguments if necessary.
|
||||
*
|
||||
* <p>As of Spring Framework 5.3.26, {@code MessagingMessageListenerAdapter} implements
|
||||
* {@link SubscriptionNameProvider} in order to provide a meaningful default
|
||||
* subscription name. See {@link #getSubscriptionName()} for details.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Sam Brannen
|
||||
* @since 4.1
|
||||
* @see Message
|
||||
* @see JmsHeaderMapper
|
||||
* @see InvocableHandlerMethod
|
||||
*/
|
||||
public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageListener {
|
||||
public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageListener
|
||||
implements SubscriptionNameProvider {
|
||||
|
||||
@Nullable
|
||||
private InvocableHandlerMethod handlerMethod;
|
||||
@@ -83,17 +90,6 @@ public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageLis
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object preProcessResponse(Object result) {
|
||||
MethodParameter returnType = getHandlerMethod().getReturnType();
|
||||
if (result instanceof Message) {
|
||||
return MessageBuilder.fromMessage((Message<?>) result)
|
||||
.setHeader(AbstractMessageSendingTemplate.CONVERSION_HINT_HEADER, returnType).build();
|
||||
}
|
||||
return MessageBuilder.withPayload(result).setHeader(
|
||||
AbstractMessageSendingTemplate.CONVERSION_HINT_HEADER, returnType).build();
|
||||
}
|
||||
|
||||
protected Message<?> toMessagingMessage(javax.jms.Message jmsMessage) {
|
||||
try {
|
||||
return (Message<?>) getMessagingMessageConverter().fromMessage(jmsMessage);
|
||||
@@ -104,7 +100,7 @@ public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageLis
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the handler, wrapping any exception to a {@link ListenerExecutionFailedException}
|
||||
* Invoke the handler, wrapping any exception in a {@link ListenerExecutionFailedException}
|
||||
* with a dedicated error message.
|
||||
*/
|
||||
@Nullable
|
||||
@@ -132,4 +128,39 @@ public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageLis
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object preProcessResponse(Object result) {
|
||||
MethodParameter returnType = getHandlerMethod().getReturnType();
|
||||
MessageBuilder<?> messageBuilder = (result instanceof Message ?
|
||||
MessageBuilder.fromMessage((Message<?>) result) :
|
||||
MessageBuilder.withPayload(result));
|
||||
return messageBuilder
|
||||
.setHeader(AbstractMessageSendingTemplate.CONVERSION_HINT_HEADER, returnType)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a subscription name for this {@code MessageListener} adapter based
|
||||
* on the following rules.
|
||||
* <ul>
|
||||
* <li>If the {@link #setHandlerMethod(InvocableHandlerMethod) handlerMethod}
|
||||
* has been set, the generated subscription name takes the form of
|
||||
* {@code handlerMethod.getBeanType().getName() + "." + handlerMethod.getMethod().getName()}.</li>
|
||||
* <li>Otherwise, the generated subscription name is the result of invoking
|
||||
* {@code getClass().getName()}, which aligns with the default behavior of
|
||||
* {@link org.springframework.jms.listener.AbstractMessageListenerContainer}.</li>
|
||||
* </ul>
|
||||
* @since 5.3.26
|
||||
* @see SubscriptionNameProvider#getSubscriptionName()
|
||||
*/
|
||||
@Override
|
||||
public String getSubscriptionName() {
|
||||
if (this.handlerMethod != null) {
|
||||
return this.handlerMethod.getBeanType().getName() + "." + this.handlerMethod.getMethod().getName();
|
||||
}
|
||||
else {
|
||||
return getClass().getName();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+77
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.jms.connection;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.ConnectionFactory;
|
||||
import javax.jms.ExceptionListener;
|
||||
@@ -30,7 +33,10 @@ import javax.jms.TopicSession;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
@@ -342,6 +348,76 @@ public class SingleConnectionFactoryTests {
|
||||
assertThat(listener.getCount()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithConnectionFactoryAndExceptionListenerAndReconnectOnExceptionWithJMSException() throws Exception {
|
||||
// Throws JMSException on setExceptionListener() method, but only at the first time
|
||||
class FailingTestConnection extends TestConnection {
|
||||
private int setExceptionListenerInvocationCounter;
|
||||
|
||||
@Override
|
||||
public void setExceptionListener(ExceptionListener exceptionListener) throws JMSException {
|
||||
setExceptionListenerInvocationCounter++;
|
||||
// Throw JMSException on first invocation
|
||||
if (setExceptionListenerInvocationCounter == 1) {
|
||||
throw new JMSException("Test JMSException (setExceptionListener())");
|
||||
}
|
||||
super.setExceptionListener(exceptionListener);
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare base JMS ConnectionFactory
|
||||
// - createConnection(1st) -> TestConnection,
|
||||
// - createConnection(2nd and next) -> FailingTestConnection
|
||||
TestConnection testCon = new TestConnection();
|
||||
FailingTestConnection failingCon = new FailingTestConnection();
|
||||
AtomicInteger createConnectionMethodCounter = new AtomicInteger();
|
||||
ConnectionFactory cf = mock(ConnectionFactory.class);
|
||||
given(cf.createConnection()).willAnswer(invocation -> {
|
||||
int methodInvocationCounter = createConnectionMethodCounter.incrementAndGet();
|
||||
return methodInvocationCounter == 1 ? testCon : failingCon;
|
||||
});
|
||||
|
||||
// Prepare SingleConnectionFactory (setReconnectOnException())
|
||||
// - internal connection exception listener should be registered
|
||||
SingleConnectionFactory scf = new SingleConnectionFactory(cf);
|
||||
scf.setReconnectOnException(true);
|
||||
Field conField = ReflectionUtils.findField(SingleConnectionFactory.class, "connection");
|
||||
conField.setAccessible(true);
|
||||
|
||||
// Get connection (1st)
|
||||
Connection con1 = scf.getConnection();
|
||||
assertThat(createConnectionMethodCounter.get()).isEqualTo(1);
|
||||
assertThat(con1).isNotNull();
|
||||
assertThat(con1.getExceptionListener()).isNotNull();
|
||||
assertThat(con1).isSameAs(testCon);
|
||||
// Get connection again, the same should be returned (shared connection till some problem)
|
||||
Connection con2 = scf.getConnection();
|
||||
assertThat(createConnectionMethodCounter.get()).isEqualTo(1);
|
||||
assertThat(con2.getExceptionListener()).isNotNull();
|
||||
assertThat(con2).isSameAs(con1);
|
||||
|
||||
// Invoke reset connection to simulate problem with connection
|
||||
// - SCF exception listener should be invoked -> connection should be set to null
|
||||
// - next attempt to invoke getConnection() must create new connection
|
||||
scf.resetConnection();
|
||||
assertThat(conField.get(scf)).isNull();
|
||||
|
||||
// Attempt to get connection again
|
||||
// - JMSException should be returned from FailingTestConnection
|
||||
// - connection should be still null (no new connection without exception listener like before fix)
|
||||
assertThatExceptionOfType(JMSException.class).isThrownBy(() -> scf.getConnection());
|
||||
assertThat(createConnectionMethodCounter.get()).isEqualTo(2);
|
||||
assertThat(conField.get(scf)).isNull();
|
||||
|
||||
// Attempt to get connection again -> FailingTestConnection should be returned
|
||||
// - no JMSException is thrown, exception listener should be present
|
||||
Connection con3 = scf.getConnection();
|
||||
assertThat(createConnectionMethodCounter.get()).isEqualTo(3);
|
||||
assertThat(con3).isNotNull();
|
||||
assertThat(con3).isSameAs(failingCon);
|
||||
assertThat(con3.getExceptionListener()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithConnectionFactoryAndLocalExceptionListenerWithCleanup() throws JMSException {
|
||||
ConnectionFactory cf = mock(ConnectionFactory.class);
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.jms.listener.adapter;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import org.springframework.jms.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Named.named;
|
||||
import static org.junit.jupiter.params.provider.Arguments.arguments;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link MessagingMessageListenerAdapter}.
|
||||
*
|
||||
* <p>These tests are similar to those in {@link MessagingMessageListenerAdapterTests},
|
||||
* except that these tests have a different scope and do not use mocks.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 5.3.26
|
||||
* @see MessagingMessageListenerAdapterTests
|
||||
*/
|
||||
class MessagingMessageListenerAdapterIntegrationTests {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("subscriptionNames")
|
||||
void defaultSubscriptionName(Method method, String subscriptionName) {
|
||||
MessagingMessageListenerAdapter messageListenerAdaptor = new MessagingMessageListenerAdapter();
|
||||
InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(new CustomListener(), method);
|
||||
messageListenerAdaptor.setHandlerMethod(handlerMethod);
|
||||
|
||||
SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer();
|
||||
assertThat(listenerContainer.getSubscriptionName()).isNull();
|
||||
|
||||
listenerContainer.setMessageListener(messageListenerAdaptor);
|
||||
assertThat(listenerContainer.getSubscriptionName()).isEqualTo(subscriptionName);
|
||||
}
|
||||
|
||||
|
||||
private static Stream<Arguments> subscriptionNames() {
|
||||
String method1 = "toUpperCase";
|
||||
String method2 = "toUpperCase(java.lang.String)";
|
||||
String method3 = "toUpperCase(java.lang.String,int)";
|
||||
String method4 = "toUpperCase(java.lang.String[])";
|
||||
String expectedName = CustomListener.class.getName() + ".toUpperCase";
|
||||
return Stream.of(
|
||||
arguments(named(method1, findMethod()), expectedName),
|
||||
arguments(named(method2, findMethod(String.class)), expectedName),
|
||||
arguments(named(method3, findMethod(String.class, String.class)), expectedName),
|
||||
arguments(named(method4, findMethod(byte[].class)), expectedName));
|
||||
}
|
||||
|
||||
private static Method findMethod(Class<?>... paramTypes) {
|
||||
return ReflectionUtils.findMethod(CustomListener.class, "toUpperCase", paramTypes);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class CustomListener {
|
||||
|
||||
// @JmsListener(...)
|
||||
String toUpperCase() {
|
||||
return "ENIGMA";
|
||||
}
|
||||
|
||||
// @JmsListener(...)
|
||||
String toUpperCase(String input) {
|
||||
return "ENIGMA";
|
||||
}
|
||||
|
||||
// @JmsListener(...)
|
||||
String toUpperCase(String input, String customHeader) {
|
||||
return "ENIGMA";
|
||||
}
|
||||
|
||||
// @JmsListener(...)
|
||||
String toUpperCase(byte[] input) {
|
||||
return "ENIGMA";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+16
-23
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,8 +17,10 @@
|
||||
package org.springframework.messaging.handler.invocation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -28,16 +30,14 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.target.EmptyTargetSource;
|
||||
import org.springframework.cglib.core.SpringNamingPolicy;
|
||||
import org.springframework.cglib.proxy.Callback;
|
||||
import org.springframework.cglib.proxy.Enhancer;
|
||||
import org.springframework.cglib.proxy.Factory;
|
||||
import org.springframework.cglib.proxy.MethodInterceptor;
|
||||
import org.springframework.cglib.proxy.MethodProxy;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodIntrospector;
|
||||
@@ -123,6 +123,7 @@ import static java.util.stream.Collectors.joining;
|
||||
* </pre>
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @since 5.2
|
||||
*/
|
||||
public class ResolvableMethod {
|
||||
@@ -189,7 +190,6 @@ public class ResolvableMethod {
|
||||
|
||||
/**
|
||||
* Filter on method arguments with annotation.
|
||||
* See {@link org.springframework.web.method.MvcAnnotationPredicates}.
|
||||
*/
|
||||
@SafeVarargs
|
||||
public final ArgResolver annot(Predicate<MethodParameter>... filter) {
|
||||
@@ -302,7 +302,6 @@ public class ResolvableMethod {
|
||||
|
||||
/**
|
||||
* Filter on annotated methods.
|
||||
* See {@link org.springframework.web.method.MvcAnnotationPredicates}.
|
||||
*/
|
||||
@SafeVarargs
|
||||
public final Builder<T> annot(Predicate<Method>... filters) {
|
||||
@@ -313,7 +312,6 @@ public class ResolvableMethod {
|
||||
/**
|
||||
* Filter on methods annotated with the given annotation type.
|
||||
* @see #annot(Predicate[])
|
||||
* See {@link org.springframework.web.method.MvcAnnotationPredicates}.
|
||||
*/
|
||||
@SafeVarargs
|
||||
public final Builder<T> annotPresent(Class<? extends Annotation>... annotationTypes) {
|
||||
@@ -530,7 +528,6 @@ public class ResolvableMethod {
|
||||
|
||||
/**
|
||||
* Filter on method arguments with annotations.
|
||||
* See {@link org.springframework.web.method.MvcAnnotationPredicates}.
|
||||
*/
|
||||
@SafeVarargs
|
||||
public final ArgResolver annot(Predicate<MethodParameter>... filters) {
|
||||
@@ -542,7 +539,6 @@ public class ResolvableMethod {
|
||||
* Filter on method arguments that have the given annotations.
|
||||
* @param annotationTypes the annotation types
|
||||
* @see #annot(Predicate[])
|
||||
* See {@link org.springframework.web.method.MvcAnnotationPredicates}.
|
||||
*/
|
||||
@SafeVarargs
|
||||
public final ArgResolver annotPresent(Class<? extends Annotation>... annotationTypes) {
|
||||
@@ -615,16 +611,10 @@ public class ResolvableMethod {
|
||||
}
|
||||
|
||||
|
||||
private static class MethodInvocationInterceptor
|
||||
implements org.springframework.cglib.proxy.MethodInterceptor, MethodInterceptor {
|
||||
private static class MethodInvocationInterceptor implements MethodInterceptor, InvocationHandler {
|
||||
|
||||
private Method invokedMethod;
|
||||
|
||||
|
||||
Method getInvokedMethod() {
|
||||
return this.invokedMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) {
|
||||
@@ -639,20 +629,23 @@ public class ResolvableMethod {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object invoke(org.aopalliance.intercept.MethodInvocation inv) throws Throwable {
|
||||
return intercept(inv.getThis(), inv.getMethod(), inv.getArguments(), null);
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
return intercept(proxy, method, args, null);
|
||||
}
|
||||
|
||||
Method getInvokedMethod() {
|
||||
return this.invokedMethod;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T initProxy(Class<?> type, MethodInvocationInterceptor interceptor) {
|
||||
Assert.notNull(type, "'type' must not be null");
|
||||
if (type.isInterface()) {
|
||||
ProxyFactory factory = new ProxyFactory(EmptyTargetSource.INSTANCE);
|
||||
factory.addInterface(type);
|
||||
factory.addInterface(Supplier.class);
|
||||
factory.addAdvice(interceptor);
|
||||
return (T) factory.getProxy();
|
||||
return (T) Proxy.newProxyInstance(type.getClassLoader(),
|
||||
new Class<?>[] {type, Supplier.class},
|
||||
interceptor);
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
+13
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -62,6 +62,7 @@ import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
* @author Rod Johnson
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @author Sam Brannen
|
||||
* @since 2.0
|
||||
* @see javax.persistence.PersistenceContext
|
||||
* @see javax.persistence.PersistenceContextType#TRANSACTION
|
||||
@@ -74,9 +75,9 @@ public abstract class SharedEntityManagerCreator {
|
||||
|
||||
private static final Map<Class<?>, Class<?>[]> cachedQueryInterfaces = new ConcurrentReferenceHashMap<>(4);
|
||||
|
||||
private static final Set<String> transactionRequiringMethods = new HashSet<>(8);
|
||||
private static final Set<String> transactionRequiringMethods = new HashSet<>(6);
|
||||
|
||||
private static final Set<String> queryTerminatingMethods = new HashSet<>(8);
|
||||
private static final Set<String> queryTerminatingMethods = new HashSet<>(9);
|
||||
|
||||
static {
|
||||
transactionRequiringMethods.add("joinTransaction");
|
||||
@@ -86,12 +87,15 @@ public abstract class SharedEntityManagerCreator {
|
||||
transactionRequiringMethods.add("remove");
|
||||
transactionRequiringMethods.add("refresh");
|
||||
|
||||
queryTerminatingMethods.add("execute"); // JPA 2.1 StoredProcedureQuery
|
||||
queryTerminatingMethods.add("executeUpdate");
|
||||
queryTerminatingMethods.add("getSingleResult");
|
||||
queryTerminatingMethods.add("getResultStream");
|
||||
queryTerminatingMethods.add("getResultList");
|
||||
queryTerminatingMethods.add("list"); // Hibernate Query.list() method
|
||||
queryTerminatingMethods.add("execute"); // javax.persistence.StoredProcedureQuery.execute()
|
||||
queryTerminatingMethods.add("executeUpdate"); // javax.persistence.Query.executeUpdate()
|
||||
queryTerminatingMethods.add("getSingleResult"); // javax.persistence.Query.getSingleResult()
|
||||
queryTerminatingMethods.add("getResultStream"); // javax.persistence.Query.getResultStream()
|
||||
queryTerminatingMethods.add("getResultList"); // javax.persistence.Query.getResultList()
|
||||
queryTerminatingMethods.add("list"); // org.hibernate.query.Query.list()
|
||||
queryTerminatingMethods.add("stream"); // org.hibernate.query.Query.stream()
|
||||
queryTerminatingMethods.add("uniqueResult"); // org.hibernate.query.Query.uniqueResult()
|
||||
queryTerminatingMethods.add("uniqueResultOptional"); // org.hibernate.query.Query.uniqueResultOptional()
|
||||
}
|
||||
|
||||
|
||||
|
||||
+25
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -193,7 +193,7 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
|
||||
Mono<Connection> newCon = Mono.from(obtainConnectionFactory().create());
|
||||
connectionMono = newCon.doOnNext(connection -> {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Acquired Connection [" + newCon + "] for R2DBC transaction");
|
||||
logger.debug("Acquired Connection [" + connection + "] for R2DBC transaction");
|
||||
}
|
||||
txObject.setConnectionHolder(new ConnectionHolder(connection), true);
|
||||
});
|
||||
@@ -325,31 +325,48 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
|
||||
Mono<Void> afterCleanup = Mono.empty();
|
||||
|
||||
if (txObject.isMustRestoreAutoCommit()) {
|
||||
afterCleanup = afterCleanup.then(Mono.from(con.setAutoCommit(true)));
|
||||
Mono<Void> restoreAutoCommitStep = safeCleanupStep(
|
||||
"doCleanupAfterCompletion when restoring autocommit", Mono.from(con.setAutoCommit(true)));
|
||||
afterCleanup = afterCleanup.then(restoreAutoCommitStep);
|
||||
}
|
||||
|
||||
if (txObject.getPreviousIsolationLevel() != null) {
|
||||
afterCleanup = afterCleanup
|
||||
.then(Mono.from(con.setTransactionIsolationLevel(txObject.getPreviousIsolationLevel())));
|
||||
Mono<Void> restoreIsolationStep = safeCleanupStep(
|
||||
"doCleanupAfterCompletion when restoring isolation level",
|
||||
Mono.from(con.setTransactionIsolationLevel(txObject.getPreviousIsolationLevel())));
|
||||
afterCleanup = afterCleanup.then(restoreIsolationStep);
|
||||
}
|
||||
|
||||
return afterCleanup.then(Mono.defer(() -> {
|
||||
Mono<Void> releaseConnectionStep = Mono.defer(() -> {
|
||||
try {
|
||||
if (txObject.isNewConnectionHolder()) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Releasing R2DBC Connection [" + con + "] after transaction");
|
||||
}
|
||||
return ConnectionFactoryUtils.releaseConnection(con, obtainConnectionFactory());
|
||||
return safeCleanupStep("doCleanupAfterCompletion when releasing R2DBC Connection",
|
||||
ConnectionFactoryUtils.releaseConnection(con, obtainConnectionFactory()));
|
||||
}
|
||||
}
|
||||
finally {
|
||||
txObject.getConnectionHolder().clear();
|
||||
}
|
||||
return Mono.empty();
|
||||
}));
|
||||
});
|
||||
return afterCleanup.then(releaseConnectionStep);
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Void> safeCleanupStep(String stepDescription, Mono<Void> stepMono) {
|
||||
if (!logger.isDebugEnabled()) {
|
||||
return stepMono.onErrorComplete();
|
||||
}
|
||||
else {
|
||||
return stepMono.doOnError(e ->
|
||||
logger.debug(String.format("Error ignored during %s: %s", stepDescription, e)))
|
||||
.onErrorComplete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the transactional {@link Connection} right before transaction begin.
|
||||
* @deprecated in favor of {@link #prepareTransactionalConnection(Connection, TransactionDefinition)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,34 +20,18 @@ import java.util.function.Function;
|
||||
|
||||
import io.r2dbc.spi.Connection;
|
||||
|
||||
|
||||
/**
|
||||
* Union type combining {@link Function} and {@link SqlProvider} to expose the SQL that is
|
||||
* related to the underlying action.
|
||||
* related to the underlying action. The SqlProvider can support lazy / generate once semantics,
|
||||
* in which case {@link #getSql()} can be {@code null} until the {@code #apply(Connection)}
|
||||
* method is invoked.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Simon Baslé
|
||||
* @since 5.3
|
||||
* @param <R> the type of the result of the function.
|
||||
*/
|
||||
class ConnectionFunction<R> implements Function<Connection, R>, SqlProvider {
|
||||
|
||||
private final String sql;
|
||||
|
||||
private final Function<Connection, R> function;
|
||||
|
||||
|
||||
ConnectionFunction(String sql, Function<Connection, R> function) {
|
||||
this.sql = sql;
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public R apply(Connection t) {
|
||||
return this.function.apply(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSql() {
|
||||
return this.sql;
|
||||
}
|
||||
interface ConnectionFunction<R> extends Function<Connection, R>, SqlProvider {
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -79,7 +79,10 @@ public interface DatabaseClient extends ConnectionAccessor {
|
||||
* the execution. The SQL string can contain either native parameter
|
||||
* bind markers or named parameters (e.g. {@literal :foo, :bar}) when
|
||||
* {@link NamedParameterExpander} is enabled.
|
||||
* <p>Accepts {@link PreparedOperation} as SQL and binding {@link Supplier}
|
||||
* <p>Accepts {@link PreparedOperation} as SQL and binding {@link Supplier}.
|
||||
* <p>{@code DatabaseClient} implementations should defer the resolution of
|
||||
* the SQL string as much as possible, ideally up to the point where a
|
||||
* {@code Subscription} happens. This is the case for the default implementation.
|
||||
* @param sqlSupplier a supplier for the SQL statement
|
||||
* @return a new {@link GenericExecuteSpec}
|
||||
* @see NamedParameterExpander
|
||||
|
||||
+23
-28
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -60,6 +60,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Mark Paluch
|
||||
* @author Mingyuan Wu
|
||||
* @author Bogdan Ilchyshyn
|
||||
* @author Simon Baslé
|
||||
* @since 5.3
|
||||
*/
|
||||
class DefaultDatabaseClient implements DatabaseClient {
|
||||
@@ -158,11 +159,9 @@ class DefaultDatabaseClient implements DatabaseClient {
|
||||
/**
|
||||
* Release the {@link Connection}.
|
||||
* @param connection to close.
|
||||
* @return a {@link Publisher} that completes successfully when the connection is
|
||||
* closed
|
||||
* @return a {@link Publisher} that completes successfully when the connection is closed
|
||||
*/
|
||||
private Publisher<Void> closeConnection(Connection connection) {
|
||||
|
||||
return ConnectionFactoryUtils.currentConnectionFactory(
|
||||
obtainConnectionFactory()).then().onErrorResume(Exception.class,
|
||||
e -> Mono.from(connection.close()));
|
||||
@@ -188,24 +187,22 @@ class DefaultDatabaseClient implements DatabaseClient {
|
||||
new CloseSuppressingInvocationHandler(con));
|
||||
}
|
||||
|
||||
private static Mono<Integer> sumRowsUpdated(
|
||||
Function<Connection, Flux<Result>> resultFunction, Connection it) {
|
||||
private static Mono<Integer> sumRowsUpdated(Function<Connection, Flux<Result>> resultFunction, Connection it) {
|
||||
return resultFunction.apply(it)
|
||||
.flatMap(Result::getRowsUpdated)
|
||||
.collect(Collectors.summingInt(Integer::intValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine SQL from potential provider object.
|
||||
* @param sqlProvider object that's potentially a SqlProvider
|
||||
* Get SQL from a potential provider object.
|
||||
* @param object an object that is potentially an SqlProvider
|
||||
* @return the SQL string, or {@code null}
|
||||
* @see SqlProvider
|
||||
*/
|
||||
@Nullable
|
||||
private static String getSql(Object sqlProvider) {
|
||||
|
||||
if (sqlProvider instanceof SqlProvider) {
|
||||
return ((SqlProvider) sqlProvider).getSql();
|
||||
private static String getSql(Object object) {
|
||||
if (object instanceof SqlProvider) {
|
||||
return ((SqlProvider) object).getSql();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
@@ -214,7 +211,7 @@ class DefaultDatabaseClient implements DatabaseClient {
|
||||
|
||||
|
||||
/**
|
||||
* Base class for {@link DatabaseClient.GenericExecuteSpec} implementations.
|
||||
* Default {@link DatabaseClient.GenericExecuteSpec} implementation.
|
||||
*/
|
||||
class DefaultGenericExecuteSpec implements GenericExecuteSpec {
|
||||
|
||||
@@ -322,9 +319,8 @@ class DefaultDatabaseClient implements DatabaseClient {
|
||||
return fetch().rowsUpdated().then();
|
||||
}
|
||||
|
||||
private <T> FetchSpec<T> execute(Supplier<String> sqlSupplier, BiFunction<Row, RowMetadata, T> mappingFunction) {
|
||||
String sql = getRequiredSql(sqlSupplier);
|
||||
Function<Connection, Statement> statementFunction = connection -> {
|
||||
private ResultFunction getResultFunction(Supplier<String> sqlSupplier) {
|
||||
BiFunction<Connection, String, Statement> statementFunction = (connection, sql) -> {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Executing SQL statement [" + sql + "]");
|
||||
}
|
||||
@@ -370,16 +366,16 @@ class DefaultDatabaseClient implements DatabaseClient {
|
||||
return statement;
|
||||
};
|
||||
|
||||
Function<Connection, Flux<Result>> resultFunction = connection -> {
|
||||
Statement statement = statementFunction.apply(connection);
|
||||
return Flux.from(this.filterFunction.filter(statement, DefaultDatabaseClient.this.executeFunction))
|
||||
.cast(Result.class).checkpoint("SQL \"" + sql + "\" [DatabaseClient]");
|
||||
};
|
||||
return new ResultFunction(sqlSupplier, statementFunction, this.filterFunction, DefaultDatabaseClient.this.executeFunction);
|
||||
}
|
||||
|
||||
private <T> FetchSpec<T> execute(Supplier<String> sqlSupplier, BiFunction<Row, RowMetadata, T> mappingFunction) {
|
||||
ResultFunction resultHandler = getResultFunction(sqlSupplier);
|
||||
|
||||
return new DefaultFetchSpec<>(
|
||||
DefaultDatabaseClient.this, sql,
|
||||
new ConnectionFunction<>(sql, resultFunction),
|
||||
new ConnectionFunction<>(sql, connection -> sumRowsUpdated(resultFunction, connection)),
|
||||
DefaultDatabaseClient.this,
|
||||
resultHandler,
|
||||
connection -> sumRowsUpdated(resultHandler, connection),
|
||||
mappingFunction);
|
||||
}
|
||||
|
||||
@@ -505,12 +501,11 @@ class DefaultDatabaseClient implements DatabaseClient {
|
||||
|
||||
private static final long serialVersionUID = -8994138383301201380L;
|
||||
|
||||
final Connection connection;
|
||||
final transient Connection connection;
|
||||
|
||||
final Function<Connection, Publisher<Void>> closeFunction;
|
||||
final transient Function<Connection, Publisher<Void>> closeFunction;
|
||||
|
||||
ConnectionCloseHolder(Connection connection,
|
||||
Function<Connection, Publisher<Void>> closeFunction) {
|
||||
ConnectionCloseHolder(Connection connection, Function<Connection, Publisher<Void>> closeFunction) {
|
||||
this.connection = connection;
|
||||
this.closeFunction = closeFunction;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,7 +20,6 @@ import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.r2dbc.spi.Connection;
|
||||
import io.r2dbc.spi.Result;
|
||||
import io.r2dbc.spi.Row;
|
||||
import io.r2dbc.spi.RowMetadata;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -32,6 +31,7 @@ import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
* Default {@link FetchSpec} implementation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Simon Baslé
|
||||
* @since 5.3
|
||||
* @param <T> the row result type
|
||||
*/
|
||||
@@ -39,24 +39,21 @@ class DefaultFetchSpec<T> implements FetchSpec<T> {
|
||||
|
||||
private final ConnectionAccessor connectionAccessor;
|
||||
|
||||
private final String sql;
|
||||
|
||||
private final Function<Connection, Flux<Result>> resultFunction;
|
||||
private final ResultFunction resultFunction;
|
||||
|
||||
private final Function<Connection, Mono<Integer>> updatedRowsFunction;
|
||||
|
||||
private final BiFunction<Row, RowMetadata, T> mappingFunction;
|
||||
|
||||
|
||||
DefaultFetchSpec(ConnectionAccessor connectionAccessor, String sql,
|
||||
Function<Connection, Flux<Result>> resultFunction,
|
||||
DefaultFetchSpec(ConnectionAccessor connectionAccessor,
|
||||
ResultFunction resultFunction,
|
||||
Function<Connection, Mono<Integer>> updatedRowsFunction,
|
||||
BiFunction<Row, RowMetadata, T> mappingFunction) {
|
||||
|
||||
this.sql = sql;
|
||||
this.connectionAccessor = connectionAccessor;
|
||||
this.resultFunction = resultFunction;
|
||||
this.updatedRowsFunction = updatedRowsFunction;
|
||||
this.updatedRowsFunction = new DelegateConnectionFunction<>(resultFunction, updatedRowsFunction);
|
||||
this.mappingFunction = mappingFunction;
|
||||
}
|
||||
|
||||
@@ -70,7 +67,7 @@ class DefaultFetchSpec<T> implements FetchSpec<T> {
|
||||
}
|
||||
if (list.size() > 1) {
|
||||
return Mono.error(new IncorrectResultSizeDataAccessException(
|
||||
String.format("Query [%s] returned non unique result.", this.sql),
|
||||
String.format("Query [%s] returned non unique result.", this.resultFunction.getSql()),
|
||||
1));
|
||||
}
|
||||
return Mono.just(list.get(0));
|
||||
@@ -84,7 +81,7 @@ class DefaultFetchSpec<T> implements FetchSpec<T> {
|
||||
|
||||
@Override
|
||||
public Flux<T> all() {
|
||||
return this.connectionAccessor.inConnectionMany(new ConnectionFunction<>(this.sql,
|
||||
return this.connectionAccessor.inConnectionMany(new DelegateConnectionFunction<>(this.resultFunction,
|
||||
connection -> this.resultFunction.apply(connection)
|
||||
.flatMap(result -> result.map(this.mappingFunction))));
|
||||
}
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.r2dbc.core;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.r2dbc.spi.Connection;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A {@link ConnectionFunction} that delegates to a {@code SqlProvider} and a plain
|
||||
* {@code Function}.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
* @since 5.3.26
|
||||
* @param <R> the type of the result of the function.
|
||||
*/
|
||||
final class DelegateConnectionFunction<R> implements ConnectionFunction<R> {
|
||||
|
||||
private final SqlProvider sql;
|
||||
|
||||
private final Function<Connection, R> function;
|
||||
|
||||
|
||||
DelegateConnectionFunction(SqlProvider sql, Function<Connection, R> function) {
|
||||
this.sql = sql;
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public R apply(Connection t) {
|
||||
return this.function.apply(t);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getSql() {
|
||||
return this.sql.getSql();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.r2dbc.core;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.r2dbc.spi.Connection;
|
||||
import io.r2dbc.spi.Result;
|
||||
import io.r2dbc.spi.Statement;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link ConnectionFunction} that produces a {@code Flux} of {@link Result} and that
|
||||
* defers generation of the SQL until the function has been applied.
|
||||
* Beforehand, the {@code getSql()} method simply returns {@code null}. The sql String is
|
||||
* also memoized during application, so that subsequent calls to {@link #getSql()} return
|
||||
* the same {@code String} without further calls to the {@code Supplier}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Simon Baslé
|
||||
* @since 5.3.26
|
||||
*/
|
||||
final class ResultFunction implements ConnectionFunction<Flux<Result>> {
|
||||
|
||||
final Supplier<String> sqlSupplier;
|
||||
final BiFunction<Connection, String, Statement> statementFunction;
|
||||
final StatementFilterFunction filterFunction;
|
||||
final ExecuteFunction executeFunction;
|
||||
|
||||
@Nullable
|
||||
String resolvedSql = null;
|
||||
|
||||
ResultFunction(Supplier<String> sqlSupplier, BiFunction<Connection, String, Statement> statementFunction, StatementFilterFunction filterFunction, ExecuteFunction executeFunction) {
|
||||
this.sqlSupplier = sqlSupplier;
|
||||
this.statementFunction = statementFunction;
|
||||
this.filterFunction = filterFunction;
|
||||
this.executeFunction = executeFunction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Result> apply(Connection connection) {
|
||||
String sql = this.sqlSupplier.get();
|
||||
Assert.state(StringUtils.hasText(sql), "SQL returned by supplier must not be empty");
|
||||
this.resolvedSql = sql;
|
||||
Statement statement = this.statementFunction.apply(connection, sql);
|
||||
return Flux.from(this.filterFunction.filter(statement, this.executeFunction))
|
||||
.cast(Result.class).checkpoint("SQL \"" + sql + "\" [DatabaseClient]");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getSql() {
|
||||
return this.resolvedSql;
|
||||
}
|
||||
}
|
||||
+32
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,12 +22,15 @@ import io.r2dbc.spi.Connection;
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import io.r2dbc.spi.IsolationLevel;
|
||||
import io.r2dbc.spi.R2dbcBadGrammarException;
|
||||
import io.r2dbc.spi.R2dbcTimeoutException;
|
||||
import io.r2dbc.spi.Statement;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.r2dbc.BadSqlGrammarException;
|
||||
import org.springframework.transaction.CannotCreateTransactionException;
|
||||
import org.springframework.transaction.IllegalTransactionStateException;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
@@ -319,6 +322,34 @@ class R2dbcTransactionManagerUnitTests {
|
||||
verifyNoMoreInteractions(connectionMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void testConnectionReleasedWhenRollbackFails() {
|
||||
when(connectionMock.rollbackTransaction()).thenReturn(Mono.defer(() -> Mono.error(new R2dbcBadGrammarException("Rollback should fail"))), Mono.empty());
|
||||
|
||||
TransactionalOperator operator = TransactionalOperator.create(tm);
|
||||
|
||||
when(connectionMock.isAutoCommit()).thenReturn(true);
|
||||
when(connectionMock.setAutoCommit(true)).thenReturn(Mono.defer(() -> Mono.error(new R2dbcTimeoutException("SET AUTOCOMMIT = 1 timed out"))));
|
||||
when(connectionMock.setTransactionIsolationLevel(any())).thenReturn(Mono.empty());
|
||||
when(connectionMock.setAutoCommit(false)).thenReturn(Mono.empty());
|
||||
|
||||
operator.execute(reactiveTransaction -> ConnectionFactoryUtils.getConnection(connectionFactoryMock)
|
||||
.doOnNext(connection -> {
|
||||
throw new IllegalStateException("Intentional error to trigger rollback");
|
||||
}).then()).as(StepVerifier::create)
|
||||
.verifyErrorSatisfies(e -> Assertions.assertThat(e)
|
||||
.isInstanceOf(BadSqlGrammarException.class)
|
||||
.hasCause(new R2dbcBadGrammarException("Rollback should fail"))
|
||||
);
|
||||
|
||||
verify(connectionMock).isAutoCommit();
|
||||
verify(connectionMock).beginTransaction();
|
||||
verify(connectionMock, never()).commitTransaction();
|
||||
verify(connectionMock).rollbackTransaction();
|
||||
verify(connectionMock).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTransactionSetRollbackOnly() {
|
||||
when(connectionMock.rollbackTransaction()).thenReturn(Mono.empty());
|
||||
|
||||
+45
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,8 @@
|
||||
package org.springframework.r2dbc.core;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import io.r2dbc.spi.Connection;
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
@@ -64,6 +66,7 @@ import static org.mockito.BDDMockito.when;
|
||||
* @author Mark Paluch
|
||||
* @author Ferdinand Jacobs
|
||||
* @author Jens Schauder
|
||||
* @author Simon Baslé
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@@ -397,6 +400,47 @@ class DefaultDatabaseClientUnitTests {
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sqlSupplierInvocationIsDeferredUntilSubscription() {
|
||||
// We'll have either 2 or 3 rows, depending on the subscription and the generated SQL
|
||||
MockRowMetadata metadata = MockRowMetadata.builder().columnMetadata(
|
||||
MockColumnMetadata.builder().name("id").javaType(Integer.class).build()).build();
|
||||
final MockRow row1 = MockRow.builder().identified("id", Integer.class, 1).build();
|
||||
final MockRow row2 = MockRow.builder().identified("id", Integer.class, 2).build();
|
||||
final MockRow row3 = MockRow.builder().identified("id", Integer.class, 3).build();
|
||||
// Set up 2 mock statements
|
||||
mockStatementFor("SELECT id FROM test WHERE id < '3'", MockResult.builder()
|
||||
.rowMetadata(metadata)
|
||||
.row(row1, row2).build());
|
||||
mockStatementFor("SELECT id FROM test WHERE id < '4'", MockResult.builder()
|
||||
.rowMetadata(metadata)
|
||||
.row(row1, row2, row3).build());
|
||||
// Create the client
|
||||
DatabaseClient databaseClient = this.databaseClientBuilder.build();
|
||||
|
||||
AtomicInteger invoked = new AtomicInteger();
|
||||
// Assemble a publisher, but don't subscribe yet
|
||||
Mono<List<Integer>> operation = databaseClient
|
||||
.sql(() -> {
|
||||
int idMax = 2 + invoked.incrementAndGet();
|
||||
return String.format("SELECT id FROM test WHERE id < '%s'", idMax);
|
||||
})
|
||||
.map(r -> r.get("id", Integer.class))
|
||||
.all()
|
||||
.collectList();
|
||||
|
||||
assertThat(invoked).as("invoked (before subscription)").hasValue(0);
|
||||
|
||||
List<Integer> rows = operation.block();
|
||||
assertThat(invoked).as("invoked (after 1st subscription)").hasValue(1);
|
||||
assertThat(rows).containsExactly(1, 2);
|
||||
|
||||
rows = operation.block();
|
||||
assertThat(invoked).as("invoked (after 2nd subscription)").hasValue(2);
|
||||
assertThat(rows).containsExactly(1, 2, 3);
|
||||
}
|
||||
|
||||
|
||||
private Statement mockStatement() {
|
||||
return mockStatementFor(null, null);
|
||||
}
|
||||
|
||||
+5
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -70,14 +70,15 @@ import java.lang.annotation.Target;
|
||||
* class ExampleIntegrationTests {
|
||||
*
|
||||
* @Container
|
||||
* static RedisContainer redis = new RedisContainer();
|
||||
* static GenericContainer redis =
|
||||
* new GenericContainer("redis:5.0.3-alpine").withExposedPorts(6379);
|
||||
*
|
||||
* // ...
|
||||
*
|
||||
* @DynamicPropertySource
|
||||
* static void redisProperties(DynamicPropertyRegistry registry) {
|
||||
* registry.add("redis.host", redis::getContainerIpAddress);
|
||||
* registry.add("redis.port", redis::getMappedPort);
|
||||
* registry.add("redis.host", redis::getHost);
|
||||
* registry.add("redis.port", redis::getFirstMappedPort);
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
|
||||
+121
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -47,6 +47,7 @@ import static org.springframework.test.util.AssertionErrors.fail;
|
||||
* @author Craig Walls
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sam Brannen
|
||||
* @author Simon Baslé
|
||||
* @since 3.2
|
||||
*/
|
||||
public abstract class MockRestRequestMatchers {
|
||||
@@ -112,8 +113,49 @@ public abstract class MockRestRequestMatchers {
|
||||
return request -> assertEquals("Unexpected request", uri, request.getURI());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert request query parameter values with the given Hamcrest matcher,
|
||||
* matching on the entire {@link List} of values.
|
||||
* <p>For example, this can be used to check that the list of query parameter
|
||||
* values has at least one value matching a given Hamcrest matcher (such as
|
||||
* {@link org.hamcrest.Matchers#hasItem(Matcher)}), that every value in the list
|
||||
* matches common criteria (such as {@link org.hamcrest.Matchers#everyItem(Matcher)}),
|
||||
* that each value in the list matches corresponding dedicated criteria
|
||||
* (such as {@link org.hamcrest.Matchers#contains(Matcher[])}), etc.
|
||||
* @param name the name of the query parameter whose value(s) will be asserted
|
||||
* @param matcher the Hamcrest matcher to apply to the entire list of values
|
||||
* for the given query parameter
|
||||
* @since 5.3.26
|
||||
* @see #queryParam(String, Matcher...)
|
||||
* @see #queryParam(String, String...)
|
||||
*/
|
||||
public static RequestMatcher queryParam(String name, Matcher<? super List<String>> matcher) {
|
||||
return request -> {
|
||||
MultiValueMap<String, String> params = getQueryParams(request);
|
||||
List<String> paramValues = params.get(name);
|
||||
if (paramValues == null) {
|
||||
fail("Expected query param <" + name + "> to exist but was null");
|
||||
}
|
||||
assertThat("Query param [" + name + "] values", paramValues, matcher);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert request query parameter values with the given Hamcrest matcher(s).
|
||||
* <p>If the query parameter value list is larger than the number of provided
|
||||
* {@code matchers}, no matchers will be applied to the extra query parameter
|
||||
* values, effectively ignoring the additional parameter values. If the number
|
||||
* of provided {@code matchers} exceeds the number of query parameter values,
|
||||
* an {@link AssertionError} will be thrown to signal the mismatch.
|
||||
* <p>See {@link #queryParam(String, Matcher)} for a variant which accepts a
|
||||
* {@code Matcher} that applies to the entire list of values as opposed to
|
||||
* applying only to individual values.
|
||||
* @param name the name of the query parameter whose value(s) will be asserted
|
||||
* @param matchers the Hamcrest matchers to apply to individual query parameter
|
||||
* values; the n<sup>th</sup> matcher is applied to the n<sup>th</sup> query
|
||||
* parameter value
|
||||
* @see #queryParam(String, Matcher)
|
||||
* @see #queryParam(String, String...)
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static RequestMatcher queryParam(String name, Matcher<? super String>... matchers) {
|
||||
@@ -128,6 +170,20 @@ public abstract class MockRestRequestMatchers {
|
||||
|
||||
/**
|
||||
* Assert request query parameter values.
|
||||
* <p>If the query parameter value list is larger than the number of
|
||||
* {@code expectedValues}, no assertions will be applied to the extra query
|
||||
* parameter values, effectively ignoring the additional parameter values. If
|
||||
* the number of {@code expectedValues} exceeds the number of query parameter
|
||||
* values, an {@link AssertionError} will be thrown to signal the mismatch.
|
||||
* <p>See {@link #queryParam(String, Matcher)} for a variant which accepts a
|
||||
* Hamcrest {@code Matcher} that applies to the entire list of values as opposed
|
||||
* to asserting only individual values.
|
||||
* @param name the name of the query parameter whose value(s) will be asserted
|
||||
* @param expectedValues the expected values of individual query parameter values;
|
||||
* the n<sup>th</sup> expected value is compared to the n<sup>th</sup> query
|
||||
* parameter value
|
||||
* @see #queryParam(String, Matcher)
|
||||
* @see #queryParam(String, Matcher...)
|
||||
*/
|
||||
public static RequestMatcher queryParam(String name, String... expectedValues) {
|
||||
return request -> {
|
||||
@@ -143,21 +199,47 @@ public abstract class MockRestRequestMatchers {
|
||||
return UriComponentsBuilder.fromUri(request.getURI()).build().getQueryParams();
|
||||
}
|
||||
|
||||
private static void assertValueCount(
|
||||
String valueType, String name, MultiValueMap<String, String> map, int count) {
|
||||
|
||||
List<String> values = map.get(name);
|
||||
String message = "Expected " + valueType + " <" + name + ">";
|
||||
if (values == null) {
|
||||
fail(message + " to exist but was null");
|
||||
}
|
||||
if (count > values.size()) {
|
||||
fail(message + " to have at least <" + count + "> values but found " + values);
|
||||
}
|
||||
/**
|
||||
* Assert request header values with the given Hamcrest matcher, matching on
|
||||
* the entire {@link List} of values.
|
||||
* <p>For example, this can be used to check that the list of header values
|
||||
* has at least one value matching a given Hamcrest matcher (such as
|
||||
* {@link org.hamcrest.Matchers#hasItem(Matcher)}), that every value in the list
|
||||
* matches common criteria (such as {@link org.hamcrest.Matchers#everyItem(Matcher)}),
|
||||
* that each value in the list matches corresponding dedicated criteria
|
||||
* (such as {@link org.hamcrest.Matchers#contains(Matcher[])}), etc.
|
||||
* @param name the name of the header whose value(s) will be asserted
|
||||
* @param matcher the Hamcrest matcher to apply to the entire list of values
|
||||
* for the given header
|
||||
* @since 5.3.26
|
||||
* @see #header(String, Matcher...)
|
||||
* @see #header(String, String...)
|
||||
*/
|
||||
public static RequestMatcher header(String name, Matcher<? super List<String>> matcher) {
|
||||
return request -> {
|
||||
List<String> headerValues = request.getHeaders().get(name);
|
||||
if (headerValues == null) {
|
||||
fail("Expected header <" + name + "> to exist but was null");
|
||||
}
|
||||
assertThat("Request header [" + name + "] values", headerValues, matcher);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert request header values with the given Hamcrest matcher(s).
|
||||
* <p>If the header value list is larger than the number of provided
|
||||
* {@code matchers}, no matchers will be applied to the extra header values,
|
||||
* effectively ignoring the additional header values. If the number of
|
||||
* provided {@code matchers} exceeds the number of header values, an
|
||||
* {@link AssertionError} will be thrown to signal the mismatch.
|
||||
* <p>See {@link #header(String, Matcher)} for a variant which accepts a
|
||||
* Hamcrest {@code Matcher} that applies to the entire list of values as
|
||||
* opposed to applying only to individual values.
|
||||
* @param name the name of the header whose value(s) will be asserted
|
||||
* @param matchers the Hamcrest matchers to apply to individual header values;
|
||||
* the n<sup>th</sup> matcher is applied to the n<sup>th</sup> header value
|
||||
* @see #header(String, Matcher)
|
||||
* @see #header(String, String...)
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static RequestMatcher header(String name, Matcher<? super String>... matchers) {
|
||||
@@ -173,6 +255,19 @@ public abstract class MockRestRequestMatchers {
|
||||
|
||||
/**
|
||||
* Assert request header values.
|
||||
* <p>If the header value list is larger than the number of {@code expectedValues},
|
||||
* no matchers will be applied to the extra header values, effectively ignoring the
|
||||
* additional header values. If the number of {@code expectedValues} exceeds the
|
||||
* number of header values, an {@link AssertionError} will be thrown to signal the
|
||||
* mismatch.
|
||||
* <p>See {@link #header(String, Matcher)} for a variant which accepts a
|
||||
* Hamcrest {@code Matcher} that applies to the entire list of values as
|
||||
* opposed to applying only to individual values.
|
||||
* @param name the name of the header whose value(s) will be asserted
|
||||
* @param expectedValues the expected values of individual header values; the
|
||||
* n<sup>th</sup> expected value is compared to the n<sup>th</sup> header value
|
||||
* @see #header(String, Matcher)
|
||||
* @see #header(String, Matcher...)
|
||||
*/
|
||||
public static RequestMatcher header(String name, String... expectedValues) {
|
||||
return request -> {
|
||||
@@ -258,4 +353,18 @@ public abstract class MockRestRequestMatchers {
|
||||
return new XpathRequestMatchers(expression, namespaces, args);
|
||||
}
|
||||
|
||||
|
||||
private static void assertValueCount(
|
||||
String valueType, String name, MultiValueMap<String, String> map, int count) {
|
||||
|
||||
List<String> values = map.get(name);
|
||||
String message = "Expected " + valueType + " <" + name + ">";
|
||||
if (values == null) {
|
||||
fail(message + " to exist but was null");
|
||||
}
|
||||
if (count > values.size()) {
|
||||
fail(message + " to have at least <" + count + "> values but found " + values);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+9
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -68,6 +68,8 @@ public final class MockMvcWebConnection implements WebConnection {
|
||||
|
||||
private WebClient webClient;
|
||||
|
||||
private static int MAX_FORWARDS = 100;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance that assumes the context path of the application
|
||||
@@ -133,10 +135,15 @@ public final class MockMvcWebConnection implements WebConnection {
|
||||
|
||||
MockHttpServletResponse httpServletResponse = getResponse(requestBuilder);
|
||||
String forwardedUrl = httpServletResponse.getForwardedUrl();
|
||||
while (forwardedUrl != null) {
|
||||
int forwards = 0;
|
||||
while (forwardedUrl != null && forwards < MAX_FORWARDS) {
|
||||
requestBuilder.setForwardPostProcessor(new ForwardRequestPostProcessor(forwardedUrl));
|
||||
httpServletResponse = getResponse(requestBuilder);
|
||||
forwardedUrl = httpServletResponse.getForwardedUrl();
|
||||
forwards += 1;
|
||||
}
|
||||
if (forwards == MAX_FORWARDS) {
|
||||
throw new IllegalStateException("Forwarded more than " + forwards + " times in a row, potential infinite forward loop");
|
||||
}
|
||||
storeCookies(webRequest, httpServletResponse.getCookies());
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -83,7 +83,7 @@ public class XpathResultMatchers {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response encoding if explicitly defined in the response, {code null} otherwise.
|
||||
* Get the response encoding if explicitly defined in the response, {@code null} otherwise.
|
||||
*/
|
||||
@Nullable
|
||||
private String getDefinedEncoding(MockHttpServletResponse response) {
|
||||
|
||||
+25
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -224,6 +224,18 @@ fun MockMvc.multipart(urlTemplate: String, vararg vars: Any?, dsl: MockMultipart
|
||||
return MockMultipartHttpServletRequestDsl(requestBuilder).apply(dsl).perform(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* [MockMvc] extension providing access to [MockMultipartHttpServletRequestDsl] Kotlin DSL.
|
||||
*
|
||||
* @see MockMvcRequestBuilders.multipart
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.3.26
|
||||
*/
|
||||
fun MockMvc.multipart(httpMethod: HttpMethod, urlTemplate: String, vararg vars: Any?, dsl: MockMultipartHttpServletRequestDsl.() -> Unit = {}): ResultActionsDsl {
|
||||
val requestBuilder = MockMvcRequestBuilders.multipart(httpMethod, urlTemplate, *vars)
|
||||
return MockMultipartHttpServletRequestDsl(requestBuilder).apply(dsl).perform(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* [MockMvc] extension providing access to [MockMultipartHttpServletRequestDsl] Kotlin DSL.
|
||||
*
|
||||
@@ -235,3 +247,15 @@ fun MockMvc.multipart(uri: URI, dsl: MockMultipartHttpServletRequestDsl.() -> Un
|
||||
val requestBuilder = MockMvcRequestBuilders.multipart(uri)
|
||||
return MockMultipartHttpServletRequestDsl(requestBuilder).apply(dsl).perform(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* [MockMvc] extension providing access to [MockMultipartHttpServletRequestDsl] Kotlin DSL.
|
||||
*
|
||||
* @see MockMvcRequestBuilders.multipart
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.3.26
|
||||
*/
|
||||
fun MockMvc.multipart(httpMethod: HttpMethod, uri: URI, dsl: MockMultipartHttpServletRequestDsl.() -> Unit = {}): ResultActionsDsl {
|
||||
val requestBuilder = MockMvcRequestBuilders.multipart(httpMethod, uri)
|
||||
return MockMultipartHttpServletRequestDsl(requestBuilder).apply(dsl).perform(this)
|
||||
}
|
||||
|
||||
+205
-62
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,18 +16,33 @@
|
||||
|
||||
package org.springframework.test.web.client.match;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.assertj.core.api.ThrowableTypeAssert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.mock.http.client.MockClientHttpRequest;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.any;
|
||||
import static org.hamcrest.Matchers.anything;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.either;
|
||||
import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.hamcrest.Matchers.everyItem;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MockRestRequestMatchers}.
|
||||
@@ -35,179 +50,307 @@ import static org.hamcrest.Matchers.containsString;
|
||||
* @author Craig Walls
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sam Brannen
|
||||
* @author Simon Baslé
|
||||
*/
|
||||
public class MockRestRequestMatchersTests {
|
||||
class MockRestRequestMatchersTests {
|
||||
|
||||
private final MockClientHttpRequest request = new MockClientHttpRequest();
|
||||
|
||||
|
||||
@Test
|
||||
public void requestTo() throws Exception {
|
||||
void requestTo() throws Exception {
|
||||
this.request.setURI(new URI("http://www.foo.example/bar"));
|
||||
|
||||
MockRestRequestMatchers.requestTo("http://www.foo.example/bar").match(this.request);
|
||||
}
|
||||
|
||||
@Test // SPR-15819
|
||||
public void requestToUriTemplate() throws Exception {
|
||||
void requestToUriTemplate() throws Exception {
|
||||
this.request.setURI(new URI("http://www.foo.example/bar"));
|
||||
|
||||
MockRestRequestMatchers.requestToUriTemplate("http://www.foo.example/{bar}", "bar").match(this.request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestToNoMatch() throws Exception {
|
||||
void requestToNoMatch() throws Exception {
|
||||
this.request.setURI(new URI("http://www.foo.example/bar"));
|
||||
|
||||
assertThatThrownBy(
|
||||
() -> MockRestRequestMatchers.requestTo("http://www.foo.example/wrong").match(this.request))
|
||||
.isInstanceOf(AssertionError.class);
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.requestTo("http://www.foo.example/wrong").match(this.request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestToContains() throws Exception {
|
||||
void requestToContains() throws Exception {
|
||||
this.request.setURI(new URI("http://www.foo.example/bar"));
|
||||
|
||||
MockRestRequestMatchers.requestTo(containsString("bar")).match(this.request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void method() throws Exception {
|
||||
void method() throws Exception {
|
||||
this.request.setMethod(HttpMethod.GET);
|
||||
|
||||
MockRestRequestMatchers.method(HttpMethod.GET).match(this.request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodNoMatch() throws Exception {
|
||||
void methodNoMatch() throws Exception {
|
||||
this.request.setMethod(HttpMethod.POST);
|
||||
|
||||
assertThatThrownBy(() -> MockRestRequestMatchers.method(HttpMethod.GET).match(this.request))
|
||||
.isInstanceOf(AssertionError.class)
|
||||
.hasMessageContaining("expected:<GET> but was:<POST>");
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.method(HttpMethod.GET).match(this.request))
|
||||
.withMessageContaining("expected:<GET> but was:<POST>");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void header() throws Exception {
|
||||
void header() throws Exception {
|
||||
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
|
||||
|
||||
MockRestRequestMatchers.header("foo", "bar", "baz").match(this.request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headerDoesNotExist() throws Exception {
|
||||
void headerDoesNotExist() throws Exception {
|
||||
MockRestRequestMatchers.headerDoesNotExist(null).match(this.request);
|
||||
MockRestRequestMatchers.headerDoesNotExist("").match(this.request);
|
||||
MockRestRequestMatchers.headerDoesNotExist("foo").match(this.request);
|
||||
|
||||
List<String> values = Arrays.asList("bar", "baz");
|
||||
this.request.getHeaders().put("foo", values);
|
||||
assertThatThrownBy(() -> MockRestRequestMatchers.headerDoesNotExist("foo").match(this.request))
|
||||
.isInstanceOf(AssertionError.class)
|
||||
.hasMessage("Expected header <foo> not to exist, but it exists with values: " + values);
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.headerDoesNotExist("foo").match(this.request))
|
||||
.withMessage("Expected header <foo> not to exist, but it exists with values: " + values);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headerMissing() throws Exception {
|
||||
assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", "bar").match(this.request))
|
||||
.isInstanceOf(AssertionError.class)
|
||||
.hasMessageContaining("was null");
|
||||
void headerMissing() {
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.header("foo", "bar").match(this.request))
|
||||
.withMessageContaining("was null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headerMissingValue() throws Exception {
|
||||
void headerMissingValue() {
|
||||
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
|
||||
|
||||
assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", "bad").match(this.request))
|
||||
.isInstanceOf(AssertionError.class)
|
||||
.hasMessageContaining("expected:<bad> but was:<bar>");
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.header("foo", "bad").match(this.request))
|
||||
.withMessageContaining("expected:<bad> but was:<bar>");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headerContains() throws Exception {
|
||||
void headerContains() throws Exception {
|
||||
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
|
||||
|
||||
MockRestRequestMatchers.header("foo", containsString("ba")).match(this.request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headerContainsWithMissingHeader() throws Exception {
|
||||
assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", containsString("baz")).match(this.request))
|
||||
.isInstanceOf(AssertionError.class)
|
||||
.hasMessageContaining("but was null");
|
||||
void headerContainsWithMissingHeader() {
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.header("foo", containsString("baz")).match(this.request))
|
||||
.withMessage("Expected header <foo> to exist but was null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headerContainsWithMissingValue() throws Exception {
|
||||
void headerContainsWithMissingValue() {
|
||||
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
|
||||
|
||||
assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", containsString("bx")).match(this.request))
|
||||
.isInstanceOf(AssertionError.class)
|
||||
.hasMessageContaining("was \"bar\"");
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.header("foo", containsString("bx")).match(this.request))
|
||||
.withMessageContaining("was \"bar\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headers() throws Exception {
|
||||
void headerListMissing() {
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.header("foo", hasSize(2)).match(this.request))
|
||||
.withMessage("Expected header <foo> to exist but was null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void headerListMatchers() throws IOException {
|
||||
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
|
||||
|
||||
MockRestRequestMatchers.header("foo", containsInAnyOrder(endsWith("baz"), endsWith("bar"))).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", contains(is("bar"), is("baz"))).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", contains(is("bar"), anything())).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", hasItem(endsWith("baz"))).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", everyItem(startsWith("ba"))).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", hasSize(2)).match(this.request);
|
||||
|
||||
// These can be a bit ambiguous when reading the test (the compiler selects the list matcher):
|
||||
MockRestRequestMatchers.header("foo", notNullValue()).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", is(anything())).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", allOf(notNullValue(), notNullValue())).match(this.request);
|
||||
|
||||
// These are not as ambiguous thanks to an inner matcher that is either obviously list-oriented,
|
||||
// string-oriented, or obviously a vararg of matchers
|
||||
|
||||
// list matcher version
|
||||
MockRestRequestMatchers.header("foo", allOf(notNullValue(), hasSize(2))).match(this.request);
|
||||
|
||||
// vararg version
|
||||
MockRestRequestMatchers.header("foo", allOf(notNullValue(), endsWith("ar"))).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", is((any(String.class)))).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", either(is("bar")).or(is(nullValue()))).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", is(notNullValue()), is(notNullValue())).match(this.request);
|
||||
}
|
||||
|
||||
@Test
|
||||
void headerListContainsMismatch() {
|
||||
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
|
||||
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.header("foo", contains(containsString("ba"))).match(this.request))
|
||||
.withMessageContainingAll(
|
||||
"Request header [foo] values",
|
||||
"Expected: iterable containing [a string containing \"ba\"]",
|
||||
"but: not matched: \"baz\"");
|
||||
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.header("foo", hasItem(endsWith("ba"))).match(this.request))
|
||||
.withMessageContainingAll(
|
||||
"Request header [foo] values",
|
||||
"Expected: a collection containing a string ending with \"ba\"",
|
||||
"but: mismatches were: [was \"bar\", was \"baz\"]");
|
||||
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.header("foo", everyItem(endsWith("ar"))).match(this.request))
|
||||
.withMessageContainingAll(
|
||||
"Request header [foo] values",
|
||||
"Expected: every item is a string ending with \"ar\"",
|
||||
"but: an item was \"baz\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void headers() throws Exception {
|
||||
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
|
||||
|
||||
MockRestRequestMatchers.header("foo", "bar", "baz").match(this.request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWithMissingHeader() throws Exception {
|
||||
assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", "bar").match(this.request))
|
||||
.isInstanceOf(AssertionError.class)
|
||||
.hasMessageContaining("but was null");
|
||||
void headersWithMissingHeader() {
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.header("foo", "bar").match(this.request))
|
||||
.withMessage("Expected header <foo> to exist but was null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void headersWithMissingValue() throws Exception {
|
||||
this.request.getHeaders().put("foo", Collections.singletonList("bar"));
|
||||
void headersWithMissingValue() {
|
||||
this.request.getHeaders().put("foo", Arrays.asList("bar"));
|
||||
|
||||
assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", "bar", "baz").match(this.request))
|
||||
.isInstanceOf(AssertionError.class)
|
||||
.hasMessageContaining("to have at least <2> values");
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.header("foo", "bar", "baz").match(this.request))
|
||||
.withMessageContaining("to have at least <2> values");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryParam() throws Exception {
|
||||
void queryParam() throws Exception {
|
||||
this.request.setURI(new URI("http://www.foo.example/a?foo=bar&foo=baz"));
|
||||
|
||||
MockRestRequestMatchers.queryParam("foo", "bar", "baz").match(this.request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryParamMissing() throws Exception {
|
||||
void queryParamMissing() throws Exception {
|
||||
this.request.setURI(new URI("http://www.foo.example/a"));
|
||||
|
||||
assertThatThrownBy(() -> MockRestRequestMatchers.queryParam("foo", "bar").match(this.request))
|
||||
.isInstanceOf(AssertionError.class)
|
||||
.hasMessageContaining("but was null");
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.queryParam("foo", "bar").match(this.request))
|
||||
.withMessage("Expected query param <foo> to exist but was null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryParamMissingValue() throws Exception {
|
||||
void queryParamMissingValue() throws Exception {
|
||||
this.request.setURI(new URI("http://www.foo.example/a?foo=bar&foo=baz"));
|
||||
|
||||
assertThatThrownBy(() -> MockRestRequestMatchers.queryParam("foo", "bad").match(this.request))
|
||||
.isInstanceOf(AssertionError.class)
|
||||
.hasMessageContaining("expected:<bad> but was:<bar>");
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.queryParam("foo", "bad").match(this.request))
|
||||
.withMessageContaining("expected:<bad> but was:<bar>");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryParamContains() throws Exception {
|
||||
void queryParamContains() throws Exception {
|
||||
this.request.setURI(new URI("http://www.foo.example/a?foo=bar&foo=baz"));
|
||||
|
||||
MockRestRequestMatchers.queryParam("foo", containsString("ba")).match(this.request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryParamContainsWithMissingValue() throws Exception {
|
||||
void queryParamContainsWithMissingValue() throws Exception {
|
||||
this.request.setURI(new URI("http://www.foo.example/a?foo=bar&foo=baz"));
|
||||
|
||||
assertThatThrownBy(() -> MockRestRequestMatchers.queryParam("foo", containsString("bx")).match(this.request))
|
||||
.isInstanceOf(AssertionError.class)
|
||||
.hasMessageContaining("was \"bar\"");
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.queryParam("foo", containsString("bx")).match(this.request))
|
||||
.withMessageContaining("was \"bar\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryParamListMissing() {
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.queryParam("foo", hasSize(2)).match(this.request))
|
||||
.withMessage("Expected query param <foo> to exist but was null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryParamListMatchers() throws IOException {
|
||||
this.request.setURI(URI.create("http://www.foo.example/a?foo=bar&foo=baz"));
|
||||
|
||||
MockRestRequestMatchers.queryParam("foo", containsInAnyOrder(endsWith("baz"), endsWith("bar"))).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", contains(is("bar"), is("baz"))).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", contains(is("bar"), anything())).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", hasItem(endsWith("baz"))).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", everyItem(startsWith("ba"))).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", hasSize(2)).match(this.request);
|
||||
|
||||
// These can be a bit ambiguous when reading the test (the compiler selects the list matcher):
|
||||
MockRestRequestMatchers.queryParam("foo", notNullValue()).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", is(anything())).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", allOf(notNullValue(), notNullValue())).match(this.request);
|
||||
|
||||
// These are not as ambiguous thanks to an inner matcher that is either obviously list-oriented,
|
||||
// string-oriented, or obviously a vararg of matchers
|
||||
|
||||
// list matcher version
|
||||
MockRestRequestMatchers.queryParam("foo", allOf(notNullValue(), hasSize(2))).match(this.request);
|
||||
|
||||
// vararg version
|
||||
MockRestRequestMatchers.queryParam("foo", allOf(notNullValue(), endsWith("ar"))).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", is((any(String.class)))).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", either(is("bar")).or(is(nullValue()))).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", is(notNullValue()), is(notNullValue())).match(this.request);
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryParamListContainsMismatch() {
|
||||
this.request.setURI(URI.create("http://www.foo.example/a?foo=bar&foo=baz"));
|
||||
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.queryParam("foo", contains(containsString("ba"))).match(this.request))
|
||||
.withMessageContainingAll(
|
||||
"Query param [foo] values",
|
||||
"Expected: iterable containing [a string containing \"ba\"]",
|
||||
"but: not matched: \"baz\"");
|
||||
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.queryParam("foo", hasItem(endsWith("ba"))).match(this.request))
|
||||
.withMessageContainingAll(
|
||||
"Query param [foo] values",
|
||||
"Expected: a collection containing a string ending with \"ba\"",
|
||||
"but: mismatches were: [was \"bar\", was \"baz\"]");
|
||||
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.queryParam("foo", everyItem(endsWith("ar"))).match(this.request))
|
||||
.withMessageContainingAll(
|
||||
"Query param [foo] values",
|
||||
"Expected: every item is a string ending with \"ar\"",
|
||||
"but: an item was \"baz\"");
|
||||
}
|
||||
|
||||
private static ThrowableTypeAssert<AssertionError> assertThatAssertionError() {
|
||||
return assertThatExceptionOfType(AssertionError.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,4 +31,9 @@ public class ForwardController {
|
||||
return "forward:/a";
|
||||
}
|
||||
|
||||
@RequestMapping("/infiniteForward")
|
||||
public String infiniteForward() {
|
||||
return "forward:/infiniteForward";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+9
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -29,6 +29,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
|
||||
/**
|
||||
@@ -80,6 +81,13 @@ public class MockMvcWebConnectionTests {
|
||||
assertThat(page.getWebResponse().getContentAsString()).isEqualTo("hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void infiniteForward() {
|
||||
this.webClient.setWebConnection(new MockMvcWebConnection(this.mockMvc, this.webClient, ""));
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.webClient.getPage("http://localhost/infiniteForward"))
|
||||
.withMessage("Forwarded more than 100 times in a row, potential infinite forward loop");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("resource")
|
||||
public void contextPathDoesNotStartWithSlash() throws IOException {
|
||||
|
||||
+4
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -84,11 +84,12 @@ public class GenericMessageEndpointFactory extends AbstractMessageEndpointFactor
|
||||
@Override
|
||||
public MessageEndpoint createEndpoint(XAResource xaResource) throws UnavailableException {
|
||||
GenericMessageEndpoint endpoint = (GenericMessageEndpoint) super.createEndpoint(xaResource);
|
||||
ProxyFactory proxyFactory = new ProxyFactory(getMessageListener());
|
||||
Object target = getMessageListener();
|
||||
ProxyFactory proxyFactory = new ProxyFactory(target);
|
||||
DelegatingIntroductionInterceptor introduction = new DelegatingIntroductionInterceptor(endpoint);
|
||||
introduction.suppressInterface(MethodInterceptor.class);
|
||||
proxyFactory.addAdvice(introduction);
|
||||
return (MessageEndpoint) proxyFactory.getProxy();
|
||||
return (MessageEndpoint) proxyFactory.getProxy(target.getClass().getClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.http.client.reactive;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Used to support Jetty 10.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Arjen Poutsma
|
||||
* @since 5.3.26
|
||||
*/
|
||||
abstract class Jetty10HttpFieldsHelper {
|
||||
|
||||
private static final boolean jetty10Present;
|
||||
|
||||
private static final Method requestGetHeadersMethod;
|
||||
|
||||
private static final Method responseGetHeadersMethod;
|
||||
|
||||
private static final Method getNameMethod;
|
||||
|
||||
private static final Method getValueMethod;
|
||||
|
||||
|
||||
static {
|
||||
try {
|
||||
ClassLoader classLoader = JettyClientHttpResponse.class.getClassLoader();
|
||||
Class<?> httpFieldsClass = classLoader.loadClass("org.eclipse.jetty.http.HttpFields");
|
||||
jetty10Present = httpFieldsClass.isInterface();
|
||||
requestGetHeadersMethod = Request.class.getMethod("getHeaders");
|
||||
responseGetHeadersMethod = Response.class.getMethod("getHeaders");
|
||||
Class<?> httpFieldClass = classLoader.loadClass("org.eclipse.jetty.http.HttpField");
|
||||
getNameMethod = httpFieldClass.getMethod("getName");
|
||||
getValueMethod = httpFieldClass.getMethod("getValue");
|
||||
}
|
||||
catch (ClassNotFoundException | NoSuchMethodException ex) {
|
||||
throw new IllegalStateException("No compatible Jetty version found", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean jetty10Present() {
|
||||
return jetty10Present;
|
||||
}
|
||||
|
||||
public static HttpHeaders getHttpHeaders(Request request) {
|
||||
Iterable<?> iterator = (Iterable<?>)
|
||||
ReflectionUtils.invokeMethod(requestGetHeadersMethod, request);
|
||||
return getHttpHeadersInternal(iterator);
|
||||
}
|
||||
|
||||
public static HttpHeaders getHttpHeaders(Response response) {
|
||||
Iterable<?> iterator = (Iterable<?>)
|
||||
ReflectionUtils.invokeMethod(responseGetHeadersMethod, response);
|
||||
return getHttpHeadersInternal(iterator);
|
||||
}
|
||||
|
||||
private static HttpHeaders getHttpHeadersInternal(@Nullable Iterable<?> iterator) {
|
||||
Assert.notNull(iterator, "Iterator must not be null");
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
for (Object field : iterator) {
|
||||
String name = (String) ReflectionUtils.invokeMethod(getNameMethod, field);
|
||||
Assert.notNull(name, "Header name must not be null");
|
||||
String value = (String) ReflectionUtils.invokeMethod(getValueMethod, field);
|
||||
headers.add(name, value);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
+8
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -37,6 +37,7 @@ import org.springframework.core.io.buffer.PooledDataBuffer;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* {@link ClientHttpRequest} implementation for the Jetty ReactiveStreams HTTP client.
|
||||
@@ -54,7 +55,6 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest {
|
||||
private final ReactiveRequest.Builder builder;
|
||||
|
||||
|
||||
|
||||
public JettyClientHttpRequest(Request jettyRequest, DataBufferFactory bufferFactory) {
|
||||
this.jettyRequest = jettyRequest;
|
||||
this.bufferFactory = bufferFactory;
|
||||
@@ -96,8 +96,7 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest {
|
||||
.as(chunks -> ReactiveRequest.Content.fromPublisher(chunks, getContentType()));
|
||||
this.builder.content(content);
|
||||
sink.success();
|
||||
})
|
||||
.then(doCommit());
|
||||
}).then(doCommit());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,7 +143,11 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest {
|
||||
|
||||
@Override
|
||||
protected HttpHeaders initReadOnlyHeaders() {
|
||||
return HttpHeaders.readOnlyHttpHeaders(new JettyHeadersAdapter(this.jettyRequest.getHeaders()));
|
||||
MultiValueMap<String, String> headers = (Jetty10HttpFieldsHelper.jetty10Present() ?
|
||||
Jetty10HttpFieldsHelper.getHttpHeaders(this.jettyRequest) :
|
||||
new JettyHeadersAdapter(this.jettyRequest.getHeaders()));
|
||||
|
||||
return HttpHeaders.readOnlyHttpHeaders(headers);
|
||||
}
|
||||
|
||||
public ReactiveRequest toReactiveRequest() {
|
||||
|
||||
+3
-58
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,13 +16,11 @@
|
||||
|
||||
package org.springframework.http.client.reactive;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.HttpCookie;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.reactive.client.ReactiveResponse;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -32,11 +30,9 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link ClientHttpResponse} implementation for the Jetty ReactiveStreams HTTP client.
|
||||
@@ -50,10 +46,6 @@ class JettyClientHttpResponse implements ClientHttpResponse {
|
||||
|
||||
private static final Pattern SAMESITE_PATTERN = Pattern.compile("(?i).*SameSite=(Strict|Lax|None).*");
|
||||
|
||||
private static final ClassLoader classLoader = JettyClientHttpResponse.class.getClassLoader();
|
||||
|
||||
private static final boolean jetty10Present;
|
||||
|
||||
|
||||
private final ReactiveResponse reactiveResponse;
|
||||
|
||||
@@ -62,23 +54,12 @@ class JettyClientHttpResponse implements ClientHttpResponse {
|
||||
private final HttpHeaders headers;
|
||||
|
||||
|
||||
static {
|
||||
try {
|
||||
Class<?> httpFieldsClass = classLoader.loadClass("org.eclipse.jetty.http.HttpFields");
|
||||
jetty10Present = httpFieldsClass.isInterface();
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new IllegalStateException("No compatible Jetty version found", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public JettyClientHttpResponse(ReactiveResponse reactiveResponse, Publisher<DataBuffer> content) {
|
||||
this.reactiveResponse = reactiveResponse;
|
||||
this.content = Flux.from(content);
|
||||
|
||||
MultiValueMap<String, String> headers = (jetty10Present ?
|
||||
Jetty10HttpFieldsHelper.getHttpHeaders(reactiveResponse) :
|
||||
MultiValueMap<String, String> headers = (Jetty10HttpFieldsHelper.jetty10Present() ?
|
||||
Jetty10HttpFieldsHelper.getHttpHeaders(reactiveResponse.getResponse()) :
|
||||
new JettyHeadersAdapter(reactiveResponse.getHeaders()));
|
||||
|
||||
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
|
||||
@@ -132,40 +113,4 @@ class JettyClientHttpResponse implements ClientHttpResponse {
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
|
||||
private static class Jetty10HttpFieldsHelper {
|
||||
|
||||
private static final Method getHeadersMethod;
|
||||
|
||||
private static final Method getNameMethod;
|
||||
|
||||
private static final Method getValueMethod;
|
||||
|
||||
static {
|
||||
try {
|
||||
getHeadersMethod = Response.class.getMethod("getHeaders");
|
||||
Class<?> type = classLoader.loadClass("org.eclipse.jetty.http.HttpField");
|
||||
getNameMethod = type.getMethod("getName");
|
||||
getValueMethod = type.getMethod("getValue");
|
||||
}
|
||||
catch (ClassNotFoundException | NoSuchMethodException ex) {
|
||||
throw new IllegalStateException("No compatible Jetty version found", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static HttpHeaders getHttpHeaders(ReactiveResponse response) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
Iterable<?> iterator = (Iterable<?>)
|
||||
ReflectionUtils.invokeMethod(getHeadersMethod, response.getResponse());
|
||||
Assert.notNull(iterator, "Iterator must not be null");
|
||||
for (Object field : iterator) {
|
||||
String name = (String) ReflectionUtils.invokeMethod(getNameMethod, field);
|
||||
Assert.notNull(name, "Header name must not be null");
|
||||
String value = (String) ReflectionUtils.invokeMethod(getValueMethod, field);
|
||||
headers.add(name, value);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -265,7 +265,7 @@ public class ProtobufDecoder extends ProtobufCodecSupport implements Decoder<Mes
|
||||
* Parse message size as a varint from the input stream, updating {@code messageBytesToRead} and
|
||||
* {@code offset} fields if needed to allow processing of upcoming chunks.
|
||||
* Inspired from {@link CodedInputStream#readRawVarint32(int, java.io.InputStream)}
|
||||
* @return {code true} when the message size is parsed successfully, {code false} when the message size is
|
||||
* @return {@code true} when the message size is parsed successfully, {@code false} when the message size is
|
||||
* truncated
|
||||
* @see <a href="https://developers.google.com/protocol-buffers/docs/encoding#varints">Base 128 Varints</a>
|
||||
*/
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -57,7 +57,6 @@ import com.fasterxml.jackson.dataformat.xml.XmlFactory;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.FatalBeanException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -837,7 +836,7 @@ public class Jackson2ObjectMapperBuilder {
|
||||
objectMapper.configure((MapperFeature) feature, enabled);
|
||||
}
|
||||
else {
|
||||
throw new FatalBeanException("Unknown feature class: " + feature.getClass().getName());
|
||||
throw new IllegalArgumentException("Unknown feature class: " + feature.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-34
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,7 +24,6 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.cookie.Cookie;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -80,40 +79,15 @@ class ReactorServerHttpRequest extends AbstractServerHttpRequest {
|
||||
return new URI(resolveBaseUrl(request) + resolveRequestUri(request));
|
||||
}
|
||||
|
||||
private static URI resolveBaseUrl(HttpServerRequest request) throws URISyntaxException {
|
||||
String scheme = getScheme(request);
|
||||
String header = request.requestHeaders().get(HttpHeaderNames.HOST);
|
||||
if (header != null) {
|
||||
final int portIndex;
|
||||
if (header.startsWith("[")) {
|
||||
portIndex = header.indexOf(':', header.indexOf(']'));
|
||||
}
|
||||
else {
|
||||
portIndex = header.indexOf(':');
|
||||
}
|
||||
if (portIndex != -1) {
|
||||
try {
|
||||
return new URI(scheme, null, header.substring(0, portIndex),
|
||||
Integer.parseInt(header.substring(portIndex + 1)), null, null, null);
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new URISyntaxException(header, "Unable to parse port", portIndex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return new URI(scheme, header, null, null);
|
||||
}
|
||||
}
|
||||
else {
|
||||
InetSocketAddress localAddress = request.hostAddress();
|
||||
Assert.state(localAddress != null, "No host address available");
|
||||
return new URI(scheme, null, localAddress.getHostString(),
|
||||
localAddress.getPort(), null, null, null);
|
||||
}
|
||||
private static String resolveBaseUrl(HttpServerRequest request) {
|
||||
String scheme = request.scheme();
|
||||
int port = request.hostPort();
|
||||
return scheme + "://" + request.hostName() + (usePort(scheme, port) ? ":" + port : "");
|
||||
}
|
||||
|
||||
private static String getScheme(HttpServerRequest request) {
|
||||
return request.scheme();
|
||||
private static boolean usePort(String scheme, int port) {
|
||||
return ((scheme.equals("http") || scheme.equals("ws")) && (port != 80)) ||
|
||||
((scheme.equals("https") || scheme.equals("wss")) && (port != 443));
|
||||
}
|
||||
|
||||
private static String resolveRequestUri(HttpServerRequest request) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -818,7 +818,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
|
||||
logger.debug("Response " + (status != null ? status : code));
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
logger.debug("Failed to obtain response status code", ex);
|
||||
}
|
||||
}
|
||||
if (hasError) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -133,22 +133,6 @@ public class ContextLoader {
|
||||
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
|
||||
|
||||
|
||||
private static final Properties defaultStrategies;
|
||||
|
||||
static {
|
||||
// Load default strategy implementations from properties file.
|
||||
// This is currently strictly internal and not meant to be customized
|
||||
// by application developers.
|
||||
try {
|
||||
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
|
||||
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Map from (thread context) ClassLoader to corresponding 'current' WebApplicationContext.
|
||||
*/
|
||||
@@ -162,6 +146,8 @@ public class ContextLoader {
|
||||
@Nullable
|
||||
private static volatile WebApplicationContext currentContext;
|
||||
|
||||
@Nullable
|
||||
private static Properties defaultStrategies;
|
||||
|
||||
/**
|
||||
* The root WebApplicationContext instance that this loader manages.
|
||||
@@ -357,6 +343,18 @@ public class ContextLoader {
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (defaultStrategies == null) {
|
||||
// Load default strategy implementations from properties file.
|
||||
// This is currently strictly internal and not meant to be customized
|
||||
// by application developers.
|
||||
try {
|
||||
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
|
||||
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
|
||||
try {
|
||||
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -66,7 +66,7 @@ public abstract class CorsUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the request is a valid CORS pre-flight one by checking {code OPTIONS} method with
|
||||
* Returns {@code true} if the request is a valid CORS pre-flight one by checking {@code OPTIONS} method with
|
||||
* {@code Origin} and {@code Access-Control-Request-Method} headers presence.
|
||||
*/
|
||||
public static boolean isPreFlightRequest(HttpServletRequest request) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -45,7 +45,7 @@ public abstract class CorsUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the request is a valid CORS pre-flight one by checking {code OPTIONS} method with
|
||||
* Returns {@code true} if the request is a valid CORS pre-flight one by checking {@code OPTIONS} method with
|
||||
* {@code Origin} and {@code Access-Control-Request-Method} headers presence.
|
||||
*/
|
||||
public static boolean isPreFlightRequest(ServerHttpRequest request) {
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+2
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -83,11 +83,9 @@ import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.FatalBeanException;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
@@ -106,7 +104,7 @@ class Jackson2ObjectMapperBuilderTests {
|
||||
|
||||
@Test
|
||||
void unknownFeature() {
|
||||
assertThatExceptionOfType(FatalBeanException.class).isThrownBy(() ->
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
Jackson2ObjectMapperBuilder.json().featuresToEnable(Boolean.TRUE).build());
|
||||
}
|
||||
|
||||
|
||||
+3
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -62,10 +62,8 @@ import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.FatalBeanException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Test cases for {@link Jackson2ObjectMapperFactoryBean}.
|
||||
@@ -88,8 +86,7 @@ public class Jackson2ObjectMapperFactoryBeanTests {
|
||||
@Test
|
||||
public void unknownFeature() {
|
||||
this.factory.setFeaturesToEnable(Boolean.TRUE);
|
||||
assertThatExceptionOfType(FatalBeanException.class).isThrownBy(
|
||||
this.factory::afterPropertiesSet);
|
||||
assertThatIllegalArgumentException().isThrownBy(this.factory::afterPropertiesSet);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+16
-18
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,8 +17,10 @@
|
||||
package org.springframework.web.testfixture.method;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -28,16 +30,14 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.target.EmptyTargetSource;
|
||||
import org.springframework.cglib.core.SpringNamingPolicy;
|
||||
import org.springframework.cglib.proxy.Callback;
|
||||
import org.springframework.cglib.proxy.Enhancer;
|
||||
import org.springframework.cglib.proxy.Factory;
|
||||
import org.springframework.cglib.proxy.MethodInterceptor;
|
||||
import org.springframework.cglib.proxy.MethodProxy;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodIntrospector;
|
||||
@@ -122,6 +122,7 @@ import static java.util.stream.Collectors.joining;
|
||||
* </pre>
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @since 5.0
|
||||
*/
|
||||
public class ResolvableMethod {
|
||||
@@ -614,16 +615,10 @@ public class ResolvableMethod {
|
||||
}
|
||||
|
||||
|
||||
private static class MethodInvocationInterceptor
|
||||
implements org.springframework.cglib.proxy.MethodInterceptor, MethodInterceptor {
|
||||
private static class MethodInvocationInterceptor implements MethodInterceptor, InvocationHandler {
|
||||
|
||||
private Method invokedMethod;
|
||||
|
||||
|
||||
Method getInvokedMethod() {
|
||||
return this.invokedMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) {
|
||||
@@ -638,20 +633,23 @@ public class ResolvableMethod {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object invoke(org.aopalliance.intercept.MethodInvocation inv) throws Throwable {
|
||||
return intercept(inv.getThis(), inv.getMethod(), inv.getArguments(), null);
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
return intercept(proxy, method, args, null);
|
||||
}
|
||||
|
||||
Method getInvokedMethod() {
|
||||
return this.invokedMethod;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T initProxy(Class<?> type, MethodInvocationInterceptor interceptor) {
|
||||
Assert.notNull(type, "'type' must not be null");
|
||||
if (type.isInterface()) {
|
||||
ProxyFactory factory = new ProxyFactory(EmptyTargetSource.INSTANCE);
|
||||
factory.addInterface(type);
|
||||
factory.addInterface(Supplier.class);
|
||||
factory.addAdvice(interceptor);
|
||||
return (T) factory.getProxy();
|
||||
return (T) Proxy.newProxyInstance(type.getClassLoader(),
|
||||
new Class<?>[] {type, Supplier.class},
|
||||
interceptor);
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
+7
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -94,7 +94,7 @@ public class RequestedContentTypeResolverBuilder {
|
||||
return exchange -> {
|
||||
for (RequestedContentTypeResolver resolver : resolvers) {
|
||||
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
|
||||
if (mediaTypes.equals(RequestedContentTypeResolver.MEDIA_TYPE_ALL_LIST)) {
|
||||
if (isMediaTypeAll(mediaTypes)) {
|
||||
continue;
|
||||
}
|
||||
return mediaTypes;
|
||||
@@ -103,6 +103,11 @@ public class RequestedContentTypeResolverBuilder {
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isMediaTypeAll(List<MediaType> mediaTypes) {
|
||||
return mediaTypes.size() == 1
|
||||
&& mediaTypes.get(0).removeQualityValue().equals(MediaType.ALL);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper to create and configure {@link ParameterContentTypeResolver}.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user