Compare commits

..

1 Commits

Author SHA1 Message Date
Spring Builds 2aeddb4fcb Release v5.3.25 2023-01-11 11:05:48 +00:00
126 changed files with 1137 additions and 2153 deletions
-1
View File
@@ -28,7 +28,6 @@ buildSrc/build
/integration-tests/build
/src/asciidoc/build
target/
/target/
# Eclipse artifacts, including WTP generated manifests
.classpath
+1 -1
View File
@@ -1,3 +1,3 @@
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=8.0.362-librca
java=8.0.352-librca
+15 -15
View File
@@ -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.90.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.30"
mavenBom "io.netty:netty-bom:4.1.86.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.27"
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR13"
mavenBom "io.rsocket:rsocket-bom:1.1.3"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.51.v20230217"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.50.v20221201"
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.20.0') {
dependencySet(group: 'org.apache.logging.log4j', version: '2.19.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.33"
dependency "org.yaml:snakeyaml:1.30"
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.73') {
dependencySet(group: 'org.apache.tomcat', version: '9.0.70') {
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.73') {
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.70') {
entry 'tomcat-embed-core'
entry 'tomcat-embed-websocket'
}
dependencySet(group: 'io.undertow', version: '2.2.23.Final') {
dependencySet(group: 'io.undertow', version: '2.2.22.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.2"
dependency "org.assertj:assertj-core:3.24.1"
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.70.0") {
dependency("net.sourceforge.htmlunit:htmlunit:2.69.0") {
exclude group: "commons-logging", name: "commons-logging"
}
dependency("org.seleniumhq.selenium:htmlunit-driver:2.70.0") {
dependency("org.seleniumhq.selenium:htmlunit-driver:2.67.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.9.1"
toolVersion = "10.6.0"
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://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://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://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/",
+3
View File
@@ -8,4 +8,7 @@ milestone: "5.3.x"
build-name: "spring-framework"
pipeline-name: "spring-framework"
concourse-url: "https://ci.spring.io"
registry-mirror-host: docker.repo.spring.io
registry-mirror-username: ((artifactory-username))
registry-mirror-password: ((artifactory-password))
task-timeout: 1h00m
+9 -11
View File
@@ -23,6 +23,11 @@ anchors:
docker-resource-source: &docker-resource-source
username: ((docker-hub-username))
password: ((docker-hub-password))
tag: ((milestone))
registry-mirror-vars: &registry-mirror-vars
registry-mirror-host: ((registry-mirror-host))
registry-mirror-username: ((registry-mirror-username))
registry-mirror-password: ((registry-mirror-password))
slack-fail-params: &slack-fail-params
text: >
:concourse-failed: <https://ci.spring.io/teams/${BUILD_TEAM_NAME}/pipelines/${BUILD_PIPELINE_NAME}/jobs/${BUILD_JOB_NAME}/builds/${BUILD_NAME}|${BUILD_PIPELINE_NAME} ${BUILD_JOB_NAME} failed!>
@@ -43,37 +48,31 @@ resource_types:
- name: registry-image
type: registry-image
source:
<<: *docker-resource-source
repository: concourse/registry-image-resource
tag: 1.7.1
tag: 1.5.0
- name: artifactory-resource
type: registry-image
source:
<<: *docker-resource-source
repository: springio/artifactory-resource
tag: 0.0.18
tag: 0.0.17
- name: github-release
type: registry-image
source:
<<: *docker-resource-source
repository: concourse/github-release-resource
tag: 1.8.0
tag: 1.5.5
- 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:
@@ -102,7 +101,6 @@ resources:
source:
<<: *docker-resource-source
repository: ((docker-hub-organization))/spring-framework-ci
tag: ((milestone))
- name: artifactory-repo
type: artifactory-resource
icon: package-variant
@@ -170,7 +168,7 @@ jobs:
image: ci-image
vars:
ci-image-name: ci-image
<<: *docker-resource-source
<<: *registry-mirror-vars
- put: ci-image
params:
image: ci-image/image.tar
+5 -3
View File
@@ -4,9 +4,11 @@ image_resource:
type: registry-image
source:
repository: concourse/oci-build-task
tag: 0.10.0
username: ((docker-hub-username))
password: ((docker-hub-password))
tag: 0.9.1
registry_mirror:
host: ((registry-mirror-host))
username: ((registry-mirror-username))
password: ((registry-mirror-password))
inputs:
- name: ci-images-git-repo
outputs:
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.3.26
version=5.3.25
org.gradle.jvmargs=-Xmx2048m
org.gradle.caching=true
org.gradle.parallel=true
+2 -2
View File
@@ -7,8 +7,8 @@ pluginManagement {
}
plugins {
id "com.gradle.enterprise" version "3.12.1"
id "io.spring.ge.conventions" version "0.0.13"
id "com.gradle.enterprise" version "3.11.1"
id "io.spring.ge.conventions" version "0.0.11"
}
include "spring-aop"
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,10 @@ 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
@@ -31,13 +35,13 @@ import org.springframework.lang.Nullable;
public interface MetadataAwareAspectInstanceFactory extends AspectInstanceFactory {
/**
* Get the AspectJ AspectMetadata for this factory's aspect.
* Return the AspectJ AspectMetadata for this factory's aspect.
* @return the aspect metadata
*/
AspectMetadata getAspectMetadata();
/**
* Get the best possible creation mutex for this factory.
* Return the best possible creation mutex for this factory.
* @return the mutex object (may be {@code null} for no mutex to use)
* @since 4.3
*/
@@ -1,10 +0,0 @@
/**
* 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-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -247,8 +247,7 @@ public abstract class BeanUtils {
// A single public constructor
return (Constructor<T>) ctors[0];
}
else if (ctors.length == 0) {
// No public constructors -> check non-public
else if (ctors.length == 0){
ctors = clazz.getDeclaredConstructors();
if (ctors.length == 1) {
// A single non-public constructor, e.g. from a non-public record type
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +25,6 @@ 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;
@@ -241,11 +240,10 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
}
}
if (targetAnnotation == null) {
BeanFactory beanFactory = getBeanFactory();
// Look for matching annotation on the target class
if (beanFactory != null) {
if (getBeanFactory() != null) {
try {
Class<?> beanType = beanFactory.getType(bdHolder.getBeanName());
Class<?> beanType = getBeanFactory().getType(bdHolder.getBeanName());
if (beanType != null) {
targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(beanType), type);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -184,9 +184,8 @@ public abstract class YamlProcessor {
protected Yaml createYaml() {
LoaderOptions loaderOptions = new LoaderOptions();
loaderOptions.setAllowDuplicateKeys(false);
DumperOptions dumperOptions = new DumperOptions();
return new Yaml(new FilteringConstructor(loaderOptions), new Representer(dumperOptions),
dumperOptions, loaderOptions);
return new Yaml(new FilteringConstructor(loaderOptions), new Representer(),
new DumperOptions(), loaderOptions);
}
private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -214,15 +214,12 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
}
catch (Throwable 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);
}
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);
}
}
}
@@ -243,15 +240,12 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
}
catch (Throwable 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);
}
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);
}
}
}
@@ -326,23 +320,18 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
}
catch (InvocationTargetException ex) {
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());
}
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());
}
}
catch (Throwable ex) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to invoke custom destroy method '" + this.destroyMethodName +
"' on bean with name '" + this.beanName + "'", ex);
}
logger.warn("Failed to invoke custom destroy method '" + this.destroyMethodName +
"' on bean with name '" + this.beanName + "'", ex);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -547,8 +547,7 @@ public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
* @see DefaultNamespaceHandlerResolver#DefaultNamespaceHandlerResolver(ClassLoader)
*/
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
ResourceLoader resourceLoader = getResourceLoader();
ClassLoader cl = (resourceLoader != null ? resourceLoader.getClassLoader() : getBeanClassLoader());
ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
return new DefaultNamespaceHandlerResolver(cl);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -395,7 +395,6 @@ class BeanWrapperTests extends AbstractPropertyAccessorTests {
}
@SuppressWarnings("try")
public static class ActiveResource implements AutoCloseable {
public ActiveResource getResource() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -436,6 +436,7 @@ 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();
@@ -777,6 +778,21 @@ 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();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,7 +51,6 @@ import org.springframework.util.ObjectUtils;
* @author Juergen Hoeller
* @author Stephane Nicoll
* @author Sam Brannen
* @author Brian Clozel
* @since 4.3
* @see CaffeineCache
*/
@@ -189,13 +188,8 @@ public class CaffeineCacheManager implements CacheManager {
@Override
@Nullable
public Cache getCache(String name) {
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);
}
return this.cacheMap.computeIfAbsent(name, cacheName ->
this.dynamic ? createCaffeineCache(cacheName) : null);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2018 the original author 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;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -729,11 +729,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
/**
* Modify the application context's internal bean factory after its standard
* 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.
* 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.
* @param beanFactory the bean factory used by the application context
*/
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
@@ -82,17 +82,6 @@ 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.V21) {
if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V19) {
throw new IllegalArgumentException(
"Unsupported class file major version " + readShort(classFileOffset + 6));
}
@@ -308,7 +308,6 @@ 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) {
@@ -377,7 +376,7 @@ public class ClassReader {
}
/**
* Returns the internal name of the super class (see {@link Type#getInternalName()}). For
* Returns the internal of 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,8 +42,7 @@ public final class ClassTooLargeException extends IndexOutOfBoundsException {
/**
* Constructs a new {@link ClassTooLargeException}.
*
* @param className the internal name of the class (see {@link
* org.objectweb.asm.Type#getInternalName()}).
* @param className the internal name of the class.
* @param constantPoolCount the number of constant pool items of the class.
*/
public ClassTooLargeException(final String className, final int constantPoolCount) {
@@ -53,7 +52,7 @@ public final class ClassTooLargeException extends IndexOutOfBoundsException {
}
/**
* Returns the internal name of the class (see {@link org.objectweb.asm.Type#getInternalName()}).
* Returns the internal name of the class.
*
* @return the internal name of the class.
*/
@@ -81,15 +81,6 @@ 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.
*
@@ -164,8 +155,7 @@ 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 (see {@link
* Type#getInternalName()}).
* @param nestHost the internal name of the host class of the nest.
*/
public void visitNestHost(final String nestHost) {
if (api < Opcodes.ASM7) {
@@ -177,19 +167,14 @@ public abstract class ClassVisitor {
}
/**
* 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.
* Visits the enclosing class of the class. This method must be called only if the class has an
* enclosing class.
*
* @param owner internal name of the enclosing class of the class (see {@link
* Type#getInternalName()}).
* @param owner internal name of the enclosing class of the class.
* @param name the name of the method that contains the class, or {@literal null} if 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).
* not enclosed in a method of its enclosing class.
* @param descriptor the descriptor of the method that contains the class, or {@literal null} if
* 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).
* the class is not enclosed in a method of its enclosing class.
*/
public void visitOuterClass(final String owner, final String name, final String descriptor) {
if (cv != null) {
@@ -256,7 +241,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 (see {@link Type#getInternalName()}).
* @param nestMember the internal name of a nest member.
*/
public void visitNestMember(final String nestMember) {
if (api < Opcodes.ASM7) {
@@ -271,8 +256,7 @@ 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 (see {@link
* Type#getInternalName()}).
* @param permittedSubclass the internal name of a permitted subclass.
*/
public void visitPermittedSubclass(final String permittedSubclass) {
if (api < Opcodes.ASM9) {
@@ -285,18 +269,15 @@ public abstract class ClassVisitor {
/**
* Visits information about an inner class. This inner class is not necessarily a member of the
* 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.
* class being visited.
*
* @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.
* @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.
*/
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 (see {@link Type#getInternalName()}).
* @param value the internal name of the class.
* @return the index of a new or already existing class reference item.
*/
public int newClass(final String value) {
@@ -894,8 +894,7 @@ 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 (see {@link
* Type#getInternalName()}).
* @param owner the internal name of the field or method owner class.
* @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.
@@ -917,8 +916,7 @@ 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 (see {@link
* Type#getInternalName()}).
* @param owner the internal name of the field or method owner class.
* @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.
@@ -980,7 +978,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 (see {@link Type#getInternalName()}).
* @param owner the internal name of the field's owner class.
* @param name the field's name.
* @param descriptor the field's descriptor.
* @return the index of a new or already existing field reference item.
@@ -994,8 +992,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 method's owner class (see {@link
* Type#getInternalName()}).
* @param owner the internal name of the method's owner class.
* @param name the method's name.
* @param descriptor the method's descriptor.
* @param isInterface {@literal true} if {@code owner} is an interface.
@@ -1031,10 +1028,9 @@ 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 (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()}).
* @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.
*/
protected String getCommonSuperClass(final String type1, final String type2) {
ClassLoader classLoader = getClassLoader();
@@ -78,15 +78,6 @@ 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,12 +367,11 @@ class Frame {
typeValue = REFERENCE_KIND | symbolTable.addType(internalName);
break;
default:
throw new IllegalArgumentException(
"Invalid descriptor fragment: " + buffer.substring(elementDescriptorOffset));
throw new IllegalArgumentException();
}
return ((elementDescriptorOffset - offset) << DIM_SHIFT) | typeValue;
default:
throw new IllegalArgumentException("Invalid descriptor: " + buffer.substring(offset));
throw new IllegalArgumentException();
}
}
@@ -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 (see {@link Type#getInternalName()}).
* handle.
* @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 (see {@link Type#getInternalName()}).
* handle.
* @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,8 +118,7 @@ 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
* (see {@link Type#getInternalName()}).
* @return the internal name of the class that owns the field or method designated by this handle.
*/
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 (see {@link Type#getInternalName()}).
* @param className the internal name of the owner class.
* @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 (see {@link Type#getInternalName()}).
* @return the internal name of the owner class.
*/
public String getClassName() {
return className;
@@ -94,16 +94,6 @@ 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
// -----------------------------------------------------------------------------------------------
@@ -130,7 +120,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,
* exactly one visit method must be called on this annotation visitor, followed by visitEnd.
* exacly one visit method must be called on this annotation visitor, followed by visitEnd.
*/
public AnnotationVisitor visitAnnotationDefault() {
if (mv != null) {
@@ -283,17 +273,15 @@ 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. Long and double values
* count for one variable.
* @param numLocal the number of local variables in the visited frame.
* @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, 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.
* 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.
* @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
@@ -372,7 +360,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 (see {@link Type#getInternalName()}).
* a class as parameter.
*
* @param opcode the opcode of the type instruction to be visited. This opcode is either NEW,
* ANEWARRAY, CHECKCAST or INSTANCEOF.
@@ -564,12 +552,12 @@ public abstract class MethodVisitor {
/**
* Visits an IINC instruction.
*
* @param varIndex index of the local variable to be incremented.
* @param var index of the local variable to be incremented.
* @param increment amount to increment the local variable by.
*/
public void visitIincInsn(final int varIndex, final int increment) {
public void visitIincInsn(final int var, final int increment) {
if (mv != null) {
mv.visitIincInsn(varIndex, increment);
mv.visitIincInsn(var, increment);
}
}
@@ -655,9 +643,8 @@ 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 (see {@link
* Type#getInternalName()}), or {@literal null} to catch any exceptions (for "finally"
* blocks).
* @param type the internal name of the type of exceptions handled by the handler, 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,8 +284,6 @@ 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}.
*/
protected RecordComponentVisitor delegate;
/*package-private*/ RecordComponentVisitor delegate;
/**
* Constructs a new {@link RecordComponentVisitor}.
@@ -83,8 +83,7 @@ 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 Record attribute. */
/** The descriptor_index field of the 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 (see {@link Type#getInternalName()}).
* @param internalName an internal name.
* @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 &lt;&lt; 2) | returnSize} (argumentsSize is therefore equal to {@code
* i &gt;&gt; 2}, and returnSize to {@code i &amp; 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 &lt;&lt; 2) | returnSize} (argumentsSize is therefore equal to {@code
* i &gt;&gt; 2}, and returnSize to {@code i &amp; 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-2023 the original author or authors.
* Copyright 2002-2019 the original author 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,13 +22,12 @@ 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 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 Java 8 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)
@@ -78,7 +77,7 @@ public abstract class LogMessage implements CharSequence {
/**
* Build a lazily resolving message from the given supplier.
* @param supplier the supplier (typically bound to a lambda expression)
* @param supplier the supplier (typically bound to a Java 8 lambda expression)
* @see #toString()
*/
public static LogMessage of(Supplier<? extends CharSequence> supplier) {
@@ -88,68 +87,63 @@ 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 (can be {@code null})
* @param arg1 the argument
* @see String#format(String, Object...)
*/
public static LogMessage format(String format, @Nullable Object arg1) {
public static LogMessage format(String format, 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 (can be {@code null})
* @param arg2 the second argument (can be {@code null})
* @param arg1 the first argument
* @param arg2 the second argument
* @see String#format(String, Object...)
*/
public static LogMessage format(String format, @Nullable Object arg1, @Nullable Object arg2) {
public static LogMessage format(String format, Object arg1, 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 (can be {@code null})
* @param arg2 the second argument (can be {@code null})
* @param arg3 the third argument (can be {@code null})
* @param arg1 the first argument
* @param arg2 the second argument
* @param arg3 the third argument
* @see String#format(String, Object...)
*/
public static LogMessage format(String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3) {
public static LogMessage format(String format, Object arg1, Object arg2, 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 (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})
* @param arg1 the first argument
* @param arg2 the second argument
* @param arg3 the third argument
* @param arg4 the fourth argument
* @see String#format(String, Object...)
*/
public static LogMessage format(String format, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3,
@Nullable Object arg4) {
public static LogMessage format(String format, Object arg1, Object arg2, Object arg3, 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 (can be {@code null} and can contain {@code null}
* elements)
* @param args the varargs array (costly, prefer individual arguments)
* @see String#format(String, Object...)
*/
public static LogMessage format(String format, @Nullable Object... args) {
public static LogMessage format(String format, Object... args) {
return new FormatMessageX(format, args);
}
private static final class SupplierMessage extends LogMessage {
private final Supplier<? extends CharSequence> supplier;
private Supplier<? extends CharSequence> supplier;
SupplierMessage(Supplier<? extends CharSequence> supplier) {
Assert.notNull(supplier, "Supplier must not be null");
@@ -176,10 +170,9 @@ public abstract class LogMessage implements CharSequence {
private static final class FormatMessage1 extends FormatMessage {
@Nullable
private final Object arg1;
FormatMessage1(String format, @Nullable Object arg1) {
FormatMessage1(String format, Object arg1) {
super(format);
this.arg1 = arg1;
}
@@ -193,13 +186,11 @@ 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, @Nullable Object arg1, @Nullable Object arg2) {
FormatMessage2(String format, Object arg1, Object arg2) {
super(format);
this.arg1 = arg1;
this.arg2 = arg2;
@@ -214,16 +205,13 @@ 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, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3) {
FormatMessage3(String format, Object arg1, Object arg2, Object arg3) {
super(format);
this.arg1 = arg1;
this.arg2 = arg2;
@@ -239,20 +227,15 @@ 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, @Nullable Object arg1, @Nullable Object arg2, @Nullable Object arg3,
@Nullable Object arg4) {
FormatMessage4(String format, Object arg1, Object arg2, Object arg3, Object arg4) {
super(format);
this.arg1 = arg1;
this.arg2 = arg2;
@@ -269,10 +252,9 @@ public abstract class LogMessage implements CharSequence {
private static final class FormatMessageX extends FormatMessage {
@Nullable
private final Object[] args;
FormatMessageX(String format, @Nullable Object... args) {
FormatMessageX(String format, Object... args) {
super(format);
this.args = args;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2018 the original author 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,9 +90,4 @@ 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-2023 the original author or authors.
* Copyright 2002-2018 the original author 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");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -264,15 +264,7 @@ 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}''"),
/** @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}''");
"Array declares too many elements, exceeding the threshold of ''{0}''");
private final Kind kind;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2019 the original author 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;
return '#' + this.name + sj.toString();
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -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;
return this.name + sj.toString();
}
/**
@@ -288,9 +288,12 @@ public class MethodReference extends SpelNodeImpl {
if (executor.didArgumentConversionOccur()) {
return false;
}
Class<?> clazz = executor.getMethod().getDeclaringClass();
return (Modifier.isPublic(clazz.getModifiers()) || executor.getPublicDeclaringClass() != null);
if (!Modifier.isPublic(clazz.getModifiers()) && executor.getPublicDeclaringClass() == null) {
return false;
}
return true;
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2019 the original author 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,8 +25,6 @@ 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;
@@ -54,13 +52,6 @@ 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);
}
@@ -118,13 +109,10 @@ public class OpMultiply extends Operator {
}
if (leftOperand instanceof String && rightOperand instanceof Integer) {
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);
int repeats = (Integer) rightOperand;
StringBuilder result = new StringBuilder();
for (int i = 0; i < repeats; i++) {
result.append(leftOperand);
}
return new TypedValue(result.toString());
}
@@ -132,13 +120,6 @@ 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()) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2019 the original author 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,41 +36,19 @@ 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;
/**
* 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;
private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>();
/**
* 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) {
this(new ConcurrentHashMap<>(), startPos, endPos, operands);
super("matches", 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.
@@ -84,28 +62,26 @@ 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);
String input = leftOp.getValue(state, String.class);
if (input == null) {
if (left == 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 {
Pattern pattern = this.patternCache.get(regex);
String rightString = (String) right;
Pattern pattern = this.patternCache.get(rightString);
if (pattern == null) {
checkRegexLength(regex);
pattern = Pattern.compile(regex);
this.patternCache.putIfAbsent(regex, pattern);
pattern = Pattern.compile(rightString);
this.patternCache.putIfAbsent(rightString, pattern);
}
Matcher matcher = pattern.matcher(new MatcherInput(input, new AccessCount()));
Matcher matcher = pattern.matcher(new MatcherInput(left, new AccessCount()));
return BooleanTypedValue.forValue(matcher.matches());
}
catch (PatternSyntaxException ex) {
@@ -118,13 +94,6 @@ 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 {
@@ -142,7 +111,7 @@ public class OperatorMatches extends Operator {
private final CharSequence value;
private final AccessCount access;
private AccessCount access;
public MatcherInput(CharSequence value, AccessCount access) {
this.value = value;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2019 the original author 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,8 +21,6 @@ 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;
@@ -85,7 +83,6 @@ import org.springframework.util.StringUtils;
* @author Andy Clement
* @author Juergen Hoeller
* @author Phillip Webb
* @author Sam Brannen
* @since 3.0
*/
class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
@@ -98,9 +95,6 @@ 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 = "";
@@ -254,7 +248,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
}
if (tk == TokenKind.MATCHES) {
return new OperatorMatches(this.patternCache, t.startPos, t.endPos, expr, rhExpr);
return new OperatorMatches(t.startPos, t.endPos, expr, rhExpr);
}
Assert.isTrue(tk == TokenKind.BETWEEN, "Between token expected");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,7 +48,6 @@ import org.springframework.lang.Nullable;
* @author Andy Clement
* @author Juergen Hoeller
* @author Chris Beams
* @author Sam Brannen
* @since 3.0
* @see StandardEvaluationContext#addMethodResolver(MethodResolver)
*/
@@ -226,7 +225,8 @@ 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(..)
for (Method method : getMethods(type)) {
Method[] methods = getMethods(type);
for (Method method : methods) {
if (Modifier.isStatic(method.getModifiers())) {
result.add(method);
}
@@ -239,23 +239,19 @@ 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()) {
for (Method method : getMethods(ifc)) {
Method[] methods = getMethods(ifc);
for (Method method : methods) {
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<>();
for (Method method : getMethods(type)) {
Method[] methods = getMethods(type);
for (Method method : methods) {
if (isCandidateForInvocation(method, type)) {
result.add(method);
}
@@ -280,7 +276,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 and non-user-declared methods on the {@link Object} base class.
* static methods sand 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
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*/
public abstract class AbstractExpressionTests {
protected static final boolean DEBUG = false;
private static final boolean DEBUG = false;
protected static final boolean SHOULD_BE_WRITABLE = true;
@@ -202,9 +202,7 @@ public abstract class AbstractExpressionTests {
protected void parseAndCheckError(String expression, SpelMessage expectedMessage, Object... otherProperties) {
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> {
Expression expr = parser.parseExpression(expression);
if (DEBUG) {
SpelUtilities.printAbstractSyntaxTree(System.out, expr);
}
SpelUtilities.printAbstractSyntaxTree(System.out, expr);
}).satisfies(ex -> {
assertThat(ex.getMessageCode()).isEqualTo(expectedMessage);
if (otherProperties != null && otherProperties.length != 0) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -127,6 +127,17 @@ 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() {
@@ -449,49 +460,28 @@ class EvaluationTests extends AbstractExpressionTests {
}
@Test
void matchesTrue() {
evaluate("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "true", Boolean.class);
}
@Test
void matchesFalse() {
void relOperatorsMatches01() {
evaluate("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'", "false", Boolean.class);
}
@Test
void matchesWithInputConversion() {
evaluate("27 matches '^.*2.*$'", true, Boolean.class); // conversion int --> string
void relOperatorsMatches02() {
evaluate("'5.00' matches '^-?\\d+(\\.\\d{2})?$'", "true", Boolean.class);
}
@Test
void matchesWithNullInput() {
void relOperatorsMatches03() {
evaluateAndCheckError("null matches '^.*$'", SpelMessage.INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR, 0, null);
}
@Test
void matchesWithNullPattern() {
void relOperatorsMatches04() {
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 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);
void relOperatorsMatches05() {
evaluate("27 matches '^.*2.*$'", true, Boolean.class); // conversion int>string
}
}
@@ -1458,9 +1448,7 @@ class EvaluationTests extends AbstractExpressionTests {
private void expectFail(ExpressionParser parser, EvaluationContext eContext, String expressionString, SpelMessage messageCode) {
assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> {
Expression e = parser.parseExpression(expressionString);
if (DEBUG) {
SpelUtilities.printAbstractSyntaxTree(System.out, e);
}
SpelUtilities.printAbstractSyntaxTree(System.out, e);
e.getValue(eContext);
}).satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(messageCode));
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,25 +21,22 @@ 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 various operators.
* Tests the evaluation of expressions using relational operators.
*
* @author Andy Clement
* @author Juergen Hoeller
* @author Giovanni Dall'Oglio Risso
* @author Sam Brannen
*/
class OperatorTests extends AbstractExpressionTests {
@Test
void equal() {
void testEqual() {
evaluate("3 == 5", false, Boolean.class);
evaluate("5 == 3", false, Boolean.class);
evaluate("6 == 6", true, Boolean.class);
@@ -88,7 +85,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void notEqual() {
void testNotEqual() {
evaluate("3 != 5", true, Boolean.class);
evaluate("5 != 3", true, Boolean.class);
evaluate("6 != 6", false, Boolean.class);
@@ -137,7 +134,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void lessThan() {
void testLessThan() {
evaluate("5 < 5", false, Boolean.class);
evaluate("3 < 5", true, Boolean.class);
evaluate("5 < 3", false, Boolean.class);
@@ -179,7 +176,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void lessThanOrEqual() {
void testLessThanOrEqual() {
evaluate("3 <= 5", true, Boolean.class);
evaluate("5 <= 3", false, Boolean.class);
evaluate("6 <= 6", true, Boolean.class);
@@ -228,7 +225,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void greaterThan() {
void testGreaterThan() {
evaluate("3 > 5", false, Boolean.class);
evaluate("5 > 3", true, Boolean.class);
evaluate("3L > 5L", false, Boolean.class);
@@ -269,7 +266,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void greaterThanOrEqual() {
void testGreaterThanOrEqual() {
evaluate("3 >= 5", false, Boolean.class);
evaluate("5 >= 3", true, Boolean.class);
evaluate("6 >= 6", true, Boolean.class);
@@ -318,22 +315,27 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void integerLiteral() {
void testIntegerLiteral() {
evaluate("3", 3, Integer.class);
}
@Test
void realLiteral() {
void testRealLiteral() {
evaluate("3.5", 3.5d, Double.class);
}
@Test
void multiplyDoubleDoubleGivesDouble() {
void testMultiplyStringInt() {
evaluate("'a' * 5", "aaaaa", String.class);
}
@Test
void testMultiplyDoubleDoubleGivesDouble() {
evaluate("3.0d * 5.0d", 15.0d, Double.class);
}
@Test
void mixedOperandsBigDecimal() {
void testMixedOperandsBigDecimal() {
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);
@@ -359,19 +361,19 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void mathOperatorAdd02() {
void testMathOperatorAdd02() {
evaluate("'hello' + ' ' + 'world'", "hello world", String.class);
}
@Test
void mathOperatorsInChains() {
void testMathOperatorsInChains() {
evaluate("1+2+3",6,Integer.class);
evaluate("2*3*4",24,Integer.class);
evaluate("12-1-2",9,Integer.class);
}
@Test
void integerArithmetic() {
void testIntegerArithmetic() {
evaluate("2 + 4", "6", Integer.class);
evaluate("5 - 4", "1", Integer.class);
evaluate("3 * 5", 15, Integer.class);
@@ -386,7 +388,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void plus() {
void testPlus() {
evaluate("7 + 2", "9", Integer.class);
evaluate("3.0f + 5.0f", 8.0f, Float.class);
evaluate("3.0d + 5.0d", 8.0d, Double.class);
@@ -398,44 +400,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 minus() {
void testMinus() {
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 modulus() {
void testModulus() {
evaluate("3%2",1,Integer.class);
evaluate("3L%2L",1L,Long.class);
evaluate("3.0f%2.0f",1f,Float.class);
@@ -446,7 +448,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void divide() {
void testDivide() {
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);
@@ -459,17 +461,17 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void mathOperatorDivide_ConvertToDouble() {
void testMathOperatorDivide_ConvertToDouble() {
evaluateAndAskForReturnType("8/4", 2.0, Double.class);
}
@Test
void mathOperatorDivide04_ConvertToFloat() {
void testMathOperatorDivide04_ConvertToFloat() {
evaluateAndAskForReturnType("8/4", 2.0F, Float.class);
}
@Test
void doubles() {
void testDoubles() {
evaluate("3.0d == 5.0d", false, Boolean.class);
evaluate("3.0d == 3.0d", true, Boolean.class);
evaluate("3.0d != 5.0d", true, Boolean.class);
@@ -482,7 +484,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void bigDecimals() {
void testBigDecimals() {
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);
@@ -493,7 +495,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void operatorNames() {
void testOperatorNames() {
Operator node = getOperatorNode((SpelExpression)parser.parseExpression("1==3"));
assertThat(node.getOperatorName()).isEqualTo("==");
@@ -532,22 +534,22 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void operatorOverloading() {
void testOperatorOverloading() {
evaluateAndCheckError("'a' * '2'", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
evaluateAndCheckError("'a' ^ '2'", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
}
@Test
void power() {
evaluate("3^2", 9, Integer.class);
evaluate("3.0d^2.0d", 9.0d, Double.class);
evaluate("3L^2L", 9L, Long.class);
void testPower() {
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 mixedOperands_FloatsAndDoubles() {
void testMixedOperands_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);
@@ -556,7 +558,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void mixedOperands_DoublesAndInts() {
void testMixedOperands_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);
@@ -567,7 +569,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void strings() {
void testStrings() {
evaluate("'abc' == 'abc'", true, Boolean.class);
evaluate("'abc' == 'def'", false, Boolean.class);
evaluate("'abc' != 'abc'", false, Boolean.class);
@@ -575,20 +577,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
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() {
void testLongs() {
evaluate("3L == 4L", false, Boolean.class);
evaluate("3L == 3L", true, Boolean.class);
evaluate("3L != 4L", true, Boolean.class);
@@ -599,7 +588,7 @@ class OperatorTests extends AbstractExpressionTests {
}
@Test
void bigIntegers() {
void testBigIntegers() {
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);
@@ -630,7 +619,7 @@ class OperatorTests extends AbstractExpressionTests {
}
static class BaseComparable implements Comparable<BaseComparable> {
public static class BaseComparable implements Comparable<BaseComparable> {
@Override
public int compareTo(BaseComparable other) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1817,6 +1817,7 @@ class SpelReproTests extends AbstractExpressionTests {
static class CCC {
public boolean method(Object o) {
System.out.println(o);
return false;
}
}
@@ -1888,6 +1889,7 @@ class SpelReproTests extends AbstractExpressionTests {
static class Foo2 {
public void execute(String str) {
System.out.println("Value: " + str);
}
}
@@ -1962,6 +1964,7 @@ 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)";
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,17 +19,13 @@ 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;
@@ -368,20 +364,6 @@ 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,42 +48,31 @@ 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 or {@code static} nested class, and it must have a default or
* no-arg constructor.
* top-level 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 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>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>Mapping is provided for properties in the target class for many common types &mdash;
* for example: 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 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>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>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>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>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>If you need to map to a target class which has a <em>data class</em> constructor
* &mdash; for example, a Java {@code record} or a Kotlin {@code data} class &mdash;
* 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.
* <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.
*
* @author Thomas Risberg
* @author Juergen Hoeller
* @author Sam Brannen
* @since 2.5
* @param <T> the result type
* @see DataClassRowMapper
@@ -100,24 +89,20 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
/** Whether we're strictly validating. */
private boolean checkFullyPopulated = false;
/**
* Whether {@code NULL} database values should be ignored for primitive
* properties in the target class.
* @see #setPrimitivesDefaultedForNullValue(boolean)
*/
/** Whether we're defaulting primitives when mapping a null value. */
private boolean primitivesDefaultedForNullValue = false;
/** ConversionService for binding JDBC values to bean properties. */
@Nullable
private ConversionService conversionService = DefaultConversionService.getSharedInstance();
/** Map of the properties we provide mapping for. */
/** Map of the fields we provide mapping for. */
@Nullable
private Map<String, PropertyDescriptor> mappedProperties;
private Map<String, PropertyDescriptor> mappedFields;
/** Set of bean property names we provide mapping for. */
/** Set of bean properties we provide mapping for. */
@Nullable
private Set<String> mappedPropertyNames;
private Set<String> mappedProperties;
/**
@@ -141,7 +126,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 columns
* all bean properties have been mapped from corresponding database fields
*/
public BeanPropertyRowMapper(Class<T> mappedClass, boolean checkFullyPopulated) {
initialize(mappedClass);
@@ -174,7 +159,7 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
/**
* Set whether we're strictly validating that all bean properties have been mapped
* from corresponding database columns.
* from corresponding database fields.
* <p>Default is {@code false}, accepting unpopulated properties in the target bean.
*/
public void setCheckFullyPopulated(boolean checkFullyPopulated) {
@@ -183,33 +168,24 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
/**
* Return whether we're strictly validating that all bean properties have been
* mapped from corresponding database columns.
* mapped from corresponding database fields.
*/
public boolean isCheckFullyPopulated() {
return this.checkFullyPopulated;
}
/**
* 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.
* 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.
*/
public void setPrimitivesDefaultedForNullValue(boolean primitivesDefaultedForNullValue) {
this.primitivesDefaultedForNullValue = primitivesDefaultedForNullValue;
}
/**
* Get the value of the {@code primitivesDefaultedForNullValue} flag.
* @see #setPrimitivesDefaultedForNullValue(boolean)
* Return whether we're defaulting Java primitives in the case of mapping a null value
* from corresponding database fields.
*/
public boolean isPrimitivesDefaultedForNullValue() {
return this.primitivesDefaultedForNullValue;
@@ -244,31 +220,31 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
*/
protected void initialize(Class<T> mappedClass) {
this.mappedClass = mappedClass;
this.mappedProperties = new HashMap<>();
this.mappedPropertyNames = new HashSet<>();
this.mappedFields = new HashMap<>();
this.mappedProperties = new HashSet<>();
for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(mappedClass)) {
if (pd.getWriteMethod() != null) {
String lowerCaseName = lowerCaseName(pd.getName());
this.mappedProperties.put(lowerCaseName, pd);
this.mappedFields.put(lowerCaseName, pd);
String underscoreName = underscoreName(pd.getName());
if (!lowerCaseName.equals(underscoreName)) {
this.mappedProperties.put(underscoreName, pd);
this.mappedFields.put(underscoreName, pd);
}
this.mappedPropertyNames.add(pd.getName());
this.mappedProperties.add(pd.getName());
}
}
}
/**
* Remove the specified property from the mapped properties.
* Remove the specified property from the mapped fields.
* @param propertyName the property name (as used by property descriptors)
* @since 5.3.9
*/
protected void suppressProperty(String propertyName) {
if (this.mappedProperties != null) {
this.mappedProperties.remove(lowerCaseName(propertyName));
this.mappedProperties.remove(underscoreName(propertyName));
if (this.mappedFields != null) {
this.mappedFields.remove(lowerCaseName(propertyName));
this.mappedFields.remove(underscoreName(propertyName));
}
}
@@ -330,8 +306,8 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
for (int index = 1; index <= columnCount; index++) {
String column = JdbcUtils.lookupColumnName(rsmd, index);
String property = lowerCaseName(StringUtils.delete(column, " "));
PropertyDescriptor pd = (this.mappedProperties != null ? this.mappedProperties.get(property) : null);
String field = lowerCaseName(StringUtils.delete(column, " "));
PropertyDescriptor pd = (this.mappedFields != null ? this.mappedFields.get(field) : null);
if (pd != null) {
try {
Object value = getColumnValue(rs, index, pd);
@@ -345,11 +321,11 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
catch (TypeMismatchException ex) {
if (value == null && this.primitivesDefaultedForNullValue) {
if (logger.isDebugEnabled()) {
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);
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);
}
}
else {
@@ -367,9 +343,9 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
}
}
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);
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);
}
return mappedObject;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,30 +31,15 @@ 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 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.
* 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).
*
* <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-2023 the original author or authors.
* Copyright 2002-2018 the original author 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 {@code JdbcTemplate}.
* by the calling JdbcTemplate.
*
* <p>Typically used either for {@code JdbcTemplate}'s query methods or for
* {@code out} parameters of stored procedures. {@code RowMapper} objects are
* <p>Typically used either for {@link JdbcTemplate}'s query methods
* or for out parameters of stored procedures. 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
* {@code JdbcTemplate} and {@code RowMapper} objects, you can build executable
* query objects (containing row-mapping logic) in that style.
* {@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.
*
* @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
* {@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)
* 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)
* @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 while getting
* @throws SQLException if an SQLException is encountered getting
* column values (that is, there's no need to catch SQLException)
*/
@Nullable
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
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;
@@ -42,15 +44,12 @@ 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
@@ -62,106 +61,104 @@ class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
@Test
void staticQueryWithRowMapper() throws Exception {
Mock mock = new Mock();
Person person = mock.getJdbcTemplate().queryForObject(
List<Person> result = mock.getJdbcTemplate().query(
"select name, age, birth_date, balance from people",
new BeanPropertyRowMapper<>(Person.class));
verifyPerson(person);
assertThat(result).hasSize(1);
verifyPerson(result.get(0));
mock.verifyClosed();
}
@Test
void mappingWithInheritance() throws Exception {
Mock mock = new Mock();
ConcretePerson person = mock.getJdbcTemplate().queryForObject(
List<ConcretePerson> result = mock.getJdbcTemplate().query(
"select name, age, birth_date, balance from people",
new BeanPropertyRowMapper<>(ConcretePerson.class));
verifyPerson(person);
assertThat(result).hasSize(1);
verifyPerson(result.get(0));
mock.verifyClosed();
}
@Test
void mappingWithNoUnpopulatedFieldsFound() throws Exception {
Mock mock = new Mock();
ConcretePerson person = mock.getJdbcTemplate().queryForObject(
List<ConcretePerson> result = mock.getJdbcTemplate().query(
"select name, age, birth_date, balance from people",
new BeanPropertyRowMapper<>(ConcretePerson.class, true));
verifyPerson(person);
assertThat(result).hasSize(1);
verifyPerson(result.get(0));
mock.verifyClosed();
}
@Test
void mappingWithUnpopulatedFieldsNotChecked() throws Exception {
Mock mock = new Mock();
ExtendedPerson person = mock.getJdbcTemplate().queryForObject(
List<ExtendedPerson> result = mock.getJdbcTemplate().query(
"select name, age, birth_date, balance from people",
new BeanPropertyRowMapper<>(ExtendedPerson.class));
verifyPerson(person);
assertThat(result).hasSize(1);
verifyPerson(result.get(0));
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", mapper));
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() ->
mock.getJdbcTemplate().query("select name, age, birth_date, balance from people",
new BeanPropertyRowMapper<>(ExtendedPerson.class, true)));
}
@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_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();
assertThatExceptionOfType(TypeMismatchException.class).isThrownBy(() ->
mock.getJdbcTemplate().query("select name, null as age, birth_date, balance from people", mapper));
}
@Test
void queryWithSpaceInColumnNameAndLocalDateTime() throws Exception {
Mock mock = new Mock(MockType.THREE);
SpacePerson person = mock.getJdbcTemplate().queryForObject(
List<SpacePerson> result = mock.getJdbcTemplate().query(
"select last_name as \"Last Name\", age, birth_date, balance from people",
new BeanPropertyRowMapper<>(SpacePerson.class));
verifyPerson(person);
assertThat(result).hasSize(1);
verifyPerson(result.get(0));
mock.verifyClosed();
}
@Test
void queryWithSpaceInColumnNameAndLocalDate() throws Exception {
Mock mock = new Mock(MockType.THREE);
DatePerson person = mock.getJdbcTemplate().queryForObject(
List<DatePerson> result = mock.getJdbcTemplate().query(
"select last_name as \"Last Name\", age, birth_date, balance from people",
new BeanPropertyRowMapper<>(DatePerson.class));
verifyPerson(person);
assertThat(result).hasSize(1);
verifyPerson(result.get(0));
mock.verifyClosed();
}
@Test
void queryWithDirectNameMatchOnBirthDate() throws Exception {
Mock mock = new Mock(MockType.FOUR);
ConcretePerson person = mock.getJdbcTemplate().queryForObject(
List<ConcretePerson> result = mock.getJdbcTemplate().query(
"select name, age, birthdate, balance from people",
new BeanPropertyRowMapper<>(ConcretePerson.class));
verifyPerson(person);
assertThat(result).hasSize(1);
verifyPerson(result.get(0));
mock.verifyClosed();
}
@Test
void queryWithUnderscoreInColumnNameAndPersonWithMultipleAdjacentUppercaseLettersInPropertyName() throws Exception {
Mock mock = new Mock();
EmailPerson person = mock.getJdbcTemplate().queryForObject(
List<EmailPerson> result = mock.getJdbcTemplate().query(
"select name, age, birth_date, balance, e_mail from people",
new BeanPropertyRowMapper<>(EmailPerson.class));
verifyPerson(person);
assertThat(result).hasSize(1);
verifyPerson(result.get(0));
mock.verifyClosed();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,9 @@
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;
@@ -28,48 +30,50 @@ 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
*/
class DataClassRowMapperTests extends AbstractRowMapperTests {
public class DataClassRowMapperTests extends AbstractRowMapperTests {
@Test
void staticQueryWithDataClass() throws Exception {
public void testStaticQueryWithDataClass() throws Exception {
Mock mock = new Mock();
ConstructorPerson person = mock.getJdbcTemplate().queryForObject(
List<ConstructorPerson> result = mock.getJdbcTemplate().query(
"select name, age, birth_date, balance from people",
new DataClassRowMapper<>(ConstructorPerson.class));
verifyPerson(person);
assertThat(result.size()).isEqualTo(1);
verifyPerson(result.get(0));
mock.verifyClosed();
}
@Test
void staticQueryWithDataClassAndGenerics() throws Exception {
public void testStaticQueryWithDataClassAndGenerics() throws Exception {
Mock mock = new Mock();
ConstructorPersonWithGenerics person = mock.getJdbcTemplate().queryForObject(
List<ConstructorPersonWithGenerics> result = mock.getJdbcTemplate().query(
"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 Date(1221222L));
assertThat(person.balance()).containsExactly(new BigDecimal("1234.56"));
assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
assertThat(person.balance()).isEqualTo(Collections.singletonList(new BigDecimal("1234.56")));
mock.verifyClosed();
}
@Test
void staticQueryWithDataClassAndSetters() throws Exception {
public void testStaticQueryWithDataClassAndSetters() throws Exception {
Mock mock = new Mock(MockType.FOUR);
ConstructorPersonWithSetters person = mock.getJdbcTemplate().queryForObject(
List<ConstructorPersonWithSetters> result = mock.getJdbcTemplate().query(
"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 Date(1221222L));
assertThat(person.birthDate()).usingComparator(Date::compareTo).isEqualTo(new java.util.Date(1221222L));
assertThat(person.balance()).isEqualTo(new BigDecimal("1234.56"));
mock.verifyClosed();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2018 the original author 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,9 +31,6 @@ 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-2023 the original author or authors.
* Copyright 2002-2018 the original author 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,7 +76,6 @@ 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
@@ -111,12 +110,6 @@ 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 &mdash; 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 "";
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -347,26 +347,8 @@ public class SingleConnectionFactory implements ConnectionFactory, QueueConnecti
if (this.connection != null) {
closeConnection(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;
}
this.connection = doCreateConnection();
prepareConnection(this.connection);
if (this.startedCount > 0) {
this.connection.start();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@ import 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;
@@ -35,7 +34,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} in Spring's {@link Message}
* <p>Wraps the incoming {@link javax.jms.Message} to Spring's {@link Message}
* abstraction, copying the JMS standard headers using a configurable
* {@link JmsHeaderMapper}.
*
@@ -43,19 +42,13 @@ 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
implements SubscriptionNameProvider {
public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageListener {
@Nullable
private InvocableHandlerMethod handlerMethod;
@@ -90,6 +83,17 @@ 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);
@@ -100,7 +104,7 @@ public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageLis
}
/**
* Invoke the handler, wrapping any exception in a {@link ListenerExecutionFailedException}
* Invoke the handler, wrapping any exception to a {@link ListenerExecutionFailedException}
* with a dedicated error message.
*/
@Nullable
@@ -128,39 +132,4 @@ 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-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2019 the original author 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,9 +16,6 @@
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;
@@ -33,10 +30,7 @@ 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;
@@ -348,76 +342,6 @@ 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);
@@ -1,104 +0,0 @@
/*
* 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";
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,10 +17,8 @@
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;
@@ -30,14 +28,16 @@ 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,7 +123,6 @@ import static java.util.stream.Collectors.joining;
* </pre>
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 5.2
*/
public class ResolvableMethod {
@@ -190,6 +189,7 @@ 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,6 +302,7 @@ public class ResolvableMethod {
/**
* Filter on annotated methods.
* See {@link org.springframework.web.method.MvcAnnotationPredicates}.
*/
@SafeVarargs
public final Builder<T> annot(Predicate<Method>... filters) {
@@ -312,6 +313,7 @@ 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) {
@@ -528,6 +530,7 @@ public class ResolvableMethod {
/**
* Filter on method arguments with annotations.
* See {@link org.springframework.web.method.MvcAnnotationPredicates}.
*/
@SafeVarargs
public final ArgResolver annot(Predicate<MethodParameter>... filters) {
@@ -539,6 +542,7 @@ 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) {
@@ -611,10 +615,16 @@ public class ResolvableMethod {
}
private static class MethodInvocationInterceptor implements MethodInterceptor, InvocationHandler {
private static class MethodInvocationInterceptor
implements org.springframework.cglib.proxy.MethodInterceptor, MethodInterceptor {
private Method invokedMethod;
Method getInvokedMethod() {
return this.invokedMethod;
}
@Override
@Nullable
public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) {
@@ -629,23 +639,20 @@ public class ResolvableMethod {
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return intercept(proxy, method, args, null);
}
Method getInvokedMethod() {
return this.invokedMethod;
public Object invoke(org.aopalliance.intercept.MethodInvocation inv) throws Throwable {
return intercept(inv.getThis(), inv.getMethod(), inv.getArguments(), null);
}
}
@SuppressWarnings("unchecked")
private static <T> T initProxy(Class<?> type, MethodInvocationInterceptor interceptor) {
Assert.notNull(type, "'type' must not be null");
if (type.isInterface()) {
return (T) Proxy.newProxyInstance(type.getClassLoader(),
new Class<?>[] {type, Supplier.class},
interceptor);
ProxyFactory factory = new ProxyFactory(EmptyTargetSource.INSTANCE);
factory.addInterface(type);
factory.addInterface(Supplier.class);
factory.addAdvice(interceptor);
return (T) factory.getProxy();
}
else {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -62,7 +62,6 @@ 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
@@ -75,9 +74,9 @@ public abstract class SharedEntityManagerCreator {
private static final Map<Class<?>, Class<?>[]> cachedQueryInterfaces = new ConcurrentReferenceHashMap<>(4);
private static final Set<String> transactionRequiringMethods = new HashSet<>(6);
private static final Set<String> transactionRequiringMethods = new HashSet<>(8);
private static final Set<String> queryTerminatingMethods = new HashSet<>(9);
private static final Set<String> queryTerminatingMethods = new HashSet<>(8);
static {
transactionRequiringMethods.add("joinTransaction");
@@ -87,15 +86,12 @@ public abstract class SharedEntityManagerCreator {
transactionRequiringMethods.add("remove");
transactionRequiringMethods.add("refresh");
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()
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
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -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 [" + connection + "] for R2DBC transaction");
logger.debug("Acquired Connection [" + newCon + "] for R2DBC transaction");
}
txObject.setConnectionHolder(new ConnectionHolder(connection), true);
});
@@ -325,48 +325,31 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
Mono<Void> afterCleanup = Mono.empty();
if (txObject.isMustRestoreAutoCommit()) {
Mono<Void> restoreAutoCommitStep = safeCleanupStep(
"doCleanupAfterCompletion when restoring autocommit", Mono.from(con.setAutoCommit(true)));
afterCleanup = afterCleanup.then(restoreAutoCommitStep);
afterCleanup = afterCleanup.then(Mono.from(con.setAutoCommit(true)));
}
if (txObject.getPreviousIsolationLevel() != null) {
Mono<Void> restoreIsolationStep = safeCleanupStep(
"doCleanupAfterCompletion when restoring isolation level",
Mono.from(con.setTransactionIsolationLevel(txObject.getPreviousIsolationLevel())));
afterCleanup = afterCleanup.then(restoreIsolationStep);
afterCleanup = afterCleanup
.then(Mono.from(con.setTransactionIsolationLevel(txObject.getPreviousIsolationLevel())));
}
Mono<Void> releaseConnectionStep = Mono.defer(() -> {
return afterCleanup.then(Mono.defer(() -> {
try {
if (txObject.isNewConnectionHolder()) {
if (logger.isDebugEnabled()) {
logger.debug("Releasing R2DBC Connection [" + con + "] after transaction");
}
return safeCleanupStep("doCleanupAfterCompletion when releasing R2DBC Connection",
ConnectionFactoryUtils.releaseConnection(con, obtainConnectionFactory()));
return 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-2023 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,18 +20,34 @@ 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. 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.
* related to the underlying action.
*
* @author Mark Paluch
* @author Simon Baslé
* @since 5.3
* @param <R> the type of the result of the function.
*/
interface ConnectionFunction<R> extends Function<Connection, R>, SqlProvider {
}
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;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -79,10 +79,7 @@ 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>{@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.
* <p>Accepts {@link PreparedOperation} as SQL and binding {@link Supplier}
* @param sqlSupplier a supplier for the SQL statement
* @return a new {@link GenericExecuteSpec}
* @see NamedParameterExpander
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -60,7 +60,6 @@ import org.springframework.util.StringUtils;
* @author Mark Paluch
* @author Mingyuan Wu
* @author Bogdan Ilchyshyn
* @author Simon Baslé
* @since 5.3
*/
class DefaultDatabaseClient implements DatabaseClient {
@@ -159,9 +158,11 @@ 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()));
@@ -187,22 +188,24 @@ 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));
}
/**
* Get SQL from a potential provider object.
* @param object an object that is potentially an SqlProvider
* Determine SQL from potential provider object.
* @param sqlProvider object that's potentially a SqlProvider
* @return the SQL string, or {@code null}
* @see SqlProvider
*/
@Nullable
private static String getSql(Object object) {
if (object instanceof SqlProvider) {
return ((SqlProvider) object).getSql();
private static String getSql(Object sqlProvider) {
if (sqlProvider instanceof SqlProvider) {
return ((SqlProvider) sqlProvider).getSql();
}
else {
return null;
@@ -211,7 +214,7 @@ class DefaultDatabaseClient implements DatabaseClient {
/**
* Default {@link DatabaseClient.GenericExecuteSpec} implementation.
* Base class for {@link DatabaseClient.GenericExecuteSpec} implementations.
*/
class DefaultGenericExecuteSpec implements GenericExecuteSpec {
@@ -319,8 +322,9 @@ class DefaultDatabaseClient implements DatabaseClient {
return fetch().rowsUpdated().then();
}
private ResultFunction getResultFunction(Supplier<String> sqlSupplier) {
BiFunction<Connection, String, Statement> statementFunction = (connection, sql) -> {
private <T> FetchSpec<T> execute(Supplier<String> sqlSupplier, BiFunction<Row, RowMetadata, T> mappingFunction) {
String sql = getRequiredSql(sqlSupplier);
Function<Connection, Statement> statementFunction = connection -> {
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL statement [" + sql + "]");
}
@@ -366,16 +370,16 @@ class DefaultDatabaseClient implements DatabaseClient {
return statement;
};
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);
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 DefaultFetchSpec<>(
DefaultDatabaseClient.this,
resultHandler,
connection -> sumRowsUpdated(resultHandler, connection),
DefaultDatabaseClient.this, sql,
new ConnectionFunction<>(sql, resultFunction),
new ConnectionFunction<>(sql, connection -> sumRowsUpdated(resultFunction, connection)),
mappingFunction);
}
@@ -501,11 +505,12 @@ class DefaultDatabaseClient implements DatabaseClient {
private static final long serialVersionUID = -8994138383301201380L;
final transient Connection connection;
final Connection connection;
final transient Function<Connection, Publisher<Void>> closeFunction;
final 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-2023 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ 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;
@@ -31,7 +32,6 @@ 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,21 +39,24 @@ class DefaultFetchSpec<T> implements FetchSpec<T> {
private final ConnectionAccessor connectionAccessor;
private final ResultFunction resultFunction;
private final String sql;
private final Function<Connection, Flux<Result>> resultFunction;
private final Function<Connection, Mono<Integer>> updatedRowsFunction;
private final BiFunction<Row, RowMetadata, T> mappingFunction;
DefaultFetchSpec(ConnectionAccessor connectionAccessor,
ResultFunction resultFunction,
DefaultFetchSpec(ConnectionAccessor connectionAccessor, String sql,
Function<Connection, Flux<Result>> resultFunction,
Function<Connection, Mono<Integer>> updatedRowsFunction,
BiFunction<Row, RowMetadata, T> mappingFunction) {
this.sql = sql;
this.connectionAccessor = connectionAccessor;
this.resultFunction = resultFunction;
this.updatedRowsFunction = new DelegateConnectionFunction<>(resultFunction, updatedRowsFunction);
this.updatedRowsFunction = updatedRowsFunction;
this.mappingFunction = mappingFunction;
}
@@ -67,7 +70,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.resultFunction.getSql()),
String.format("Query [%s] returned non unique result.", this.sql),
1));
}
return Mono.just(list.get(0));
@@ -81,7 +84,7 @@ class DefaultFetchSpec<T> implements FetchSpec<T> {
@Override
public Flux<T> all() {
return this.connectionAccessor.inConnectionMany(new DelegateConnectionFunction<>(this.resultFunction,
return this.connectionAccessor.inConnectionMany(new ConnectionFunction<>(this.sql,
connection -> this.resultFunction.apply(connection)
.flatMap(result -> result.map(this.mappingFunction))));
}
@@ -1,56 +0,0 @@
/*
* 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();
}
}
@@ -1,74 +0,0 @@
/*
* 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;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,15 +22,12 @@ 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;
@@ -322,34 +319,6 @@ 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());
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,6 @@
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;
@@ -66,7 +64,6 @@ 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)
@@ -400,47 +397,6 @@ 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);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -70,15 +70,14 @@ import java.lang.annotation.Target;
* class ExampleIntegrationTests {
*
* &#064;Container
* static GenericContainer redis =
* new GenericContainer("redis:5.0.3-alpine").withExposedPorts(6379);
* static RedisContainer redis = new RedisContainer();
*
* // ...
*
* &#064;DynamicPropertySource
* static void redisProperties(DynamicPropertyRegistry registry) {
* registry.add("redis.host", redis::getHost);
* registry.add("redis.port", redis::getFirstMappedPort);
* registry.add("redis.host", redis::getContainerIpAddress);
* registry.add("redis.port", redis::getMappedPort);
* }
*
* }</pre>
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,7 +47,6 @@ 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 {
@@ -113,49 +112,8 @@ 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) {
@@ -170,20 +128,6 @@ 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 -> {
@@ -199,47 +143,21 @@ public abstract class MockRestRequestMatchers {
return UriComponentsBuilder.fromUri(request.getURI()).build().getQueryParams();
}
/**
* 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);
};
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(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) {
@@ -255,19 +173,6 @@ 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 -> {
@@ -353,18 +258,4 @@ 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);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -68,8 +68,6 @@ 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
@@ -135,15 +133,10 @@ public final class MockMvcWebConnection implements WebConnection {
MockHttpServletResponse httpServletResponse = getResponse(requestBuilder);
String forwardedUrl = httpServletResponse.getForwardedUrl();
int forwards = 0;
while (forwardedUrl != null && forwards < MAX_FORWARDS) {
while (forwardedUrl != null) {
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());
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -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) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2019 the original author 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,18 +224,6 @@ 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.
*
@@ -247,15 +235,3 @@ 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)
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2019 the original author 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,33 +16,18 @@
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.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.assertj.core.api.Assertions.assertThatThrownBy;
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}.
@@ -50,307 +35,179 @@ import static org.hamcrest.Matchers.startsWith;
* @author Craig Walls
* @author Rossen Stoyanchev
* @author Sam Brannen
* @author Simon Baslé
*/
class MockRestRequestMatchersTests {
public class MockRestRequestMatchersTests {
private final MockClientHttpRequest request = new MockClientHttpRequest();
@Test
void requestTo() throws Exception {
public 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
void requestToUriTemplate() throws Exception {
public 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
void requestToNoMatch() throws Exception {
public void requestToNoMatch() throws Exception {
this.request.setURI(new URI("http://www.foo.example/bar"));
assertThatAssertionError()
.isThrownBy(() -> MockRestRequestMatchers.requestTo("http://www.foo.example/wrong").match(this.request));
assertThatThrownBy(
() -> MockRestRequestMatchers.requestTo("http://www.foo.example/wrong").match(this.request))
.isInstanceOf(AssertionError.class);
}
@Test
void requestToContains() throws Exception {
public void requestToContains() throws Exception {
this.request.setURI(new URI("http://www.foo.example/bar"));
MockRestRequestMatchers.requestTo(containsString("bar")).match(this.request);
}
@Test
void method() throws Exception {
public void method() throws Exception {
this.request.setMethod(HttpMethod.GET);
MockRestRequestMatchers.method(HttpMethod.GET).match(this.request);
}
@Test
void methodNoMatch() throws Exception {
public void methodNoMatch() throws Exception {
this.request.setMethod(HttpMethod.POST);
assertThatAssertionError()
.isThrownBy(() -> MockRestRequestMatchers.method(HttpMethod.GET).match(this.request))
.withMessageContaining("expected:<GET> but was:<POST>");
assertThatThrownBy(() -> MockRestRequestMatchers.method(HttpMethod.GET).match(this.request))
.isInstanceOf(AssertionError.class)
.hasMessageContaining("expected:<GET> but was:<POST>");
}
@Test
void header() throws Exception {
public void header() throws Exception {
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
MockRestRequestMatchers.header("foo", "bar", "baz").match(this.request);
}
@Test
void headerDoesNotExist() throws Exception {
public 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);
assertThatAssertionError()
.isThrownBy(() -> MockRestRequestMatchers.headerDoesNotExist("foo").match(this.request))
.withMessage("Expected header <foo> not to exist, but it exists with values: " + values);
assertThatThrownBy(() -> MockRestRequestMatchers.headerDoesNotExist("foo").match(this.request))
.isInstanceOf(AssertionError.class)
.hasMessage("Expected header <foo> not to exist, but it exists with values: " + values);
}
@Test
void headerMissing() {
assertThatAssertionError()
.isThrownBy(() -> MockRestRequestMatchers.header("foo", "bar").match(this.request))
.withMessageContaining("was null");
public void headerMissing() throws Exception {
assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", "bar").match(this.request))
.isInstanceOf(AssertionError.class)
.hasMessageContaining("was null");
}
@Test
void headerMissingValue() {
public void headerMissingValue() throws Exception {
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
assertThatAssertionError()
.isThrownBy(() -> MockRestRequestMatchers.header("foo", "bad").match(this.request))
.withMessageContaining("expected:<bad> but was:<bar>");
assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", "bad").match(this.request))
.isInstanceOf(AssertionError.class)
.hasMessageContaining("expected:<bad> but was:<bar>");
}
@Test
void headerContains() throws Exception {
public void headerContains() throws Exception {
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
MockRestRequestMatchers.header("foo", containsString("ba")).match(this.request);
}
@Test
void headerContainsWithMissingHeader() {
assertThatAssertionError()
.isThrownBy(() -> MockRestRequestMatchers.header("foo", containsString("baz")).match(this.request))
.withMessage("Expected header <foo> to exist but was null");
public void headerContainsWithMissingHeader() throws Exception {
assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", containsString("baz")).match(this.request))
.isInstanceOf(AssertionError.class)
.hasMessageContaining("but was null");
}
@Test
void headerContainsWithMissingValue() {
public void headerContainsWithMissingValue() throws Exception {
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
assertThatAssertionError()
.isThrownBy(() -> MockRestRequestMatchers.header("foo", containsString("bx")).match(this.request))
.withMessageContaining("was \"bar\"");
assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", containsString("bx")).match(this.request))
.isInstanceOf(AssertionError.class)
.hasMessageContaining("was \"bar\"");
}
@Test
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 {
public void headers() throws Exception {
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
MockRestRequestMatchers.header("foo", "bar", "baz").match(this.request);
}
@Test
void headersWithMissingHeader() {
assertThatAssertionError()
.isThrownBy(() -> MockRestRequestMatchers.header("foo", "bar").match(this.request))
.withMessage("Expected header <foo> to exist but was null");
public void headersWithMissingHeader() throws Exception {
assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", "bar").match(this.request))
.isInstanceOf(AssertionError.class)
.hasMessageContaining("but was null");
}
@Test
void headersWithMissingValue() {
this.request.getHeaders().put("foo", Arrays.asList("bar"));
public void headersWithMissingValue() throws Exception {
this.request.getHeaders().put("foo", Collections.singletonList("bar"));
assertThatAssertionError()
.isThrownBy(() -> MockRestRequestMatchers.header("foo", "bar", "baz").match(this.request))
.withMessageContaining("to have at least <2> values");
assertThatThrownBy(() -> MockRestRequestMatchers.header("foo", "bar", "baz").match(this.request))
.isInstanceOf(AssertionError.class)
.hasMessageContaining("to have at least <2> values");
}
@Test
void queryParam() throws Exception {
public 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
void queryParamMissing() throws Exception {
public void queryParamMissing() throws Exception {
this.request.setURI(new URI("http://www.foo.example/a"));
assertThatAssertionError()
.isThrownBy(() -> MockRestRequestMatchers.queryParam("foo", "bar").match(this.request))
.withMessage("Expected query param <foo> to exist but was null");
assertThatThrownBy(() -> MockRestRequestMatchers.queryParam("foo", "bar").match(this.request))
.isInstanceOf(AssertionError.class)
.hasMessageContaining("but was null");
}
@Test
void queryParamMissingValue() throws Exception {
public void queryParamMissingValue() throws Exception {
this.request.setURI(new URI("http://www.foo.example/a?foo=bar&foo=baz"));
assertThatAssertionError()
.isThrownBy(() -> MockRestRequestMatchers.queryParam("foo", "bad").match(this.request))
.withMessageContaining("expected:<bad> but was:<bar>");
assertThatThrownBy(() -> MockRestRequestMatchers.queryParam("foo", "bad").match(this.request))
.isInstanceOf(AssertionError.class)
.hasMessageContaining("expected:<bad> but was:<bar>");
}
@Test
void queryParamContains() throws Exception {
public 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
void queryParamContainsWithMissingValue() throws Exception {
public void queryParamContainsWithMissingValue() throws Exception {
this.request.setURI(new URI("http://www.foo.example/a?foo=bar&foo=baz"));
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);
assertThatThrownBy(() -> MockRestRequestMatchers.queryParam("foo", containsString("bx")).match(this.request))
.isInstanceOf(AssertionError.class)
.hasMessageContaining("was \"bar\"");
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2019 the original author 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,9 +31,4 @@ public class ForwardController {
return "forward:/a";
}
@RequestMapping("/infiniteForward")
public String infiniteForward() {
return "forward:/infiniteForward";
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,6 @@ 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;
/**
@@ -81,13 +80,6 @@ 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 {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -84,12 +84,11 @@ public class GenericMessageEndpointFactory extends AbstractMessageEndpointFactor
@Override
public MessageEndpoint createEndpoint(XAResource xaResource) throws UnavailableException {
GenericMessageEndpoint endpoint = (GenericMessageEndpoint) super.createEndpoint(xaResource);
Object target = getMessageListener();
ProxyFactory proxyFactory = new ProxyFactory(target);
ProxyFactory proxyFactory = new ProxyFactory(getMessageListener());
DelegatingIntroductionInterceptor introduction = new DelegatingIntroductionInterceptor(endpoint);
introduction.suppressInterface(MethodInterceptor.class);
proxyFactory.addAdvice(introduction);
return (MessageEndpoint) proxyFactory.getProxy(target.getClass().getClassLoader());
return (MessageEndpoint) proxyFactory.getProxy();
}
/**
@@ -1,93 +0,0 @@
/*
* 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;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,7 +37,6 @@ 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.
@@ -55,6 +54,7 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest {
private final ReactiveRequest.Builder builder;
public JettyClientHttpRequest(Request jettyRequest, DataBufferFactory bufferFactory) {
this.jettyRequest = jettyRequest;
this.bufferFactory = bufferFactory;
@@ -96,7 +96,8 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest {
.as(chunks -> ReactiveRequest.Content.fromPublisher(chunks, getContentType()));
this.builder.content(content);
sink.success();
}).then(doCommit());
})
.then(doCommit());
}
@Override
@@ -143,11 +144,7 @@ class JettyClientHttpRequest extends AbstractClientHttpRequest {
@Override
protected HttpHeaders initReadOnlyHeaders() {
MultiValueMap<String, String> headers = (Jetty10HttpFieldsHelper.jetty10Present() ?
Jetty10HttpFieldsHelper.getHttpHeaders(this.jettyRequest) :
new JettyHeadersAdapter(this.jettyRequest.getHeaders()));
return HttpHeaders.readOnlyHttpHeaders(headers);
return HttpHeaders.readOnlyHttpHeaders(new JettyHeadersAdapter(this.jettyRequest.getHeaders()));
}
public ReactiveRequest toReactiveRequest() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +16,13 @@
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;
@@ -30,9 +32,11 @@ 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.
@@ -46,6 +50,10 @@ 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;
@@ -54,12 +62,23 @@ 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 = (Jetty10HttpFieldsHelper.jetty10Present() ?
Jetty10HttpFieldsHelper.getHttpHeaders(reactiveResponse.getResponse()) :
MultiValueMap<String, String> headers = (jetty10Present ?
Jetty10HttpFieldsHelper.getHttpHeaders(reactiveResponse) :
new JettyHeadersAdapter(reactiveResponse.getHeaders()));
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
@@ -113,4 +132,40 @@ 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;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -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>
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -57,6 +57,7 @@ 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;
@@ -836,7 +837,7 @@ public class Jackson2ObjectMapperBuilder {
objectMapper.configure((MapperFeature) feature, enabled);
}
else {
throw new IllegalArgumentException("Unknown feature class: " + feature.getClass().getName());
throw new FatalBeanException("Unknown feature class: " + feature.getClass().getName());
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ 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;
@@ -79,15 +80,40 @@ class ReactorServerHttpRequest extends AbstractServerHttpRequest {
return new URI(resolveBaseUrl(request) + resolveRequestUri(request));
}
private static String resolveBaseUrl(HttpServerRequest request) {
String scheme = request.scheme();
int port = request.hostPort();
return scheme + "://" + request.hostName() + (usePort(scheme, port) ? ":" + port : "");
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 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 getScheme(HttpServerRequest request) {
return request.scheme();
}
private static String resolveRequestUri(HttpServerRequest request) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -818,7 +818,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
logger.debug("Response " + (status != null ? status : code));
}
catch (IOException ex) {
logger.debug("Failed to obtain response status code", ex);
// ignore
}
}
if (hasError) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -133,6 +133,22 @@ 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.
*/
@@ -146,8 +162,6 @@ public class ContextLoader {
@Nullable
private static volatile WebApplicationContext currentContext;
@Nullable
private static Properties defaultStrategies;
/**
* The root WebApplicationContext instance that this loader manages.
@@ -343,18 +357,6 @@ 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-2023 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -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-2023 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -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,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2018 the original author 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-2023 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -83,9 +83,11 @@ 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;
/**
@@ -104,7 +106,7 @@ class Jackson2ObjectMapperBuilderTests {
@Test
void unknownFeature() {
assertThatIllegalArgumentException().isThrownBy(() ->
assertThatExceptionOfType(FatalBeanException.class).isThrownBy(() ->
Jackson2ObjectMapperBuilder.json().featuresToEnable(Boolean.TRUE).build());
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -62,8 +62,10 @@ 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.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Test cases for {@link Jackson2ObjectMapperFactoryBean}.
@@ -86,7 +88,8 @@ public class Jackson2ObjectMapperFactoryBeanTests {
@Test
public void unknownFeature() {
this.factory.setFeaturesToEnable(Boolean.TRUE);
assertThatIllegalArgumentException().isThrownBy(this.factory::afterPropertiesSet);
assertThatExceptionOfType(FatalBeanException.class).isThrownBy(
this.factory::afterPropertiesSet);
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,10 +17,8 @@
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;
@@ -30,14 +28,16 @@ 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,7 +122,6 @@ import static java.util.stream.Collectors.joining;
* </pre>
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 5.0
*/
public class ResolvableMethod {
@@ -615,10 +614,16 @@ public class ResolvableMethod {
}
private static class MethodInvocationInterceptor implements MethodInterceptor, InvocationHandler {
private static class MethodInvocationInterceptor
implements org.springframework.cglib.proxy.MethodInterceptor, MethodInterceptor {
private Method invokedMethod;
Method getInvokedMethod() {
return this.invokedMethod;
}
@Override
@Nullable
public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) {
@@ -633,23 +638,20 @@ public class ResolvableMethod {
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return intercept(proxy, method, args, null);
}
Method getInvokedMethod() {
return this.invokedMethod;
public Object invoke(org.aopalliance.intercept.MethodInvocation inv) throws Throwable {
return intercept(inv.getThis(), inv.getMethod(), inv.getArguments(), null);
}
}
@SuppressWarnings("unchecked")
private static <T> T initProxy(Class<?> type, MethodInvocationInterceptor interceptor) {
Assert.notNull(type, "'type' must not be null");
if (type.isInterface()) {
return (T) Proxy.newProxyInstance(type.getClassLoader(),
new Class<?>[] {type, Supplier.class},
interceptor);
ProxyFactory factory = new ProxyFactory(EmptyTargetSource.INSTANCE);
factory.addInterface(type);
factory.addInterface(Supplier.class);
factory.addAdvice(interceptor);
return (T) factory.getProxy();
}
else {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2018 the original author 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 (isMediaTypeAll(mediaTypes)) {
if (mediaTypes.equals(RequestedContentTypeResolver.MEDIA_TYPE_ALL_LIST)) {
continue;
}
return mediaTypes;
@@ -103,11 +103,6 @@ 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