Compare commits

...

37 Commits

Author SHA1 Message Date
Spring Builds aca8b5d2f5 Release v5.3.37 2024-06-13 08:27:22 +00:00
Juergen Hoeller 0ce1ef97ce Polishing (aligned with 6.0.x) 2024-06-12 14:10:08 +02:00
Juergen Hoeller e26ea007fa Upgrade to spring-javaformat-checkstyle 0.0.42 and Netty 4.1.111 2024-06-12 14:09:08 +02:00
Stéphane Nicoll c10b6c2c3e Upgrade to Reactor 2020.0.45
Closes gh-33010
2024-06-11 14:02:14 +02:00
Juergen Hoeller 63568e6c4f Skip ajc-compiled aspects for ajc-compiled target classes
Includes defensive ignoring of incompatible aspect types.

Closes gh-32970

(cherry picked from commit 0ea96b4806)
2024-06-06 21:09:27 +02:00
Juergen Hoeller d41048528f Avoid NoSuchMethodException for annotation attribute checks
Closes gh-32921

(cherry picked from commit b08883b65c)
2024-06-03 12:56:36 +02:00
Sam Brannen 2384474615 Support compilation of map indexing with primitive in SpEL
Prior to this commit, the Spring Expression Language (SpEL) failed to
compile an expression that indexed into a Map using a primitive literal
(boolean, int, long, float, or double).

This commit adds support for compilation of such expressions by
ensuring that primitive literals are boxed into their corresponding
wrapper types in the compiled bytecode.

Closes gh-32903

(cherry picked from commit aed1d5f762)
2024-05-28 10:55:22 +02:00
Sam Brannen 0f04052ba1 Support compilation of array and list indexing with Integer in SpEL
Prior to this commit, the Spring Expression Language (SpEL) failed to
compile an expression that indexed into an array or list using an
Integer.

This commit adds support for compilation of such expressions by
ensuring that an Integer is unboxed into an int in the compiled
bytecode.

See gh-32694
Closes gh-32908

(cherry picked from commit 079d53c8d6)
2024-05-27 17:39:43 +02:00
Juergen Hoeller 98aa03c0c9 Test detection of original generic method for CGLIB bridge method
Includes isBridgedCandidateFor optimization (aligned with 6.0.x)

See gh-32888
2024-05-24 12:41:38 +02:00
Juergen Hoeller 3e45b76132 Polishing
(cherry picked from commit 6c08d93992)
2024-05-23 17:21:34 +02:00
Juergen Hoeller d42c9204ef Defensive handling of incompatible advice methods
This covers AspectJ transaction and caching aspects when encountered by Spring AOP.

Closes gh-32882
See gh-32793

(cherry picked from commit 6d7cd9c7dc)
2024-05-23 17:17:03 +02:00
Spring Builds d2babd46a2 Next development version (v5.3.37-SNAPSHOT) 2024-05-22 15:27:49 +00:00
Juergen Hoeller eff9f5b92a Select most specific advice method in case of override
Closes gh-32865

(cherry picked from commit ea596aa211)
2024-05-22 10:24:01 +02:00
Juergen Hoeller f1fed9c174 Polishing
(cherry picked from commit 20dea0dae2)
2024-05-21 18:18:30 +02:00
Juergen Hoeller d2e7cf4395 Default fallback parsing for UTC without milliseconds
Closes gh-32856

(cherry picked from commit fee17e11ba)
2024-05-21 18:07:31 +02:00
Juergen Hoeller 47a7abee8f Polishing 2024-05-21 12:28:48 +02:00
Juergen Hoeller ef2c140d3c Defensively catch and log pointcut parsing exceptions
Closes gh-32838
See gh-32793

(cherry picked from commit 617833bec9)
2024-05-17 12:50:50 +02:00
Spring Builds de6cf845b1 Next development version (v5.3.36-SNAPSHOT) 2024-05-16 07:09:30 +00:00
Juergen Hoeller 7da43a80e4 Upgrade to Reactor 2020.0.44 and Netty 4.1.109
Closes gh-32788
2024-05-14 22:54:32 +02:00
Juergen Hoeller 8323c87ea2 Polishing 2024-05-14 13:31:30 +02:00
Juergen Hoeller 0c65a5e04a Accept ajc-compiled @Aspect classes for Spring AOP proxy usage
AspectJExpressionPointcut leniently ignores unsupported expression.

Closes gh-32793
2024-05-14 13:14:09 +02:00
rstoyanchev ca9eb57c94 Update MockMvc section on Streaming in the docs
Closes gh-32687
2024-05-13 12:40:46 +01:00
Stéphane Nicoll df8374693b Adapt docs deployment properties
This commit fixes the artifact properties we set for "framework-docs"
artifacts. These have a different name as of 6.1.x and were backported
as is.

Closes gh-32781
2024-05-08 15:33:40 +02:00
Juergen Hoeller 9f35debdc6 Unwrap raw target Query instance in case of proxy mismatch
Closes gh-32766

(cherry picked from commit 59a125d06f)
2024-05-06 20:38:02 +02:00
Sam Brannen 8fe545edcd Fix compilation error in test 2024-05-03 12:37:42 +03:00
Sam Brannen f5baa329f7 Include repeatable annotation container in MergedAnnotations results
A bug has existed in Spring's MergedAnnotations support since it was
introduced in Spring Framework 5.2. Specifically, if the
MergedAnnotations API is used to search for annotations with "standard
repeatable annotation" support enabled (which is the default), it's
possible to search for a repeatable annotation but not for the
repeatable annotation's container annotation.

The reason is that MergedAnnotationFinder.process(Object, int, Object,
Annotation) does not process the container annotation and instead only
processes the "contained" annotations, which prevents a container
annotation from being included in search results.

In #29685, we fixed a bug that prevented the MergedAnnotations support
from recognizing an annotation as a container if the container
annotation declares attributes other than the required `value`
attribute. As a consequence of that bug fix, since Spring Framework
5.3.25, the MergedAnnotations infrastructure considers such an
annotation a container, and due to the aforementioned bug the container
is no longer processed, which results in a regression in behavior for
annotation searches for such a container annotation.

This commit addresses the original bug as well as the regression by
processing container annotations in addition to the contained
repeatable annotations.

See gh-29685
Closes gh-32731

(cherry picked from commit 4baad16437)
2024-05-03 12:21:32 +03:00
Juergen Hoeller 924b684345 Consistently propagate ApplicationStartup to BeanFactory
Closes gh-32747

(cherry picked from commit 25cedcfb99)
2024-05-01 18:15:20 +02:00
Juergen Hoeller a48e2f31a3 Polishing 2024-05-01 16:07:04 +02:00
Juergen Hoeller 1833aafc3f Ignore non-String keys in PropertiesPropertySource.getPropertyNames()
Closes gh-32742

(cherry picked from commit 610626aec6)
2024-05-01 16:04:42 +02:00
Brian Clozel b8bc780365 Fix build warnings
See gh-32725
2024-04-30 19:09:03 +02:00
Arjen Poutsma b3724ebf84 Fix guard against multiple subscriptions
This commit changes the guard against multiple subscriptions, as the
previously used doOnSubscribe hook could not function as guard in
certain scenarios.

See gh-32727
Closes gh-32728
2024-04-30 16:20:54 +02:00
Brian Clozel 2e74a4d878 Upgrade to gradle-enterprise-conventions 0.0.17
Closes gh-32725
2024-04-29 13:24:11 +02:00
Juergen Hoeller 0c307b513e Polishing 2024-04-23 16:41:52 +02:00
Juergen Hoeller 6b6beec4ee Skip close lock if acquired by other thread already
Closes gh-32445

(cherry picked from commit d151931f86)
2024-04-23 16:26:26 +02:00
Juergen Hoeller 520a1130b8 Polishing (aligned with 6.1.x) 2024-04-18 13:15:49 +02:00
yhao3 ceeb55291a Update links to HttpOnly documentation at OWASP in ResponseCookie
See gh-32663
Closes gh-32668

(cherry picked from commit 7f27ba3902)
2024-04-18 12:01:30 +02:00
Spring Builds cbc2ab6ab9 Next development version (v5.3.35-SNAPSHOT) 2024-04-11 07:16:36 +00:00
67 changed files with 1473 additions and 722 deletions
@@ -35,9 +35,7 @@ jobs:
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
run: ./gradlew -PdeploymentRepository=$(pwd)/deployment-repository build publishAllPublicationsToDeploymentRepository
- name: Deploy
uses: spring-io/artifactory-deploy-action@v0.0.1
@@ -51,9 +49,10 @@ jobs:
signing-key: ${{ secrets.GPG_PRIVATE_KEY }}
signing-passphrase: ${{ secrets.GPG_PASSPHRASE }}
artifact-properties: |
/**/framework-api-*.zip::zip.name=spring-framework,zip.deployed=false
/**/framework-api-*-docs.zip::zip.type=docs
/**/framework-api-*-schema.zip::zip.type=schema
/**/spring-*.zip::zip.name=spring-framework,zip.deployed=false
/**/spring-*-docs.zip::zip.type=docs
/**/spring-*-dist.zip::zip.type=dist
/**/spring-*-schema.zip::zip.type=schema
- name: Send notification
uses: ./.github/actions/send-notification
if: always()
+2 -4
View File
@@ -68,9 +68,7 @@ jobs:
env:
CI: 'true'
GRADLE_ENTERPRISE_URL: 'https://ge.spring.io'
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
run: ./gradlew check
- name: Send notification
uses: ./.github/actions/send-notification
@@ -79,4 +77,4 @@ jobs:
webhook-url: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
status: ${{ job.status }}
build-scan-url: ${{ steps.build.outputs.build-scan-url }}
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
run-name: ${{ format('{0} | {1} | Java {2}', github.ref_name, matrix.os.name, matrix.java.version) }}
+3 -3
View File
@@ -28,8 +28,8 @@ configure(allprojects) { project ->
dependencyManagement {
imports {
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.7"
mavenBom "io.netty:netty-bom:4.1.108.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.43"
mavenBom "io.netty:netty-bom:4.1.111.Final"
mavenBom "io.projectreactor:reactor-bom:2020.0.45"
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR13"
mavenBom "io.rsocket:rsocket-bom:1.1.3"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.54.v20240208"
@@ -362,7 +362,7 @@ configure([rootProject] + javaProjects) { project ->
// JSR-305 only used for non-required meta-annotations
compileOnly("com.google.code.findbugs:jsr305")
testCompileOnly("com.google.code.findbugs:jsr305")
checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.41")
checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:0.0.42")
}
ext.javadocLinks = [
+1 -3
View File
@@ -5,9 +5,7 @@ anchors:
password: ((github-ci-release-token))
branch: ((branch))
gradle-enterprise-task-params: &gradle-enterprise-task-params
GRADLE_ENTERPRISE_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
GRADLE_ENTERPRISE_CACHE_USERNAME: ((gradle_enterprise_cache_user.username))
GRADLE_ENTERPRISE_CACHE_PASSWORD: ((gradle_enterprise_cache_user.password))
DEVELOCITY_ACCESS_KEY: ((gradle_enterprise_secret_access_key))
sonatype-task-params: &sonatype-task-params
SONATYPE_USERNAME: ((sonatype-username))
SONATYPE_PASSWORD: ((sonatype-password))
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.3.34-SNAPSHOT
version=5.3.37
org.gradle.jvmargs=-Xmx2048m
org.gradle.caching=true
org.gradle.parallel=true
@@ -63,7 +63,7 @@ class EnableCachingIntegrationTests {
// attempt was made to look up the AJ aspect. It's due to classpath issues
// in .integration-tests that it's not found.
assertThatException().isThrownBy(ctx::refresh)
.withMessageContaining("AspectJCachingConfiguration");
.withMessageContaining("AspectJCachingConfiguration");
}
@@ -561,8 +561,7 @@ public class EnvironmentSystemIntegrationTests {
{
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setRequiredProperties("foo", "bar");
assertThatExceptionOfType(MissingRequiredPropertiesException.class).isThrownBy(
ctx::refresh);
assertThatExceptionOfType(MissingRequiredPropertiesException.class).isThrownBy(ctx::refresh);
}
{
@@ -97,7 +97,7 @@ class EnableTransactionManagementIntegrationTests {
ctx.register(Config.class, AspectJTxConfig.class);
// this test is a bit fragile, but gets the job done, proving that an
// attempt was made to look up the AJ aspect. It's due to classpath issues
// in .integration-tests that it's not found.
// in integration-tests that it's not found.
assertThatException()
.isThrownBy(ctx::refresh)
.withMessageContaining("AspectJJtaTransactionManagementConfiguration");
+3 -3
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.develocity" version "3.17.2"
id "io.spring.ge.conventions" version "0.0.17"
}
include "spring-aop"
@@ -42,7 +42,7 @@ rootProject.children.each {project ->
}
settings.gradle.projectsLoaded {
gradleEnterprise {
develocity {
buildScan {
File buildDir = settings.gradle.rootProject.getBuildDir()
buildDir.mkdirs()
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.aop.aspectj;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
@@ -42,6 +43,7 @@ import org.aspectj.weaver.tools.PointcutParameter;
import org.aspectj.weaver.tools.PointcutParser;
import org.aspectj.weaver.tools.PointcutPrimitive;
import org.aspectj.weaver.tools.ShadowMatch;
import org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.IntroductionAwareMethodMatcher;
@@ -85,6 +87,8 @@ import org.springframework.util.StringUtils;
public class AspectJExpressionPointcut extends AbstractExpressionPointcut
implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {
private static final String AJC_MAGIC = "ajc$";
private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();
static {
@@ -106,6 +110,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Nullable
private Class<?> pointcutDeclarationScope;
private boolean aspectCompiledByAjc;
private String[] pointcutParameterNames = new String[0];
private Class<?>[] pointcutParameterTypes = new Class<?>[0];
@@ -119,6 +125,8 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Nullable
private transient PointcutExpression pointcutExpression;
private transient boolean pointcutParsingFailed = false;
private transient Map<Method, ShadowMatch> shadowMatchCache = new ConcurrentHashMap<>(32);
@@ -135,7 +143,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
* @param paramTypes the parameter types for the pointcut
*/
public AspectJExpressionPointcut(Class<?> declarationScope, String[] paramNames, Class<?>[] paramTypes) {
this.pointcutDeclarationScope = declarationScope;
setPointcutDeclarationScope(declarationScope);
if (paramNames.length != paramTypes.length) {
throw new IllegalStateException(
"Number of pointcut parameter names must match number of pointcut parameter types");
@@ -150,6 +158,7 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
*/
public void setPointcutDeclarationScope(Class<?> pointcutDeclarationScope) {
this.pointcutDeclarationScope = pointcutDeclarationScope;
this.aspectCompiledByAjc = compiledByAjc(pointcutDeclarationScope);
}
/**
@@ -174,25 +183,30 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public ClassFilter getClassFilter() {
obtainPointcutExpression();
checkExpression();
return this;
}
@Override
public MethodMatcher getMethodMatcher() {
obtainPointcutExpression();
checkExpression();
return this;
}
/**
* Check whether this pointcut is ready to match,
* lazily building the underlying AspectJ pointcut expression.
* Check whether this pointcut is ready to match.
*/
private PointcutExpression obtainPointcutExpression() {
private void checkExpression() {
if (getExpression() == null) {
throw new IllegalStateException("Must set property 'expression' before attempting to match");
}
}
/**
* Lazily build the underlying AspectJ pointcut expression.
*/
private PointcutExpression obtainPointcutExpression() {
if (this.pointcutExpression == null) {
this.pointcutClassLoader = determinePointcutClassLoader();
this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
@@ -269,10 +283,18 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean matches(Class<?> targetClass) {
PointcutExpression pointcutExpression = obtainPointcutExpression();
if (this.pointcutParsingFailed) {
// Pointcut parsing failed before below -> avoid trying again.
return false;
}
if (this.aspectCompiledByAjc && compiledByAjc(targetClass)) {
// ajc-compiled aspect class for ajc-compiled target class -> already weaved.
return false;
}
try {
try {
return pointcutExpression.couldMatchJoinPointsInType(targetClass);
return obtainPointcutExpression().couldMatchJoinPointsInType(targetClass);
}
catch (ReflectionWorldException ex) {
logger.debug("PointcutExpression matching rejected target class - trying fallback expression", ex);
@@ -283,6 +305,12 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
}
}
}
catch (IllegalArgumentException | IllegalStateException | UnsupportedPointcutPrimitiveException ex) {
this.pointcutParsingFailed = true;
if (logger.isDebugEnabled()) {
logger.debug("Pointcut parser rejected expression [" + getExpression() + "]: " + ex);
}
}
catch (Throwable ex) {
logger.debug("PointcutExpression matching rejected target class", ex);
}
@@ -291,7 +319,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) {
obtainPointcutExpression();
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
// Special handling for this, target, @this, @target, @annotation
@@ -329,7 +356,6 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
obtainPointcutExpression();
ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
// Bind Spring AOP proxy to AspectJ "this" and Spring AOP target to AspectJ target,
@@ -517,6 +543,15 @@ public class AspectJExpressionPointcut extends AbstractExpressionPointcut
return shadowMatch;
}
private static boolean compiledByAjc(Class<?> clazz) {
for (Field field : clazz.getDeclaredFields()) {
if (field.getName().startsWith(AJC_MAGIC)) {
return true;
}
}
return false;
}
@Override
public boolean equals(@Nullable Object other) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package org.springframework.aop.aspectj.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
@@ -57,8 +56,6 @@ import org.springframework.lang.Nullable;
*/
public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFactory {
private static final String AJC_MAGIC = "ajc$";
private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] {
Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
@@ -69,37 +66,11 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
protected final ParameterNameDiscoverer parameterNameDiscoverer = new AspectJAnnotationParameterNameDiscoverer();
/**
* We consider something to be an AspectJ aspect suitable for use by the Spring AOP system
* if it has the @Aspect annotation, and was not compiled by ajc. The reason for this latter test
* is that aspects written in the code-style (AspectJ language) also have the annotation present
* when compiled by ajc with the -1.5 flag, yet they cannot be consumed by Spring AOP.
*/
@Override
public boolean isAspect(Class<?> clazz) {
return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
}
private boolean hasAspectAnnotation(Class<?> clazz) {
return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
}
/**
* We need to detect this as "code-style" AspectJ aspects should not be
* interpreted by Spring AOP.
*/
private boolean compiledByAjc(Class<?> clazz) {
// The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and
// annotation-style aspects. Therefore there is no 'clean' way to tell them apart. Here we rely on
// an implementation detail of the AspectJ compiler.
for (Field field : clazz.getDeclaredFields()) {
if (field.getName().startsWith(AJC_MAGIC)) {
return true;
}
}
return false;
}
@Override
public void validate(Class<?> aspectClass) throws AopConfigException {
// If the parent has the annotation and isn't abstract it's an error
@@ -124,6 +95,7 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
}
}
/**
* Find and return the first AspectJ annotation on the given method
* (there <i>should</i> only be one anyway...).
@@ -163,7 +135,7 @@ public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFac
/**
* Class modelling an AspectJ annotation, exposing its type enumeration and
* Class modeling an AspectJ annotation, exposing its type enumeration and
* pointcut String.
* @param <A> the annotation type
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -126,10 +126,16 @@ public class AspectMetadata implements Serializable {
* Extract contents from String of form {@code pertarget(contents)}.
*/
private String findPerClause(Class<?> aspectClass) {
String str = aspectClass.getAnnotation(Aspect.class).value();
int beginIndex = str.indexOf('(') + 1;
int endIndex = str.length() - 1;
return str.substring(beginIndex, endIndex);
Aspect ann = aspectClass.getAnnotation(Aspect.class);
if (ann == null) {
return "";
}
String value = ann.value();
int beginIndex = value.indexOf('(');
if (beginIndex < 0) {
return "";
}
return value.substring(beginIndex + 1, value.length() - 1);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author 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,9 +22,12 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.reflect.PerClauseKind;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.AopConfigException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.lang.Nullable;
@@ -40,6 +43,8 @@ import org.springframework.util.Assert;
*/
public class BeanFactoryAspectJAdvisorsBuilder {
private static final Log logger = LogFactory.getLog(BeanFactoryAspectJAdvisorsBuilder.class);
private final ListableBeanFactory beanFactory;
private final AspectJAdvisorFactory advisorFactory;
@@ -102,30 +107,37 @@ public class BeanFactoryAspectJAdvisorsBuilder {
continue;
}
if (this.advisorFactory.isAspect(beanType)) {
aspectNames.add(beanName);
AspectMetadata amd = new AspectMetadata(beanType, beanName);
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
MetadataAwareAspectInstanceFactory factory =
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
if (this.beanFactory.isSingleton(beanName)) {
this.advisorsCache.put(beanName, classAdvisors);
try {
AspectMetadata amd = new AspectMetadata(beanType, beanName);
if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
MetadataAwareAspectInstanceFactory factory =
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
if (this.beanFactory.isSingleton(beanName)) {
this.advisorsCache.put(beanName, classAdvisors);
}
else {
this.aspectFactoryCache.put(beanName, factory);
}
advisors.addAll(classAdvisors);
}
else {
// Per target or per this.
if (this.beanFactory.isSingleton(beanName)) {
throw new IllegalArgumentException("Bean with name '" + beanName +
"' is a singleton, but aspect instantiation model is not singleton");
}
MetadataAwareAspectInstanceFactory factory =
new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
this.aspectFactoryCache.put(beanName, factory);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
advisors.addAll(classAdvisors);
aspectNames.add(beanName);
}
else {
// Per target or per this.
if (this.beanFactory.isSingleton(beanName)) {
throw new IllegalArgumentException("Bean with name '" + beanName +
"' is a singleton, but aspect instantiation model is not singleton");
catch (IllegalArgumentException | IllegalStateException | AopConfigException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring incompatible aspect [" + beanType.getName() + "]: " + ex);
}
MetadataAwareAspectInstanceFactory factory =
new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
this.aspectFactoryCache.put(beanName, factory);
advisors.addAll(this.advisorFactory.getAdvisors(factory));
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,6 +50,7 @@ import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConvertingComparator;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodFilter;
import org.springframework.util.StringUtils;
@@ -133,17 +134,19 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
List<Advisor> advisors = new ArrayList<>();
for (Method method : getAdvisorMethods(aspectClass)) {
// Prior to Spring Framework 5.2.7, advisors.size() was supplied as the declarationOrderInAspect
// to getAdvisor(...) to represent the "current position" in the declared methods list.
// However, since Java 7 the "current position" is not valid since the JDK no longer
// returns declared methods in the order in which they are declared in the source code.
// Thus, we now hard code the declarationOrderInAspect to 0 for all advice methods
// discovered via reflection in order to support reliable advice ordering across JVM launches.
// Specifically, a value of 0 aligns with the default value used in
// AspectJPrecedenceComparator.getAspectDeclarationOrder(Advisor).
Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
if (advisor != null) {
advisors.add(advisor);
if (method.equals(ClassUtils.getMostSpecificMethod(method, aspectClass))) {
// Prior to Spring Framework 5.2.7, advisors.size() was supplied as the declarationOrderInAspect
// to getAdvisor(...) to represent the "current position" in the declared methods list.
// However, since Java 7 the "current position" is not valid since the JDK no longer
// returns declared methods in the order in which they are declared in the source code.
// Thus, we now hard code the declarationOrderInAspect to 0 for all advice methods
// discovered via reflection in order to support reliable advice ordering across JVM launches.
// Specifically, a value of 0 aligns with the default value used in
// AspectJPrecedenceComparator.getAspectDeclarationOrder(Advisor).
Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
if (advisor != null) {
advisors.add(advisor);
}
}
}
@@ -210,8 +213,16 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto
return null;
}
return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
try {
return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}
catch (IllegalArgumentException | IllegalStateException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring incompatible advice method: " + candidateAdviceMethod, ex);
}
return null;
}
}
@Nullable
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,7 +44,8 @@ import org.springframework.util.CollectionUtils;
/**
* Base class for AOP proxy configuration managers.
* These are not themselves AOP proxies, but subclasses of this class are
*
* <p>These are not themselves AOP proxies, but subclasses of this class are
* normally factories from which AOP proxy instances are obtained directly.
*
* <p>This class frees subclasses of the housekeeping of Advices
@@ -52,7 +53,8 @@ import org.springframework.util.CollectionUtils;
* methods, which are provided by subclasses.
*
* <p>This class is serializable; subclasses need not be.
* This class is used to hold snapshots of proxies.
*
* <p>This class is used to hold snapshots of proxies.
*
* @author Rod Johnson
* @author Juergen Hoeller
@@ -104,7 +106,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
}
/**
* Create a AdvisedSupport instance with the given parameters.
* Create an {@code AdvisedSupport} instance with the given parameters.
* @param interfaces the proxied interfaces
*/
public AdvisedSupport(Class<?>... interfaces) {
@@ -115,7 +117,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
/**
* Set the given object as target.
* Will create a SingletonTargetSource for the object.
* <p>Will create a SingletonTargetSource for the object.
* @see #setTargetSource
* @see org.springframework.aop.target.SingletonTargetSource
*/
@@ -344,8 +346,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
private void validateIntroductionAdvisor(IntroductionAdvisor advisor) {
advisor.validateInterfaces();
// If the advisor passed validation, we can make the change.
Class<?>[] ifcs = advisor.getInterfaces();
for (Class<?> ifc : ifcs) {
for (Class<?> ifc : advisor.getInterfaces()) {
addInterface(ifc);
}
}
@@ -491,9 +492,9 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
}
/**
* Copy the AOP configuration from the given AdvisedSupport object,
* but allow substitution of a fresh TargetSource and a given interceptor chain.
* @param other the AdvisedSupport object to take proxy configuration from
* Copy the AOP configuration from the given {@link AdvisedSupport} object,
* but allow substitution of a fresh {@link TargetSource} and a given interceptor chain.
* @param other the {@code AdvisedSupport} object to take proxy configuration from
* @param targetSource the new TargetSource
* @param advisors the Advisors for the chain
*/
@@ -513,8 +514,8 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
}
/**
* Build a configuration-only copy of this AdvisedSupport,
* replacing the TargetSource.
* Build a configuration-only copy of this {@link AdvisedSupport},
* replacing the {@link TargetSource}.
*/
AdvisedSupport getConfigurationOnlyCopy() {
AdvisedSupport copy = new AdvisedSupport();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author 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,9 +23,6 @@ import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.weaver.tools.PointcutExpression;
import org.aspectj.weaver.tools.PointcutPrimitive;
import org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import test.annotation.EmptySpringAnnotation;
@@ -42,14 +39,13 @@ import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.beans.testfixture.beans.subpkg.DeepBean;
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;
/**
* @author Rob Harrop
* @author Rod Johnson
* @author Chris Beams
* @author Juergen Hoeller
*/
public class AspectJExpressionPointcutTests {
@@ -65,7 +61,7 @@ public class AspectJExpressionPointcutTests {
@BeforeEach
public void setUp() throws NoSuchMethodException {
public void setup() throws NoSuchMethodException {
getAge = TestBean.class.getMethod("getAge");
setAge = TestBean.class.getMethod("setAge", int.class);
setSomeNumber = TestBean.class.getMethod("setSomeNumber", Number.class);
@@ -175,25 +171,25 @@ public class AspectJExpressionPointcutTests {
@Test
public void testFriendlyErrorOnNoLocationClassMatching() {
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
assertThatIllegalStateException().isThrownBy(() ->
pc.matches(ITestBean.class))
.withMessageContaining("expression");
assertThatIllegalStateException()
.isThrownBy(() -> pc.getClassFilter().matches(ITestBean.class))
.withMessageContaining("expression");
}
@Test
public void testFriendlyErrorOnNoLocation2ArgMatching() {
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
assertThatIllegalStateException().isThrownBy(() ->
pc.matches(getAge, ITestBean.class))
.withMessageContaining("expression");
assertThatIllegalStateException()
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class))
.withMessageContaining("expression");
}
@Test
public void testFriendlyErrorOnNoLocation3ArgMatching() {
AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
assertThatIllegalStateException().isThrownBy(() ->
pc.matches(getAge, ITestBean.class, (Object[]) null))
.withMessageContaining("expression");
assertThatIllegalStateException()
.isThrownBy(() -> pc.getMethodMatcher().matches(getAge, ITestBean.class, (Object[]) null))
.withMessageContaining("expression");
}
@@ -210,8 +206,10 @@ public class AspectJExpressionPointcutTests {
// not currently testable in a reliable fashion
//assertDoesNotMatchStringClass(classFilter);
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 12D)).as("Should match with setSomeNumber with Double input").isTrue();
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 11)).as("Should not match setSomeNumber with Integer input").isFalse();
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 12D))
.as("Should match with setSomeNumber with Double input").isTrue();
assertThat(methodMatcher.matches(setSomeNumber, TestBean.class, 11))
.as("Should not match setSomeNumber with Integer input").isFalse();
assertThat(methodMatcher.matches(getAge, TestBean.class)).as("Should not match getAge").isFalse();
assertThat(methodMatcher.isRuntime()).as("Should be a runtime match").isTrue();
}
@@ -246,14 +244,13 @@ public class AspectJExpressionPointcutTests {
@Test
public void testInvalidExpression() {
String expression = "execution(void org.springframework.beans.testfixture.beans.TestBean.setSomeNumber(Number) && args(Double)";
assertThatIllegalArgumentException().isThrownBy(
getPointcut(expression)::getClassFilter); // call to getClassFilter forces resolution
assertThat(getPointcut(expression).getClassFilter().matches(Object.class)).isFalse();
}
private TestBean getAdvisedProxy(String pointcutExpression, CallCountingInterceptor interceptor) {
TestBean target = new TestBean();
Pointcut pointcut = getPointcut(pointcutExpression);
AspectJExpressionPointcut pointcut = getPointcut(pointcutExpression);
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setAdvice(interceptor);
@@ -277,40 +274,29 @@ public class AspectJExpressionPointcutTests {
@Test
public void testWithUnsupportedPointcutPrimitive() {
String expression = "call(int org.springframework.beans.testfixture.beans.TestBean.getAge())";
assertThatExceptionOfType(UnsupportedPointcutPrimitiveException.class).isThrownBy(() ->
getPointcut(expression).getClassFilter()) // call to getClassFilter forces resolution...
.satisfies(ex -> assertThat(ex.getUnsupportedPrimitive()).isEqualTo(PointcutPrimitive.CALL));
assertThat(getPointcut(expression).getClassFilter().matches(Object.class)).isFalse();
}
@Test
public void testAndSubstitution() {
Pointcut pc = getPointcut("execution(* *(..)) and args(String)");
PointcutExpression expr = ((AspectJExpressionPointcut) pc).getPointcutExpression();
assertThat(expr.getPointcutExpression()).isEqualTo("execution(* *(..)) && args(String)");
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String)");
String expr = pc.getPointcutExpression().getPointcutExpression();
assertThat(expr).isEqualTo("execution(* *(..)) && args(String)");
}
@Test
public void testMultipleAndSubstitutions() {
Pointcut pc = getPointcut("execution(* *(..)) and args(String) and this(Object)");
PointcutExpression expr = ((AspectJExpressionPointcut) pc).getPointcutExpression();
assertThat(expr.getPointcutExpression()).isEqualTo("execution(* *(..)) && args(String) && this(Object)");
AspectJExpressionPointcut pc = getPointcut("execution(* *(..)) and args(String) and this(Object)");
String expr = pc.getPointcutExpression().getPointcutExpression();
assertThat(expr).isEqualTo("execution(* *(..)) && args(String) && this(Object)");
}
private Pointcut getPointcut(String expression) {
private AspectJExpressionPointcut getPointcut(String expression) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(expression);
return pointcut;
}
public static class OtherIOther implements IOther {
@Override
public void absquatulate() {
// Empty
}
}
@Test
public void testMatchGenericArgument() {
String expression = "execution(* set*(java.util.List<org.springframework.beans.testfixture.beans.TestBean>) )";
@@ -505,6 +491,15 @@ public class AspectJExpressionPointcutTests {
}
public static class OtherIOther implements IOther {
@Override
public void absquatulate() {
// Empty
}
}
public static class HasGeneric {
public void setFriends(List<TestBean> friends) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author 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.aspectj.lang.annotation.DeclareParents;
import org.aspectj.lang.annotation.DeclarePrecedence;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import test.aop.DefaultLockable;
import test.aop.Lockable;
@@ -76,25 +75,24 @@ abstract class AbstractAspectJAdvisorFactoryTests {
/**
* To be overridden by concrete test subclasses.
* @return the fixture
*/
protected abstract AspectJAdvisorFactory getFixture();
@Test
void rejectsPerCflowAspect() {
assertThatExceptionOfType(AopConfigException.class).isThrownBy(() ->
getFixture().getAdvisors(
assertThatExceptionOfType(AopConfigException.class)
.isThrownBy(() -> getFixture().getAdvisors(
new SingletonMetadataAwareAspectInstanceFactory(new PerCflowAspect(), "someBean")))
.withMessageContaining("PERCFLOW");
.withMessageContaining("PERCFLOW");
}
@Test
void rejectsPerCflowBelowAspect() {
assertThatExceptionOfType(AopConfigException.class).isThrownBy(() ->
getFixture().getAdvisors(
new SingletonMetadataAwareAspectInstanceFactory(new PerCflowBelowAspect(), "someBean")))
.withMessageContaining("PERCFLOWBELOW");
assertThatExceptionOfType(AopConfigException.class)
.isThrownBy(() -> getFixture().getAdvisors(
new SingletonMetadataAwareAspectInstanceFactory(new PerCflowBelowAspect(), "someBean")))
.withMessageContaining("PERCFLOWBELOW");
}
@Test
@@ -385,8 +383,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
assertThat(lockable.locked()).as("Already locked").isTrue();
lockable.lock();
assertThat(lockable.locked()).as("Real target ignores locking").isTrue();
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() ->
lockable.unlock());
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(lockable::unlock);
}
@Test
@@ -413,9 +410,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
lockable.locked();
}
// TODO: Why does this test fail? It hasn't been run before, so it maybe never actually passed...
@Test
@Disabled
void introductionWithArgumentBinding() {
TestBean target = new TestBean();
@@ -523,6 +518,16 @@ abstract class AbstractAspectJAdvisorFactoryTests {
assertThat(aspect.invocations).containsExactly("around - start", "before", "after throwing", "after", "around - end");
}
@Test
void parentAspect() {
TestBean target = new TestBean("Jane", 42);
MetadataAwareAspectInstanceFactory aspectInstanceFactory = new SingletonMetadataAwareAspectInstanceFactory(
new IncrementingAspect(), "incrementingAspect");
ITestBean proxy = (ITestBean) createProxy(target,
getFixture().getAdvisors(aspectInstanceFactory), ITestBean.class);
assertThat(proxy.getAge()).isEqualTo(86); // (42 + 1) * 2
}
@Test
void failureWithoutExplicitDeclarePrecedence() {
TestBean target = new TestBean();
@@ -647,7 +652,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
static class NamedPointcutAspectWithFQN {
@SuppressWarnings("unused")
private ITestBean fieldThatShouldBeIgnoredBySpringAtAspectJProcessing = new TestBean();
private final ITestBean fieldThatShouldBeIgnoredBySpringAtAspectJProcessing = new TestBean();
@Pointcut("execution(* getAge())")
void getAge() {
@@ -767,6 +772,31 @@ abstract class AbstractAspectJAdvisorFactoryTests {
}
@Aspect
abstract static class DoublingAspect {
@Around("execution(* getAge())")
public Object doubleAge(ProceedingJoinPoint pjp) throws Throwable {
return ((int) pjp.proceed()) * 2;
}
}
@Aspect
static class IncrementingAspect extends DoublingAspect {
@Override
public Object doubleAge(ProceedingJoinPoint pjp) throws Throwable {
return ((int) pjp.proceed()) * 2;
}
@Around("execution(* getAge())")
public int incrementAge(ProceedingJoinPoint pjp) throws Throwable {
return ((int) pjp.proceed()) + 1;
}
}
@Aspect
private static class InvocationTrackingAspect {
@@ -824,7 +854,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
@Around("getAge()")
int preventExecution(ProceedingJoinPoint pjp) {
return 666;
return 42;
}
}
@@ -844,7 +874,7 @@ abstract class AbstractAspectJAdvisorFactoryTests {
@Around("getAge()")
int preventExecution(ProceedingJoinPoint pjp) {
return 666;
return 42;
}
}
@@ -1066,7 +1096,7 @@ class PerThisAspect {
// Just to check that this doesn't cause problems with introduction processing
@SuppressWarnings("unused")
private ITestBean fieldThatShouldBeIgnoredBySpringAtAspectJProcessing = new TestBean();
private final ITestBean fieldThatShouldBeIgnoredBySpringAtAspectJProcessing = new TestBean();
@Around("execution(int *.getAge())")
int returnCountAsAge() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,12 +22,13 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author Adrian Colyer
* @author Juergen Hoeller
*/
public class AutoProxyWithCodeStyleAspectsTests {
@Test
@SuppressWarnings("resource")
public void noAutoproxyingOfAjcCompiledAspects() {
public void noAutoProxyingOfAjcCompiledAspects() {
new ClassPathXmlApplicationContext("org/springframework/aop/aspectj/autoproxy/ajcAutoproxyTests.xml");
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2024 the original author 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,10 @@ import org.junit.jupiter.api.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author Ramnivas Laddad
* @author Juergen Hoeller
*/
public class SpringConfiguredWithAutoProxyingTests {
@Test
@@ -2,16 +2,29 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/cache https://www.springframework.org/schema/cache/spring-cache-3.1.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context-2.5.xsd">
<aop:aspectj-autoproxy/>
<bean id="myAspect" class="org.springframework.aop.aspectj.autoproxy.CodeStyleAspect"
factory-method="aspectOf">
<context:spring-configured/>
<cache:annotation-driven mode="aspectj"/>
<bean id="cacheManager" class="org.springframework.cache.support.NoOpCacheManager"/>
<bean id="myAspect" class="org.springframework.aop.aspectj.autoproxy.CodeStyleAspect" factory-method="aspectOf">
<property name="foo" value="bar"/>
</bean>
<bean id="otherBean" class="java.lang.Object"/>
<bean id="otherBean" class="org.springframework.beans.factory.aspectj.ShouldBeConfiguredBySpring"/>
<bean id="yetAnotherBean" class="org.springframework.beans.factory.aspectj.ShouldBeConfiguredBySpring"/>
<bean id="configuredBean" class="org.springframework.beans.factory.aspectj.ShouldBeConfiguredBySpring" lazy-init="true"/>
</beans>
@@ -24,8 +24,7 @@
</property>
</bean>
<bean id="defaultCache"
class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
<bean id="defaultCache" class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
<property name="name" value="default"/>
</bean>
@@ -7,12 +7,10 @@
http://www.springframework.org/schema/task
https://www.springframework.org/schema/task/spring-task.xsd">
<task:annotation-driven mode="aspectj" executor="testExecutor"
exception-handler="testExceptionHandler"/>
<task:annotation-driven mode="aspectj" executor="testExecutor" exception-handler="testExceptionHandler"/>
<task:executor id="testExecutor"/>
<bean id="testExceptionHandler"
class="org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler"/>
<bean id="testExceptionHandler" class="org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler"/>
</beans>
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -835,11 +835,11 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
/**
* This implementation attempts to query the FactoryBean's generic parameter metadata
* if present to determine the object type. If not present, i.e. the FactoryBean is
* declared as a raw type, checks the FactoryBean's {@code getObjectType} method
* declared as a raw type, it checks the FactoryBean's {@code getObjectType} method
* on a plain instance of the FactoryBean, without bean properties applied yet.
* If this doesn't return a type yet, and {@code allowInit} is {@code true} a
* full creation of the FactoryBean is used as fallback (through delegation to the
* superclass's implementation).
* If this doesn't return a type yet and {@code allowInit} is {@code true}, full
* creation of the FactoryBean is attempted as fallback (through delegation to the
* superclass implementation).
* <p>The shortcut check for a FactoryBean is only applied in case of a singleton
* FactoryBean. If the FactoryBean instance itself is not kept as singleton,
* it will be fully created to check the type of its exposed object.
@@ -1080,7 +1080,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
@Override
public void setApplicationStartup(ApplicationStartup applicationStartup) {
Assert.notNull(applicationStartup, "applicationStartup should not be null");
Assert.notNull(applicationStartup, "ApplicationStartup must not be null");
this.applicationStartup = applicationStartup;
}
@@ -1695,7 +1695,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
* already. The implementation is allowed to instantiate the target factory bean if
* {@code allowInit} is {@code true} and the type cannot be determined another way;
* otherwise it is restricted to introspecting signatures and related metadata.
* <p>If no {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE} if set on the bean definition
* <p>If no {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE} is set on the bean definition
* and {@code allowInit} is {@code true}, the default implementation will create
* the FactoryBean via {@code getBean} to call its {@code getObjectType} method.
* Subclasses are encouraged to optimize this, typically by inspecting the generic
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,6 +42,8 @@ import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.beans.testfixture.beans.DerivedTestBean;
import org.springframework.beans.testfixture.beans.ITestBean;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceEditor;
import org.springframework.lang.Nullable;
@@ -52,7 +54,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import static org.assertj.core.api.SoftAssertions.assertSoftly;
/**
* Unit tests for {@link BeanUtils}.
* Tests for {@link BeanUtils}.
*
* @author Juergen Hoeller
* @author Rob Harrop
@@ -136,7 +138,7 @@ class BeanUtilsTests {
PropertyDescriptor[] actual = Introspector.getBeanInfo(TestBean.class).getPropertyDescriptors();
PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(TestBean.class);
assertThat(descriptors).as("Descriptors should not be null").isNotNull();
assertThat(descriptors.length).as("Invalid number of descriptors returned").isEqualTo(actual.length);
assertThat(descriptors).as("Invalid number of descriptors returned").hasSameSizeAs(actual);
}
@Test
@@ -162,13 +164,13 @@ class BeanUtilsTests {
tb.setAge(32);
tb.setTouchy("touchy");
TestBean tb2 = new TestBean();
assertThat(tb2.getName() == null).as("Name empty").isTrue();
assertThat(tb2.getAge() == 0).as("Age empty").isTrue();
assertThat(tb2.getTouchy() == null).as("Touchy empty").isTrue();
assertThat(tb2.getName()).as("Name empty").isNull();
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
BeanUtils.copyProperties(tb, tb2);
assertThat(tb2.getName().equals(tb.getName())).as("Name copied").isTrue();
assertThat(tb2.getAge() == tb.getAge()).as("Age copied").isTrue();
assertThat(tb2.getTouchy().equals(tb.getTouchy())).as("Touchy copied").isTrue();
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName());
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge());
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy());
}
@Test
@@ -178,13 +180,13 @@ class BeanUtilsTests {
tb.setAge(32);
tb.setTouchy("touchy");
TestBean tb2 = new TestBean();
assertThat(tb2.getName() == null).as("Name empty").isTrue();
assertThat(tb2.getAge() == 0).as("Age empty").isTrue();
assertThat(tb2.getTouchy() == null).as("Touchy empty").isTrue();
assertThat(tb2.getName()).as("Name empty").isNull();
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
BeanUtils.copyProperties(tb, tb2);
assertThat(tb2.getName().equals(tb.getName())).as("Name copied").isTrue();
assertThat(tb2.getAge() == tb.getAge()).as("Age copied").isTrue();
assertThat(tb2.getTouchy().equals(tb.getTouchy())).as("Touchy copied").isTrue();
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName());
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge());
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy());
}
@Test
@@ -194,13 +196,13 @@ class BeanUtilsTests {
tb.setAge(32);
tb.setTouchy("touchy");
DerivedTestBean tb2 = new DerivedTestBean();
assertThat(tb2.getName() == null).as("Name empty").isTrue();
assertThat(tb2.getAge() == 0).as("Age empty").isTrue();
assertThat(tb2.getTouchy() == null).as("Touchy empty").isTrue();
assertThat(tb2.getName()).as("Name empty").isNull();
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
BeanUtils.copyProperties(tb, tb2);
assertThat(tb2.getName().equals(tb.getName())).as("Name copied").isTrue();
assertThat(tb2.getAge() == tb.getAge()).as("Age copied").isTrue();
assertThat(tb2.getTouchy().equals(tb.getTouchy())).as("Touchy copied").isTrue();
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName());
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge());
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy());
}
/**
@@ -227,8 +229,8 @@ class BeanUtilsTests {
IntegerListHolder2 integerListHolder2 = new IntegerListHolder2();
BeanUtils.copyProperties(integerListHolder1, integerListHolder2);
assertThat(integerListHolder1.getList()).containsOnly(42);
assertThat(integerListHolder2.getList()).containsOnly(42);
assertThat(integerListHolder1.getList()).containsExactly(42);
assertThat(integerListHolder2.getList()).containsExactly(42);
}
/**
@@ -257,7 +259,7 @@ class BeanUtilsTests {
WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2();
BeanUtils.copyProperties(integerListHolder1, wildcardListHolder2);
assertThat(integerListHolder1.getList()).containsOnly(42);
assertThat(integerListHolder1.getList()).containsExactly(42);
assertThat(wildcardListHolder2.getList()).isEqualTo(Arrays.asList(42));
}
@@ -271,9 +273,8 @@ class BeanUtilsTests {
NumberUpperBoundedWildcardListHolder numberListHolder = new NumberUpperBoundedWildcardListHolder();
BeanUtils.copyProperties(integerListHolder1, numberListHolder);
assertThat(integerListHolder1.getList()).containsOnly(42);
assertThat(numberListHolder.getList()).hasSize(1);
assertThat(numberListHolder.getList().contains(Integer.valueOf(42))).isTrue();
assertThat(integerListHolder1.getList()).containsExactly(42);
assertThat(numberListHolder.getList()).isEqualTo(Arrays.asList(42));
}
/**
@@ -282,7 +283,7 @@ class BeanUtilsTests {
@Test
void copyPropertiesDoesNotCopyFromSuperTypeToSubType() {
NumberHolder numberHolder = new NumberHolder();
numberHolder.setNumber(Integer.valueOf(42));
numberHolder.setNumber(42);
IntegerHolder integerHolder = new IntegerHolder();
BeanUtils.copyProperties(numberHolder, integerHolder);
@@ -300,7 +301,7 @@ class BeanUtilsTests {
LongListHolder longListHolder = new LongListHolder();
BeanUtils.copyProperties(integerListHolder, longListHolder);
assertThat(integerListHolder.getList()).containsOnly(42);
assertThat(integerListHolder.getList()).containsExactly(42);
assertThat(longListHolder.getList()).isEmpty();
}
@@ -314,7 +315,7 @@ class BeanUtilsTests {
NumberListHolder numberListHolder = new NumberListHolder();
BeanUtils.copyProperties(integerListHolder, numberListHolder);
assertThat(integerListHolder.getList()).containsOnly(42);
assertThat(integerListHolder.getList()).containsExactly(42);
assertThat(numberListHolder.getList()).isEmpty();
}
@@ -323,12 +324,13 @@ class BeanUtilsTests {
Order original = new Order("test", Arrays.asList("foo", "bar"));
// Create a Proxy that loses the generic type information for the getLineItems() method.
OrderSummary proxy = proxyOrder(original);
OrderSummary proxy = (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class<?>[] {OrderSummary.class}, new OrderInvocationHandler(original));
assertThat(OrderSummary.class.getDeclaredMethod("getLineItems").toGenericString())
.contains("java.util.List<java.lang.String>");
.contains("java.util.List<java.lang.String>");
assertThat(proxy.getClass().getDeclaredMethod("getLineItems").toGenericString())
.contains("java.util.List")
.doesNotContain("<java.lang.String>");
.contains("java.util.List")
.doesNotContain("<java.lang.String>");
// Ensure that our custom Proxy works as expected.
assertThat(proxy.getId()).isEqualTo("test");
@@ -341,40 +343,57 @@ class BeanUtilsTests {
assertThat(target.getLineItems()).containsExactly("foo", "bar");
}
@Test // gh-32888
public void copyPropertiesWithGenericCglibClass() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(User.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> proxy.invokeSuper(obj, args));
User user = (User) enhancer.create();
user.setId(1);
user.setName("proxy");
user.setAddress("addr");
User target = new User();
BeanUtils.copyProperties(user, target);
assertThat(target.getId()).isEqualTo(user.getId());
assertThat(target.getName()).isEqualTo(user.getName());
assertThat(target.getAddress()).isEqualTo(user.getAddress());
}
@Test
void copyPropertiesWithEditable() throws Exception {
TestBean tb = new TestBean();
assertThat(tb.getName() == null).as("Name empty").isTrue();
assertThat(tb.getName()).as("Name empty").isNull();
tb.setAge(32);
tb.setTouchy("bla");
TestBean tb2 = new TestBean();
tb2.setName("rod");
assertThat(tb2.getAge() == 0).as("Age empty").isTrue();
assertThat(tb2.getTouchy() == null).as("Touchy empty").isTrue();
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
// "touchy" should not be copied: it's not defined in ITestBean
BeanUtils.copyProperties(tb, tb2, ITestBean.class);
assertThat(tb2.getName() == null).as("Name copied").isTrue();
assertThat(tb2.getAge() == 32).as("Age copied").isTrue();
assertThat(tb2.getTouchy() == null).as("Touchy still empty").isTrue();
assertThat(tb2.getName()).as("Name copied").isNull();
assertThat(tb2.getAge()).as("Age copied").isEqualTo(32);
assertThat(tb2.getTouchy()).as("Touchy still empty").isNull();
}
@Test
void copyPropertiesWithIgnore() throws Exception {
TestBean tb = new TestBean();
assertThat(tb.getName() == null).as("Name empty").isTrue();
assertThat(tb.getName()).as("Name empty").isNull();
tb.setAge(32);
tb.setTouchy("bla");
TestBean tb2 = new TestBean();
tb2.setName("rod");
assertThat(tb2.getAge() == 0).as("Age empty").isTrue();
assertThat(tb2.getTouchy() == null).as("Touchy empty").isTrue();
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0);
assertThat(tb2.getTouchy()).as("Touchy empty").isNull();
// "spouse", "touchy", "age" should not be copied
BeanUtils.copyProperties(tb, tb2, "spouse", "touchy", "age");
assertThat(tb2.getName() == null).as("Name copied").isTrue();
assertThat(tb2.getAge() == 0).as("Age still empty").isTrue();
assertThat(tb2.getTouchy() == null).as("Touchy still empty").isTrue();
assertThat(tb2.getName()).as("Name copied").isNull();
assertThat(tb2.getAge()).as("Age still empty").isEqualTo(0);
assertThat(tb2.getTouchy()).as("Touchy still empty").isNull();
}
@Test
@@ -383,7 +402,7 @@ class BeanUtilsTests {
source.setName("name");
TestBean target = new TestBean();
BeanUtils.copyProperties(source, target, "specialProperty");
assertThat("name").isEqualTo(target.getName());
assertThat(target.getName()).isEqualTo("name");
}
@Test
@@ -520,6 +539,7 @@ class BeanUtilsTests {
}
}
@SuppressWarnings("unused")
private static class IntegerHolder {
@@ -534,6 +554,7 @@ class BeanUtilsTests {
}
}
@SuppressWarnings("unused")
private static class WildcardListHolder1 {
@@ -548,6 +569,7 @@ class BeanUtilsTests {
}
}
@SuppressWarnings("unused")
private static class WildcardListHolder2 {
@@ -562,6 +584,7 @@ class BeanUtilsTests {
}
}
@SuppressWarnings("unused")
private static class NumberUpperBoundedWildcardListHolder {
@@ -576,6 +599,7 @@ class BeanUtilsTests {
}
}
@SuppressWarnings("unused")
private static class NumberListHolder {
@@ -590,6 +614,7 @@ class BeanUtilsTests {
}
}
@SuppressWarnings("unused")
private static class IntegerListHolder1 {
@@ -604,6 +629,7 @@ class BeanUtilsTests {
}
}
@SuppressWarnings("unused")
private static class IntegerListHolder2 {
@@ -618,6 +644,7 @@ class BeanUtilsTests {
}
}
@SuppressWarnings("unused")
private static class LongListHolder {
@@ -798,6 +825,7 @@ class BeanUtilsTests {
}
}
private static class BeanWithNullableTypes {
private Integer counter;
@@ -828,6 +856,7 @@ class BeanUtilsTests {
}
}
private static class BeanWithPrimitiveTypes {
private boolean flag;
@@ -840,7 +869,6 @@ class BeanUtilsTests {
private char character;
private String text;
@SuppressWarnings("unused")
public BeanWithPrimitiveTypes(boolean flag, byte byteCount, short shortCount, int intCount, long longCount,
float floatCount, double doubleCount, char character, String text) {
@@ -891,21 +919,22 @@ class BeanUtilsTests {
public String getText() {
return text;
}
}
private static class PrivateBeanWithPrivateConstructor {
private PrivateBeanWithPrivateConstructor() {
}
}
@SuppressWarnings("unused")
private static class Order {
private String id;
private List<String> lineItems;
private List<String> lineItems;
Order() {
}
@@ -937,6 +966,7 @@ class BeanUtilsTests {
}
}
private interface OrderSummary {
String getId();
@@ -945,17 +975,10 @@ class BeanUtilsTests {
}
private OrderSummary proxyOrder(Order order) {
return (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class<?>[] { OrderSummary.class }, new OrderInvocationHandler(order));
}
private static class OrderInvocationHandler implements InvocationHandler {
private final Order order;
OrderInvocationHandler(Order order) {
this.order = order;
}
@@ -973,4 +996,46 @@ class BeanUtilsTests {
}
}
private static class GenericBaseModel<T> {
private T id;
private String name;
public T getId() {
return id;
}
public void setId(T id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
private static class User extends GenericBaseModel<Integer> {
private String address;
public User() {
super();
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -126,6 +126,7 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
beanFactory.setApplicationStartup(getApplicationStartup());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author 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,11 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Default implementation of the {@link LifecycleProcessor} strategy.
* Spring's default implementation of the {@link LifecycleProcessor} strategy.
*
* <p>Provides interaction with {@link Lifecycle} and {@link SmartLifecycle} beans in
* groups for specific phases, on startup/shutdown as well as for explicit start/stop
* interactions on a {@link org.springframework.context.ConfigurableApplicationContext}.
*
* @author Mark Fisher
* @author Juergen Hoeller
@@ -145,13 +149,13 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
lifecycleBeans.forEach((beanName, bean) -> {
if (!autoStartupOnly || (bean instanceof SmartLifecycle && ((SmartLifecycle) bean).isAutoStartup())) {
int phase = getPhase(bean);
phases.computeIfAbsent(
phase,
p -> new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly)
int startupPhase = getPhase(bean);
phases.computeIfAbsent(startupPhase,
phase -> new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly)
).add(beanName, bean);
}
});
if (!phases.isEmpty()) {
phases.values().forEach(LifecycleGroup::start);
}
@@ -191,6 +195,7 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
private void stopBeans() {
Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
Map<Integer, LifecycleGroup> phases = new HashMap<>();
lifecycleBeans.forEach((beanName, bean) -> {
int shutdownPhase = getPhase(bean);
LifecycleGroup group = phases.get(shutdownPhase);
@@ -200,6 +205,7 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
}
group.add(beanName, bean);
});
if (!phases.isEmpty()) {
List<Integer> keys = new ArrayList<>(phases.keySet());
keys.sort(Collections.reverseOrder());
@@ -314,6 +320,8 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
/**
* Helper class for maintaining a group of Lifecycle beans that should be started
* and stopped together based on their 'phase' value (or the default value of 0).
* The group is expected to be created in an ad-hoc fashion and group members are
* expected to always have the same 'phase' value.
*/
private class LifecycleGroup {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author 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,8 +22,10 @@ import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.EnumMap;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import org.springframework.format.Formatter;
@@ -35,9 +37,14 @@ import org.springframework.util.StringUtils;
/**
* A formatter for {@link java.util.Date} types.
*
* <p>Supports the configuration of an explicit date time pattern, timezone,
* locale, and fallback date time patterns for lenient parsing.
*
* <p>Common ISO patterns for UTC instants are applied at millisecond precision.
* Note that {@link org.springframework.format.datetime.standard.InstantFormatter}
* is recommended for flexible UTC parsing into a {@link java.time.Instant} instead.
*
* @author Keith Donald
* @author Juergen Hoeller
* @author Phillip Webb
@@ -51,12 +58,21 @@ public class DateFormatter implements Formatter<Date> {
private static final Map<ISO, String> ISO_PATTERNS;
private static final Map<ISO, String> ISO_FALLBACK_PATTERNS;
static {
// We use an EnumMap instead of Map.of(...) since the former provides better performance.
Map<ISO, String> formats = new EnumMap<>(ISO.class);
formats.put(ISO.DATE, "yyyy-MM-dd");
formats.put(ISO.TIME, "HH:mm:ss.SSSXXX");
formats.put(ISO.DATE_TIME, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
ISO_PATTERNS = Collections.unmodifiableMap(formats);
// Fallback format for the time part without milliseconds.
Map<ISO, String> fallbackFormats = new EnumMap<>(ISO.class);
fallbackFormats.put(ISO.TIME, "HH:mm:ssXXX");
fallbackFormats.put(ISO.DATE_TIME, "yyyy-MM-dd'T'HH:mm:ssXXX");
ISO_FALLBACK_PATTERNS = Collections.unmodifiableMap(fallbackFormats);
}
@@ -201,8 +217,16 @@ public class DateFormatter implements Formatter<Date> {
return getDateFormat(locale).parse(text);
}
catch (ParseException ex) {
Set<String> fallbackPatterns = new LinkedHashSet<>();
String isoPattern = ISO_FALLBACK_PATTERNS.get(this.iso);
if (isoPattern != null) {
fallbackPatterns.add(isoPattern);
}
if (!ObjectUtils.isEmpty(this.fallbackPatterns)) {
for (String pattern : this.fallbackPatterns) {
Collections.addAll(fallbackPatterns, this.fallbackPatterns);
}
if (!fallbackPatterns.isEmpty()) {
for (String pattern : fallbackPatterns) {
try {
DateFormat dateFormat = configureDateFormat(new SimpleDateFormat(pattern, locale));
// Align timezone for parsing format with printing format if ISO is set.
@@ -220,8 +244,8 @@ public class DateFormatter implements Formatter<Date> {
}
if (this.source != null) {
ParseException parseException = new ParseException(
String.format("Unable to parse date time value \"%s\" using configuration from %s", text, this.source),
ex.getErrorOffset());
String.format("Unable to parse date time value \"%s\" using configuration from %s", text, this.source),
ex.getErrorOffset());
parseException.initCause(ex);
throw parseException;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,12 +41,12 @@ public class InstantFormatter implements Formatter<Instant> {
@Override
public Instant parse(String text, Locale locale) throws ParseException {
if (text.length() > 0 && Character.isAlphabetic(text.charAt(0))) {
if (!text.isEmpty() && Character.isAlphabetic(text.charAt(0))) {
// assuming RFC-1123 value a la "Tue, 3 Jun 2008 11:05:30 GMT"
return Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(text));
}
else {
// assuming UTC instant a la "2007-12-03T10:15:30.00Z"
// assuming UTC instant a la "2007-12-03T10:15:30.000Z"
return Instant.parse(text);
}
}
@@ -28,17 +28,14 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*
* @author Adrian Colyer
* @author Chris Beams
* @author Juergen Hoeller
*/
class OverloadedAdviceTests {
@Test
@SuppressWarnings("resource")
void testExceptionOnConfigParsingWithMismatchedAdviceMethod() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> new ClassPathXmlApplicationContext(getClass().getSimpleName() + ".xml", getClass()))
.havingRootCause()
.isInstanceOf(IllegalArgumentException.class)
.as("invalidAbsoluteTypeName should be detected by AJ").withMessageContaining("invalidAbsoluteTypeName");
void testConfigParsingWithMismatchedAdviceMethod() {
new ClassPathXmlApplicationContext(getClass().getSimpleName() + ".xml", getClass());
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2024 the original author 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,9 @@ import org.springframework.beans.factory.support.AbstractBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.core.type.AnnotationMetadata;
import static org.assertj.core.api.Assertions.assertThat;
@@ -39,51 +41,62 @@ import static org.assertj.core.api.Assertions.assertThat;
* {@link FactoryBean FactoryBeans} defined in the configuration.
*
* @author Phillip Webb
* @author Juergen Hoeller
*/
public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
class ConfigurationWithFactoryBeanEarlyDeductionTests {
@Test
public void preFreezeDirect() {
void preFreezeDirect() {
assertPreFreeze(DirectConfiguration.class);
}
@Test
public void postFreezeDirect() {
void postFreezeDirect() {
assertPostFreeze(DirectConfiguration.class);
}
@Test
public void preFreezeGenericMethod() {
void preFreezeGenericMethod() {
assertPreFreeze(GenericMethodConfiguration.class);
}
@Test
public void postFreezeGenericMethod() {
void postFreezeGenericMethod() {
assertPostFreeze(GenericMethodConfiguration.class);
}
@Test
public void preFreezeGenericClass() {
void preFreezeGenericClass() {
assertPreFreeze(GenericClassConfiguration.class);
}
@Test
public void postFreezeGenericClass() {
void postFreezeGenericClass() {
assertPostFreeze(GenericClassConfiguration.class);
}
@Test
public void preFreezeAttribute() {
void preFreezeAttribute() {
assertPreFreeze(AttributeClassConfiguration.class);
}
@Test
public void postFreezeAttribute() {
void postFreezeAttribute() {
assertPostFreeze(AttributeClassConfiguration.class);
}
@Test
public void preFreezeUnresolvedGenericFactoryBean() {
void preFreezeTargetType() {
assertPreFreeze(TargetTypeConfiguration.class);
}
@Test
void postFreezeTargetType() {
assertPostFreeze(TargetTypeConfiguration.class);
}
@Test
void preFreezeUnresolvedGenericFactoryBean() {
// Covers the case where a @Configuration is picked up via component scanning
// and its bean definition only has a String bean class. In such cases
// beanDefinition.hasBeanClass() returns false so we need to actually
@@ -108,14 +121,13 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
}
}
private void assertPostFreeze(Class<?> configurationClass) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
configurationClass);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configurationClass);
assertContainsMyBeanName(context);
}
private void assertPreFreeze(Class<?> configurationClass,
BeanFactoryPostProcessor... postProcessors) {
private void assertPreFreeze(Class<?> configurationClass, BeanFactoryPostProcessor... postProcessors) {
NameCollectingBeanFactoryPostProcessor postProcessor = new NameCollectingBeanFactoryPostProcessor();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
try {
@@ -138,41 +150,38 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
assertThat(names).containsExactly("myBean");
}
private static class NameCollectingBeanFactoryPostProcessor
implements BeanFactoryPostProcessor {
private static class NameCollectingBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
private String[] names;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
this.names = beanFactory.getBeanNamesForType(MyBean.class, true, false);
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
ResolvableType typeToMatch = ResolvableType.forClassWithGenerics(MyBean.class, String.class);
this.names = beanFactory.getBeanNamesForType(typeToMatch, true, false);
}
public String[] getNames() {
return this.names;
}
}
@Configuration
static class DirectConfiguration {
@Bean
MyBean myBean() {
return new MyBean();
MyBean<String> myBean() {
return new MyBean<>();
}
}
@Configuration
static class GenericMethodConfiguration {
@Bean
FactoryBean<MyBean> myBean() {
return new TestFactoryBean<>(new MyBean());
FactoryBean<MyBean<String>> myBean() {
return new TestFactoryBean<>(new MyBean<>());
}
}
@Configuration
@@ -182,13 +191,11 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
MyFactoryBean myBean() {
return new MyFactoryBean();
}
}
@Configuration
@Import(AttributeClassRegistrar.class)
static class AttributeClassConfiguration {
}
static class AttributeClassRegistrar implements ImportBeanDefinitionRegistrar {
@@ -197,16 +204,34 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(
RawWithAbstractObjectTypeFactoryBean.class).getBeanDefinition();
definition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, MyBean.class);
definition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE,
ResolvableType.forClassWithGenerics(MyBean.class, String.class));
registry.registerBeanDefinition("myBean", definition);
}
}
@Configuration
@Import(TargetTypeRegistrar.class)
static class TargetTypeConfiguration {
}
static class TargetTypeRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition definition = new RootBeanDefinition(RawWithAbstractObjectTypeFactoryBean.class);
definition.setTargetType(ResolvableType.forClassWithGenerics(FactoryBean.class,
ResolvableType.forClassWithGenerics(MyBean.class, String.class)));
definition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE,
ResolvableType.forClassWithGenerics(MyBean.class, String.class));
registry.registerBeanDefinition("myBean", definition);
}
}
abstract static class AbstractMyBean {
}
static class MyBean extends AbstractMyBean {
static class MyBean<T> extends AbstractMyBean {
}
static class TestFactoryBean<T> implements FactoryBean<T> {
@@ -218,7 +243,7 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
}
@Override
public T getObject() throws Exception {
public T getObject() {
return this.instance;
}
@@ -226,31 +251,26 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
public Class<?> getObjectType() {
return this.instance.getClass();
}
}
static class MyFactoryBean extends TestFactoryBean<MyBean> {
static class MyFactoryBean extends TestFactoryBean<MyBean<String>> {
public MyFactoryBean() {
super(new MyBean());
super(new MyBean<>());
}
}
static class RawWithAbstractObjectTypeFactoryBean implements FactoryBean<Object> {
private final Object object = new MyBean();
@Override
public Object getObject() throws Exception {
return object;
throw new IllegalStateException();
}
@Override
public Class<?> getObjectType() {
return MyBean.class;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2024 the original author 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,9 +23,6 @@ import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.junit.jupiter.api.Test;
import org.springframework.format.annotation.DateTimeFormat.ISO;
@@ -33,83 +30,88 @@ import org.springframework.format.annotation.DateTimeFormat.ISO;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link DateFormatter}.
*
* @author Keith Donald
* @author Phillip Webb
* @author Juergen Hoeller
*/
public class DateFormatterTests {
class DateFormatterTests {
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
@Test
public void shouldPrintAndParseDefault() throws Exception {
void shouldPrintAndParseDefault() throws Exception {
DateFormatter formatter = new DateFormatter();
formatter.setTimeZone(UTC);
Date date = getDate(2009, Calendar.JUNE, 1);
assertThat(formatter.print(date, Locale.US)).isEqualTo("Jun 1, 2009");
assertThat(formatter.parse("Jun 1, 2009", Locale.US)).isEqualTo(date);
}
@Test
public void shouldPrintAndParseFromPattern() throws ParseException {
void shouldPrintAndParseFromPattern() throws ParseException {
DateFormatter formatter = new DateFormatter("yyyy-MM-dd");
formatter.setTimeZone(UTC);
Date date = getDate(2009, Calendar.JUNE, 1);
assertThat(formatter.print(date, Locale.US)).isEqualTo("2009-06-01");
assertThat(formatter.parse("2009-06-01", Locale.US)).isEqualTo(date);
}
@Test
public void shouldPrintAndParseShort() throws Exception {
void shouldPrintAndParseShort() throws Exception {
DateFormatter formatter = new DateFormatter();
formatter.setTimeZone(UTC);
formatter.setStyle(DateFormat.SHORT);
Date date = getDate(2009, Calendar.JUNE, 1);
assertThat(formatter.print(date, Locale.US)).isEqualTo("6/1/09");
assertThat(formatter.parse("6/1/09", Locale.US)).isEqualTo(date);
}
@Test
public void shouldPrintAndParseMedium() throws Exception {
void shouldPrintAndParseMedium() throws Exception {
DateFormatter formatter = new DateFormatter();
formatter.setTimeZone(UTC);
formatter.setStyle(DateFormat.MEDIUM);
Date date = getDate(2009, Calendar.JUNE, 1);
assertThat(formatter.print(date, Locale.US)).isEqualTo("Jun 1, 2009");
assertThat(formatter.parse("Jun 1, 2009", Locale.US)).isEqualTo(date);
}
@Test
public void shouldPrintAndParseLong() throws Exception {
void shouldPrintAndParseLong() throws Exception {
DateFormatter formatter = new DateFormatter();
formatter.setTimeZone(UTC);
formatter.setStyle(DateFormat.LONG);
Date date = getDate(2009, Calendar.JUNE, 1);
assertThat(formatter.print(date, Locale.US)).isEqualTo("June 1, 2009");
assertThat(formatter.parse("June 1, 2009", Locale.US)).isEqualTo(date);
}
@Test
public void shouldPrintAndParseFull() throws Exception {
void shouldPrintAndParseFull() throws Exception {
DateFormatter formatter = new DateFormatter();
formatter.setTimeZone(UTC);
formatter.setStyle(DateFormat.FULL);
Date date = getDate(2009, Calendar.JUNE, 1);
assertThat(formatter.print(date, Locale.US)).isEqualTo("Monday, June 1, 2009");
assertThat(formatter.parse("Monday, June 1, 2009", Locale.US)).isEqualTo(date);
}
@Test
public void shouldPrintAndParseISODate() throws Exception {
void shouldPrintAndParseIsoDate() throws Exception {
DateFormatter formatter = new DateFormatter();
formatter.setTimeZone(UTC);
formatter.setIso(ISO.DATE);
Date date = getDate(2009, Calendar.JUNE, 1, 14, 23, 5, 3);
assertThat(formatter.print(date, Locale.US)).isEqualTo("2009-06-01");
assertThat(formatter.parse("2009-6-01", Locale.US))
@@ -117,79 +119,56 @@ public class DateFormatterTests {
}
@Test
public void shouldPrintAndParseISOTime() throws Exception {
void shouldPrintAndParseIsoTime() throws Exception {
DateFormatter formatter = new DateFormatter();
formatter.setTimeZone(UTC);
formatter.setIso(ISO.TIME);
Date date = getDate(2009, Calendar.JANUARY, 1, 14, 23, 5, 3);
assertThat(formatter.print(date, Locale.US)).isEqualTo("14:23:05.003Z");
assertThat(formatter.parse("14:23:05.003Z", Locale.US))
.isEqualTo(getDate(1970, Calendar.JANUARY, 1, 14, 23, 5, 3));
date = getDate(2009, Calendar.JANUARY, 1, 14, 23, 5, 0);
assertThat(formatter.print(date, Locale.US)).isEqualTo("14:23:05.000Z");
assertThat(formatter.parse("14:23:05Z", Locale.US))
.isEqualTo(getDate(1970, Calendar.JANUARY, 1, 14, 23, 5, 0).toInstant());
}
@Test
public void shouldPrintAndParseISODateTime() throws Exception {
void shouldPrintAndParseIsoDateTime() throws Exception {
DateFormatter formatter = new DateFormatter();
formatter.setTimeZone(UTC);
formatter.setIso(ISO.DATE_TIME);
Date date = getDate(2009, Calendar.JUNE, 1, 14, 23, 5, 3);
assertThat(formatter.print(date, Locale.US)).isEqualTo("2009-06-01T14:23:05.003Z");
assertThat(formatter.parse("2009-06-01T14:23:05.003Z", Locale.US)).isEqualTo(date);
date = getDate(2009, Calendar.JUNE, 1, 14, 23, 5, 0);
assertThat(formatter.print(date, Locale.US)).isEqualTo("2009-06-01T14:23:05.000Z");
assertThat(formatter.parse("2009-06-01T14:23:05Z", Locale.US)).isEqualTo(date.toInstant());
}
@Test
public void shouldSupportJodaStylePatterns() throws Exception {
String[] chars = { "S", "M", "-" };
for (String d : chars) {
for (String t : chars) {
String style = d + t;
if (!style.equals("--")) {
Date date = getDate(2009, Calendar.JUNE, 10, 14, 23, 0, 0);
if (t.equals("-")) {
date = getDate(2009, Calendar.JUNE, 10);
}
else if (d.equals("-")) {
date = getDate(1970, Calendar.JANUARY, 1, 14, 23, 0, 0);
}
testJodaStylePatterns(style, Locale.US, date);
}
}
}
}
private void testJodaStylePatterns(String style, Locale locale, Date date) throws Exception {
DateFormatter formatter = new DateFormatter();
formatter.setTimeZone(UTC);
formatter.setStylePattern(style);
DateTimeFormatter jodaFormatter = DateTimeFormat.forStyle(style).withLocale(locale).withZone(DateTimeZone.UTC);
String jodaPrinted = jodaFormatter.print(date.getTime());
assertThat(formatter.print(date, locale))
.as("Unable to print style pattern " + style)
.isEqualTo(jodaPrinted);
assertThat(formatter.parse(jodaPrinted, locale))
.as("Unable to parse style pattern " + style)
.isEqualTo(date);
}
@Test
public void shouldThrowOnUnsupportedStylePattern() throws Exception {
void shouldThrowOnUnsupportedStylePattern() {
DateFormatter formatter = new DateFormatter();
formatter.setStylePattern("OO");
assertThatIllegalStateException().isThrownBy(() ->
formatter.parse("2009", Locale.US))
.withMessageContaining("Unsupported style pattern 'OO'");
assertThatIllegalStateException().isThrownBy(() -> formatter.parse("2009", Locale.US))
.withMessageContaining("Unsupported style pattern 'OO'");
}
@Test
public void shouldUseCorrectOrder() throws Exception {
void shouldUseCorrectOrder() {
DateFormatter formatter = new DateFormatter();
formatter.setTimeZone(UTC);
formatter.setStyle(DateFormat.SHORT);
formatter.setStylePattern("L-");
formatter.setIso(ISO.DATE_TIME);
formatter.setPattern("yyyy");
Date date = getDate(2009, Calendar.JUNE, 1, 14, 23, 5, 3);
Date date = getDate(2009, Calendar.JUNE, 1, 14, 23, 5, 3);
assertThat(formatter.print(date, Locale.US)).as("uses pattern").isEqualTo("2009");
formatter.setPattern("");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,7 +52,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Juergen Hoeller
* @author Sam Brannen
*/
public class DateFormattingTests {
class DateFormattingTests {
private final FormattingConversionService conversionService = new FormattingConversionService();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -105,7 +105,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localDate", "10/31/09");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("localDate")).isEqualTo("10/31/09");
}
@@ -117,7 +117,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localDate", "October 31, 2009");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("localDate")).isEqualTo("October 31, 2009");
}
@@ -129,7 +129,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localDate", "20091031");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("localDate")).isEqualTo("20091031");
}
@@ -138,7 +138,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localDate", new String[] {"10/31/09"});
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
}
@Test
@@ -146,7 +146,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("styleLocalDate", "Oct 31, 2009");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("styleLocalDate")).isEqualTo("Oct 31, 2009");
}
@@ -164,7 +164,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("children[0].styleLocalDate", "Oct 31, 2009");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("children[0].styleLocalDate")).isEqualTo("Oct 31, 2009");
}
@@ -174,7 +174,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("styleLocalDate", "Oct 31, 2009");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("styleLocalDate")).isEqualTo("Oct 31, 2009");
}
@@ -193,7 +193,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localDate", new GregorianCalendar(2009, 9, 31, 0, 0));
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("localDate")).isEqualTo("10/31/09");
}
@@ -202,7 +202,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localTime", "12:00 PM");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("localTime")).isEqualTo("12:00 PM");
}
@@ -214,7 +214,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localTime", "12:00:00 PM");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("localTime")).isEqualTo("12:00:00 PM");
}
@@ -226,7 +226,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localTime", "130000");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("localTime")).isEqualTo("130000");
}
@@ -235,7 +235,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("styleLocalTime", "12:00:00 PM");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("styleLocalTime")).isEqualTo("12:00:00 PM");
}
@@ -244,7 +244,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localTime", new GregorianCalendar(1970, 0, 0, 12, 0));
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("localTime")).isEqualTo("12:00 PM");
}
@@ -253,10 +253,9 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localDateTime", LocalDateTime.of(2009, 10, 31, 12, 0));
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
String value = binder.getBindingResult().getFieldValue("localDateTime").toString();
assertThat(value.startsWith("10/31/09")).isTrue();
assertThat(value.endsWith("12:00 PM")).isTrue();
assertThat(value).startsWith("10/31/09").endsWith("12:00 PM");
}
@Test
@@ -264,10 +263,9 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("styleLocalDateTime", LocalDateTime.of(2009, 10, 31, 12, 0));
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
String value = binder.getBindingResult().getFieldValue("styleLocalDateTime").toString();
assertThat(value.startsWith("Oct 31, 2009")).isTrue();
assertThat(value.endsWith("12:00:00 PM")).isTrue();
assertThat(value).startsWith("Oct 31, 2009").endsWith("12:00:00 PM");
}
@Test
@@ -275,10 +273,9 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localDateTime", new GregorianCalendar(2009, 9, 31, 12, 0));
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
String value = binder.getBindingResult().getFieldValue("localDateTime").toString();
assertThat(value.startsWith("10/31/09")).isTrue();
assertThat(value.endsWith("12:00 PM")).isTrue();
assertThat(value).startsWith("10/31/09").endsWith("12:00 PM");
}
@Test
@@ -289,10 +286,9 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("localDateTime", LocalDateTime.of(2009, 10, 31, 12, 0));
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
String value = binder.getBindingResult().getFieldValue("localDateTime").toString();
assertThat(value.startsWith("Oct 31, 2009")).isTrue();
assertThat(value.endsWith("12:00:00 PM")).isTrue();
assertThat(value).startsWith("Oct 31, 2009").endsWith("12:00:00 PM");
}
@Test
@@ -300,7 +296,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("patternLocalDateTime", "10/31/09 12:00 PM");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("patternLocalDateTime")).isEqualTo("10/31/09 12:00 PM");
}
@@ -317,7 +313,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("isoLocalDate", "2009-10-31");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("isoLocalDate")).isEqualTo("2009-10-31");
}
@@ -356,7 +352,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("isoLocalTime", "12:00:00");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("isoLocalTime")).isEqualTo("12:00:00");
}
@@ -365,7 +361,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("isoLocalTime", "12:00:00.000-05:00");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("isoLocalTime")).isEqualTo("12:00:00");
}
@@ -374,7 +370,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("isoLocalDateTime", "2009-10-31T12:00:00");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("isoLocalDateTime")).isEqualTo("2009-10-31T12:00:00");
}
@@ -383,7 +379,7 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("isoLocalDateTime", "2009-10-31T12:00:00.000Z");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("isoLocalDateTime")).isEqualTo("2009-10-31T12:00:00");
}
@@ -392,8 +388,8 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("instant", "2009-10-31T12:00:00.000Z");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getFieldValue("instant").toString().startsWith("2009-10-31T12:00")).isTrue();
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("instant").toString()).startsWith("2009-10-31T12:00");
}
@Test
@@ -405,8 +401,8 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("instant", new Date(109, 9, 31, 12, 0));
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getFieldValue("instant").toString().startsWith("2009-10-31")).isTrue();
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("instant").toString()).startsWith("2009-10-31");
}
finally {
TimeZone.setDefault(defaultZone);
@@ -418,8 +414,8 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("period", "P6Y3M1D");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getFieldValue("period").toString().equals("P6Y3M1D")).isTrue();
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("period").toString()).isEqualTo("P6Y3M1D");
}
@Test
@@ -427,8 +423,8 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("duration", "PT8H6M12.345S");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getFieldValue("duration").toString().equals("PT8H6M12.345S")).isTrue();
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("duration").toString()).isEqualTo("PT8H6M12.345S");
}
@Test
@@ -436,8 +432,8 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("year", "2007");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getFieldValue("year").toString().equals("2007")).isTrue();
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("year").toString()).isEqualTo("2007");
}
@Test
@@ -445,8 +441,8 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("month", "JULY");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getFieldValue("month").toString().equals("JULY")).isTrue();
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("month").toString()).isEqualTo("JULY");
}
@Test
@@ -454,8 +450,8 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("month", "July");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getFieldValue("month").toString().equals("JULY")).isTrue();
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("month").toString()).isEqualTo("JULY");
}
@Test
@@ -463,8 +459,8 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("yearMonth", "2007-12");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getFieldValue("yearMonth").toString().equals("2007-12")).isTrue();
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("yearMonth").toString()).isEqualTo("2007-12");
}
@Test
@@ -472,10 +468,11 @@ class DateTimeFormattingTests {
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add("monthDay", "--12-03");
binder.bind(propertyValues);
assertThat(binder.getBindingResult().getErrorCount()).isEqualTo(0);
assertThat(binder.getBindingResult().getFieldValue("monthDay").toString().equals("--12-03")).isTrue();
assertThat(binder.getBindingResult().getErrorCount()).isZero();
assertThat(binder.getBindingResult().getFieldValue("monthDay").toString()).isEqualTo("--12-03");
}
@Nested
class FallbackPatternTests {
@@ -487,7 +484,7 @@ class DateTimeFormattingTests {
propertyValues.add(propertyName, propertyValue);
binder.bind(propertyValues);
BindingResult bindingResult = binder.getBindingResult();
assertThat(bindingResult.getErrorCount()).isEqualTo(0);
assertThat(bindingResult.getErrorCount()).isZero();
assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("3/2/21");
}
@@ -499,11 +496,12 @@ class DateTimeFormattingTests {
propertyValues.add(propertyName, propertyValue);
binder.bind(propertyValues);
BindingResult bindingResult = binder.getBindingResult();
assertThat(bindingResult.getErrorCount()).isEqualTo(0);
assertThat(bindingResult.getErrorCount()).isZero();
assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("2021-03-02");
}
@ParameterizedTest(name = "input date: {0}")
// @ValueSource(strings = {"12:00:00\u202FPM", "12:00:00", "12:00"})
@ValueSource(strings = {"12:00:00 PM", "12:00:00", "12:00"})
void styleLocalTime(String propertyValue) {
String propertyName = "styleLocalTimeWithFallbackPatterns";
@@ -511,7 +509,8 @@ class DateTimeFormattingTests {
propertyValues.add(propertyName, propertyValue);
binder.bind(propertyValues);
BindingResult bindingResult = binder.getBindingResult();
assertThat(bindingResult.getErrorCount()).isEqualTo(0);
assertThat(bindingResult.getErrorCount()).isZero();
// assertThat(bindingResult.getFieldValue(propertyName)).asString().matches("12:00:00\\SPM");
assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("12:00:00 PM");
}
@@ -523,7 +522,7 @@ class DateTimeFormattingTests {
propertyValues.add(propertyName, propertyValue);
binder.bind(propertyValues);
BindingResult bindingResult = binder.getBindingResult();
assertThat(bindingResult.getErrorCount()).isEqualTo(0);
assertThat(bindingResult.getErrorCount()).isZero();
assertThat(bindingResult.getFieldValue(propertyName)).isEqualTo("2021-03-02T12:00:00");
}
@@ -565,10 +564,10 @@ class DateTimeFormattingTests {
@DateTimeFormat(style = "M-")
private LocalDate styleLocalDate;
@DateTimeFormat(style = "S-", fallbackPatterns = { "yyyy-MM-dd", "yyyyMMdd", "yyyy.MM.dd" })
@DateTimeFormat(style = "S-", fallbackPatterns = {"yyyy-MM-dd", "yyyyMMdd", "yyyy.MM.dd"})
private LocalDate styleLocalDateWithFallbackPatterns;
@DateTimeFormat(pattern = "yyyy-MM-dd", fallbackPatterns = { "M/d/yy", "yyyyMMdd", "yyyy.MM.dd" })
@DateTimeFormat(pattern = "yyyy-MM-dd", fallbackPatterns = {"M/d/yy", "yyyyMMdd", "yyyy.MM.dd"})
private LocalDate patternLocalDateWithFallbackPatterns;
private LocalTime localTime;
@@ -576,7 +575,7 @@ class DateTimeFormattingTests {
@DateTimeFormat(style = "-M")
private LocalTime styleLocalTime;
@DateTimeFormat(style = "-M", fallbackPatterns = { "HH:mm:ss", "HH:mm"})
@DateTimeFormat(style = "-M", fallbackPatterns = {"HH:mm:ss", "HH:mm"})
private LocalTime styleLocalTimeWithFallbackPatterns;
private LocalDateTime localDateTime;
@@ -596,7 +595,7 @@ class DateTimeFormattingTests {
@DateTimeFormat(iso = ISO.DATE_TIME)
private LocalDateTime isoLocalDateTime;
@DateTimeFormat(iso = ISO.DATE_TIME, fallbackPatterns = { "yyyy-MM-dd HH:mm:ss", "M/d/yy HH:mm"})
@DateTimeFormat(iso = ISO.DATE_TIME, fallbackPatterns = {"yyyy-MM-dd HH:mm:ss", "M/d/yy HH:mm"})
private LocalDateTime isoLocalDateTimeWithFallbackPatterns;
private Instant instant;
@@ -615,7 +614,6 @@ class DateTimeFormattingTests {
private final List<DateTimeBean> children = new ArrayList<>();
public LocalDate getLocalDate() {
return this.localDate;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package org.springframework.format.datetime.standard;
import java.text.ParseException;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Random;
import java.util.stream.Stream;
@@ -49,13 +50,12 @@ class InstantFormatterTests {
private final InstantFormatter instantFormatter = new InstantFormatter();
@ParameterizedTest
@ArgumentsSource(ISOSerializedInstantProvider.class)
void should_parse_an_ISO_formatted_string_representation_of_an_Instant(String input) throws ParseException {
Instant expected = DateTimeFormatter.ISO_INSTANT.parse(input, Instant::from);
Instant actual = instantFormatter.parse(input, null);
Instant actual = instantFormatter.parse(input, Locale.US);
assertThat(actual).isEqualTo(expected);
}
@@ -63,9 +63,7 @@ class InstantFormatterTests {
@ArgumentsSource(RFC1123SerializedInstantProvider.class)
void should_parse_an_RFC1123_formatted_string_representation_of_an_Instant(String input) throws ParseException {
Instant expected = DateTimeFormatter.RFC_1123_DATE_TIME.parse(input, Instant::from);
Instant actual = instantFormatter.parse(input, null);
Instant actual = instantFormatter.parse(input, Locale.US);
assertThat(actual).isEqualTo(expected);
}
@@ -73,12 +71,11 @@ class InstantFormatterTests {
@ArgumentsSource(RandomInstantProvider.class)
void should_serialize_an_Instant_using_ISO_format_and_ignoring_Locale(Instant input) {
String expected = DateTimeFormatter.ISO_INSTANT.format(input);
String actual = instantFormatter.print(input, null);
String actual = instantFormatter.print(input, Locale.US);
assertThat(actual).isEqualTo(expected);
}
private static class RandomInstantProvider implements ArgumentsProvider {
private static final long DATA_SET_SIZE = 10;
@@ -100,6 +97,7 @@ class InstantFormatterTests {
}
}
private static class ISOSerializedInstantProvider extends RandomInstantProvider {
@Override
@@ -108,6 +106,7 @@ class InstantFormatterTests {
}
}
private static class RFC1123SerializedInstantProvider extends RandomInstantProvider {
// RFC-1123 supports only 4-digit years
@@ -18,4 +18,6 @@
<bean id="testBean" class="org.springframework.beans.testfixture.beans.TestBean"/>
<bean id="testBean2" class="org.springframework.beans.testfixture.beans.TestBean"/>
</beans>
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author 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,11 +57,11 @@ public final class BridgeMethodResolver {
/**
* Find the original method for the supplied {@link Method bridge Method}.
* Find the local original method for the supplied {@link Method bridge Method}.
* <p>It is safe to call this method passing in a non-bridge {@link Method} instance.
* In such a case, the supplied {@link Method} instance is returned directly to the caller.
* Callers are <strong>not</strong> required to check for bridging before calling this method.
* @param bridgeMethod the method to introspect
* @param bridgeMethod the method to introspect against its declaring class
* @return the original method (either the bridged method or the passed-in method
* if no more specific one could be found)
*/
@@ -73,8 +73,7 @@ public final class BridgeMethodResolver {
if (bridgedMethod == null) {
// Gather all methods with matching name and parameter size.
List<Method> candidateMethods = new ArrayList<>();
MethodFilter filter = candidateMethod ->
isBridgedCandidateFor(candidateMethod, bridgeMethod);
MethodFilter filter = (candidateMethod -> isBridgedCandidateFor(candidateMethod, bridgeMethod));
ReflectionUtils.doWithMethods(bridgeMethod.getDeclaringClass(), candidateMethods::add, filter);
if (!candidateMethods.isEmpty()) {
bridgedMethod = candidateMethods.size() == 1 ?
@@ -95,10 +94,10 @@ public final class BridgeMethodResolver {
* Returns {@code true} if the supplied '{@code candidateMethod}' can be
* considered a valid candidate for the {@link Method} that is {@link Method#isBridge() bridged}
* by the supplied {@link Method bridge Method}. This method performs inexpensive
* checks and can be used quickly to filter for a set of possible matches.
* checks and can be used to quickly filter for a set of possible matches.
*/
private static boolean isBridgedCandidateFor(Method candidateMethod, Method bridgeMethod) {
return (!candidateMethod.isBridge() && !candidateMethod.equals(bridgeMethod) &&
return (!candidateMethod.isBridge() &&
candidateMethod.getName().equals(bridgeMethod.getName()) &&
candidateMethod.getParameterCount() == bridgeMethod.getParameterCount());
}
@@ -121,8 +120,8 @@ public final class BridgeMethodResolver {
return candidateMethod;
}
else if (previousMethod != null) {
sameSig = sameSig &&
Arrays.equals(candidateMethod.getGenericParameterTypes(), previousMethod.getGenericParameterTypes());
sameSig = sameSig && Arrays.equals(
candidateMethod.getGenericParameterTypes(), previousMethod.getGenericParameterTypes());
}
previousMethod = candidateMethod;
}
@@ -163,7 +162,8 @@ public final class BridgeMethodResolver {
}
}
// A non-array type: compare the type itself.
if (!ClassUtils.resolvePrimitiveIfNecessary(candidateParameter).equals(ClassUtils.resolvePrimitiveIfNecessary(genericParameter.toClass()))) {
if (!ClassUtils.resolvePrimitiveIfNecessary(candidateParameter).equals(
ClassUtils.resolvePrimitiveIfNecessary(genericParameter.toClass()))) {
return false;
}
}
@@ -226,8 +226,8 @@ public final class BridgeMethodResolver {
/**
* Compare the signatures of the bridge method and the method which it bridges. If
* the parameter and return types are the same, it is a 'visibility' bridge method
* introduced in Java 6 to fix https://bugs.openjdk.org/browse/JDK-6342411.
* See also https://stas-blogspot.blogspot.com/2010/03/java-bridge-methods-explained.html
* introduced in Java 6 to fix <a href="https://bugs.openjdk.org/browse/JDK-6342411">
* JDK-6342411</a>.
* @return whether signatures match as described
*/
public static boolean isVisibilityBridgeMethodPair(Method bridgeMethod, Method bridgedMethod) {
@@ -1052,16 +1052,16 @@ public abstract class AnnotationUtils {
return null;
}
try {
Method method = annotation.annotationType().getDeclaredMethod(attributeName);
return invokeAnnotationMethod(method, annotation);
}
catch (NoSuchMethodException ex) {
return null;
for (Method method : annotation.annotationType().getDeclaredMethods()) {
if (method.getName().equals(attributeName) && method.getParameterCount() == 0) {
return invokeAnnotationMethod(method, annotation);
}
}
}
catch (Throwable ex) {
handleValueRetrievalFailure(annotation, ex);
return null;
}
return null;
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -410,7 +410,10 @@ final class TypeMappedAnnotations implements MergedAnnotations {
Annotation[] repeatedAnnotations = repeatableContainers.findRepeatedAnnotations(annotation);
if (repeatedAnnotations != null) {
return doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations);
MergedAnnotation<A> result = doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations);
if (result != null) {
return result;
}
}
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(
annotation.annotationType(), repeatableContainers, annotationFilter);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2024 the original author 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,7 @@ public class PropertiesPropertySource extends MapPropertySource {
@Override
public String[] getPropertyNames() {
synchronized (this.source) {
return super.getPropertyNames();
return ((Map<?, ?>) this.source).keySet().stream().filter(k -> k instanceof String).toArray(String[]::new);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -58,7 +58,7 @@ public class MultiValueMapAdapter<K, V> implements MultiValueMap<K, V>, Serializ
@Nullable
public V getFirst(K key) {
List<V> values = this.targetMap.get(key);
return (values != null && !values.isEmpty() ? values.get(0) : null);
return (!CollectionUtils.isEmpty(values) ? values.get(0) : null);
}
@Override
@@ -69,7 +69,7 @@ public class MultiValueMapAdapter<K, V> implements MultiValueMap<K, V>, Serializ
@Override
public void addAll(K key, List<? extends V> values) {
List<V> currentValues = this.targetMap.computeIfAbsent(key, k -> new ArrayList<>(1));
List<V> currentValues = this.targetMap.computeIfAbsent(key, k -> new ArrayList<>(values.size()));
currentValues.addAll(values);
}
@@ -96,7 +96,7 @@ public class MultiValueMapAdapter<K, V> implements MultiValueMap<K, V>, Serializ
public Map<K, V> toSingleValueMap() {
Map<K, V> singleValueMap = CollectionUtils.newLinkedHashMap(this.targetMap.size());
this.targetMap.forEach((key, values) -> {
if (values != null && !values.isEmpty()) {
if (!CollectionUtils.isEmpty(values)) {
singleValueMap.put(key, values.get(0));
}
});
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -781,6 +781,7 @@ public abstract class StringUtils {
* and {@code "0"} through {@code "9"} stay the same.</li>
* <li>Special characters {@code "-"}, {@code "_"}, {@code "."}, and {@code "*"} stay the same.</li>
* <li>A sequence "{@code %<i>xy</i>}" is interpreted as a hexadecimal representation of the character.</li>
* <li>For all other characters (including those already decoded), the output is undefined.</li>
* </ul>
* @param source the encoded String
* @param charset the character set
@@ -829,7 +830,7 @@ public abstract class StringUtils {
* the {@link Locale#toString} format as well as BCP 47 language tags as
* specified by {@link Locale#forLanguageTag}.
* @param localeValue the locale value: following either {@code Locale's}
* {@code toString()} format ("en", "en_UK", etc), also accepting spaces as
* {@code toString()} format ("en", "en_UK", etc.), also accepting spaces as
* separators (as an alternative to underscores), or BCP 47 (e.g. "en-UK")
* @return a corresponding {@code Locale} instance, or {@code null} if none
* @throws IllegalArgumentException in case of an invalid locale specification
@@ -859,7 +860,7 @@ public abstract class StringUtils {
* <p><b>Note: This delegate does not accept the BCP 47 language tag format.
* Please use {@link #parseLocale} for lenient parsing of both formats.</b>
* @param localeString the locale {@code String}: following {@code Locale's}
* {@code toString()} format ("en", "en_UK", etc), also accepting spaces as
* {@code toString()} format ("en", "en_UK", etc.), also accepting spaces as
* separators (as an alternative to underscores)
* @return a corresponding {@code Locale} instance, or {@code null} if none
* @throws IllegalArgumentException in case of an invalid locale specification
@@ -24,6 +24,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Stream;
@@ -175,7 +176,7 @@ class MergedAnnotationsRepeatableAnnotationTests {
}
@Test
void typeHierarchyWhenWhenOnSuperclassReturnsAnnotations() {
void typeHierarchyWhenOnSuperclassReturnsAnnotations() {
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.TYPE_HIERARCHY, SubRepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
@@ -240,6 +241,44 @@ class MergedAnnotationsRepeatableAnnotationTests {
assertThat(annotationTypes).containsExactly(WithRepeatedMetaAnnotations.class, Noninherited.class, Noninherited.class);
}
@Test // gh-32731
void searchFindsRepeatableContainerAnnotationAndRepeatedAnnotations() {
Class<?> clazz = StandardRepeatablesWithContainerWithMultipleAttributesTestCase.class;
// NO RepeatableContainers
MergedAnnotations mergedAnnotations = MergedAnnotations.from(clazz, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none());
ContainerWithMultipleAttributes container = mergedAnnotations
.get(ContainerWithMultipleAttributes.class)
.synthesize(MergedAnnotation::isPresent).orElse(null);
assertThat(container).as("container").isNotNull();
assertThat(container.name()).isEqualTo("enigma");
RepeatableWithContainerWithMultipleAttributes[] repeatedAnnotations = container.value();
assertThat(Arrays.stream(repeatedAnnotations).map(RepeatableWithContainerWithMultipleAttributes::value))
.containsExactly("A", "B");
Set<RepeatableWithContainerWithMultipleAttributes> set =
mergedAnnotations.stream(RepeatableWithContainerWithMultipleAttributes.class)
.collect(MergedAnnotationCollectors.toAnnotationSet());
// Only finds the locally declared repeated annotation.
assertThat(set.stream().map(RepeatableWithContainerWithMultipleAttributes::value))
.containsExactly("C");
// Standard RepeatableContainers
mergedAnnotations = MergedAnnotations.from(clazz, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.standardRepeatables());
container = mergedAnnotations
.get(ContainerWithMultipleAttributes.class)
.synthesize(MergedAnnotation::isPresent).orElse(null);
assertThat(container).as("container").isNotNull();
assertThat(container.name()).isEqualTo("enigma");
repeatedAnnotations = container.value();
assertThat(Arrays.stream(repeatedAnnotations).map(RepeatableWithContainerWithMultipleAttributes::value))
.containsExactly("A", "B");
set = mergedAnnotations.stream(RepeatableWithContainerWithMultipleAttributes.class)
.collect(MergedAnnotationCollectors.toAnnotationSet());
// Finds the locally declared repeated annotation plus the 2 in the container.
assertThat(set.stream().map(RepeatableWithContainerWithMultipleAttributes::value))
.containsExactly("A", "B", "C");
}
private <A extends Annotation> Set<A> getAnnotations(Class<? extends Annotation> container,
Class<A> repeatable, SearchStrategy searchStrategy, AnnotatedElement element) {
@@ -449,4 +488,27 @@ class MergedAnnotationsRepeatableAnnotationTests {
static class WithRepeatedMetaAnnotationsClass {
}
@Retention(RetentionPolicy.RUNTIME)
@interface ContainerWithMultipleAttributes {
RepeatableWithContainerWithMultipleAttributes[] value();
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(ContainerWithMultipleAttributes.class)
@interface RepeatableWithContainerWithMultipleAttributes {
String value() default "";
}
@ContainerWithMultipleAttributes(name = "enigma", value = {
@RepeatableWithContainerWithMultipleAttributes("A"),
@RepeatableWithContainerWithMultipleAttributes("B")
})
@RepeatableWithContainerWithMultipleAttributes("C")
static class StandardRepeatablesWithContainerWithMultipleAttributesTestCase {
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,10 @@ package org.springframework.core.env;
import java.security.AccessControlException;
import java.security.Permission;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@@ -307,6 +310,12 @@ class StandardEnvironmentTests {
// non-string keys and values work fine... until the security manager is introduced below
assertThat(systemProperties.get(STRING_PROPERTY_NAME)).isEqualTo(NON_STRING_PROPERTY_VALUE);
assertThat(systemProperties.get(NON_STRING_PROPERTY_NAME)).isEqualTo(STRING_PROPERTY_VALUE);
PropertiesPropertySource systemPropertySource = (PropertiesPropertySource)
environment.getPropertySources().get(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
Set<String> expectedKeys = new HashSet<>(System.getProperties().stringPropertyNames());
expectedKeys.add(STRING_PROPERTY_NAME); // filtered out by stringPropertyNames due to non-String value
assertThat(new HashSet<>(Arrays.asList(systemPropertySource.getPropertyNames()))).isEqualTo(expectedKeys);
}
SecurityManager securityManager = new SecurityManager() {
@@ -407,6 +416,7 @@ class StandardEnvironmentTests {
EnvironmentTestUtils.getModifiableSystemEnvironment().remove(DISALLOWED_PROPERTY_NAME);
}
@Nested
class GetActiveProfiles {
@@ -456,6 +466,7 @@ class StandardEnvironmentTests {
}
}
@Nested
class AcceptsProfilesTests {
@@ -538,9 +549,9 @@ class StandardEnvironmentTests {
environment.addActiveProfile("p2");
assertThat(environment.acceptsProfiles(Profiles.of("p1 & p2"))).isTrue();
}
}
@Nested
class MatchesProfilesTests {
@@ -650,7 +661,6 @@ class StandardEnvironmentTests {
assertThat(environment.matchesProfiles("p2 & (foo | p1)")).isTrue();
assertThat(environment.matchesProfiles("foo", "(p2 & p1)")).isTrue();
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author 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,6 +45,7 @@ class StreamUtilsTests {
private String string = "";
@BeforeEach
void setup() {
new Random().nextBytes(bytes);
@@ -53,6 +54,7 @@ class StreamUtilsTests {
}
}
@Test
void copyToByteArray() throws Exception {
InputStream inputStream = new ByteArrayInputStream(bytes);
@@ -127,4 +129,5 @@ class StreamUtilsTests {
ordered.verify(source).write(bytes, 1, 2);
ordered.verify(source, never()).close();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -226,6 +226,8 @@ public class Indexer extends SpelNodeImpl {
cf.loadTarget(mv);
}
SpelNodeImpl index = this.children[0];
if (this.indexedType == IndexedType.ARRAY) {
int insn;
if ("D".equals(this.exitTypeDescriptor)) {
@@ -262,18 +264,14 @@ public class Indexer extends SpelNodeImpl {
//depthPlusOne(exitTypeDescriptor)+"Ljava/lang/Object;");
insn = AALOAD;
}
SpelNodeImpl index = this.children[0];
cf.enterCompilationScope();
index.generateCode(mv, cf);
cf.exitCompilationScope();
generateIndexCode(mv, cf, index, int.class);
mv.visitInsn(insn);
}
else if (this.indexedType == IndexedType.LIST) {
mv.visitTypeInsn(CHECKCAST, "java/util/List");
cf.enterCompilationScope();
this.children[0].generateCode(mv, cf);
cf.exitCompilationScope();
generateIndexCode(mv, cf, index, int.class);
mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "get", "(I)Ljava/lang/Object;", true);
}
@@ -281,15 +279,13 @@ public class Indexer extends SpelNodeImpl {
mv.visitTypeInsn(CHECKCAST, "java/util/Map");
// Special case when the key is an unquoted string literal that will be parsed as
// a property/field reference
if ((this.children[0] instanceof PropertyOrFieldReference)) {
if (index instanceof PropertyOrFieldReference) {
PropertyOrFieldReference reference = (PropertyOrFieldReference) this.children[0];
String mapKeyName = reference.getName();
mv.visitLdcInsn(mapKeyName);
}
else {
cf.enterCompilationScope();
this.children[0].generateCode(mv, cf);
cf.exitCompilationScope();
generateIndexCode(mv, cf, index, Object.class);
}
mv.visitMethodInsn(
INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", true);
@@ -325,6 +321,11 @@ public class Indexer extends SpelNodeImpl {
cf.pushDescriptor(this.exitTypeDescriptor);
}
private void generateIndexCode(MethodVisitor mv, CodeFlow cf, SpelNodeImpl indexNode, Class<?> indexType) {
String indexDesc = CodeFlow.toDescriptor(indexType);
generateCodeForArgument(mv, cf, indexNode, indexDesc);
}
@Override
public String toStringAST() {
StringJoiner sj = new StringJoiner(",", "[", "]");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author 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,8 @@ import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -27,7 +29,10 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.asm.MethodVisitor;
@@ -55,6 +60,7 @@ import static org.assertj.core.api.InstanceOfAssertFactories.BOOLEAN;
* Checks SpelCompiler behavior. This should cover compilation all compiled node types.
*
* @author Andy Clement
* @author Sam Brannen
* @since 4.1
*/
public class SpelCompilationCoverageTests extends AbstractExpressionTests {
@@ -129,6 +135,488 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
private SpelNodeImpl ast;
@Nested
class VariableReferenceTests {
@Test
void userDefinedVariable() {
EvaluationContext ctx = new StandardEvaluationContext();
ctx.setVariable("target", "abc");
expression = parser.parseExpression("#target");
assertThat(expression.getValue(ctx)).isEqualTo("abc");
assertCanCompile(expression);
assertThat(expression.getValue(ctx)).isEqualTo("abc");
ctx.setVariable("target", "123");
assertThat(expression.getValue(ctx)).isEqualTo("123");
// Changing the variable type from String to Integer results in a
// ClassCastException in the compiled code.
ctx.setVariable("target", 42);
assertThatExceptionOfType(SpelEvaluationException.class)
.isThrownBy(() -> expression.getValue(ctx))
.withCauseInstanceOf(ClassCastException.class);
ctx.setVariable("target", "abc");
expression = parser.parseExpression("#target.charAt(0)");
assertThat(expression.getValue(ctx)).isEqualTo('a');
assertCanCompile(expression);
assertThat(expression.getValue(ctx)).isEqualTo('a');
ctx.setVariable("target", "1");
assertThat(expression.getValue(ctx)).isEqualTo('1');
// Changing the variable type from String to Integer results in a
// ClassCastException in the compiled code.
ctx.setVariable("target", 42);
assertThatExceptionOfType(SpelEvaluationException.class)
.isThrownBy(() -> expression.getValue(ctx))
.withCauseInstanceOf(ClassCastException.class);
}
}
@Nested
class IndexingTests {
@Test
void indexIntoPrimitiveShortArray() {
short[] shorts = { (short) 33, (short) 44, (short) 55 };
expression = parser.parseExpression("[2]");
assertThat(expression.getValue(shorts)).isEqualTo((short) 55);
assertCanCompile(expression);
assertThat(expression.getValue(shorts)).isEqualTo((short) 55);
assertThat(getAst().getExitDescriptor()).isEqualTo("S");
}
@Test
void indexIntoPrimitiveByteArray() {
byte[] bytes = { (byte) 2, (byte) 3, (byte) 4 };
expression = parser.parseExpression("[2]");
assertThat(expression.getValue(bytes)).isEqualTo((byte) 4);
assertCanCompile(expression);
assertThat(expression.getValue(bytes)).isEqualTo((byte) 4);
assertThat(getAst().getExitDescriptor()).isEqualTo("B");
}
@Test
void indexIntoPrimitiveIntArray() {
int[] ints = { 8, 9, 10 };
expression = parser.parseExpression("[2]");
assertThat(expression.getValue(ints)).isEqualTo(10);
assertCanCompile(expression);
assertThat(expression.getValue(ints)).isEqualTo(10);
assertThat(getAst().getExitDescriptor()).isEqualTo("I");
}
@Test
void indexIntoPrimitiveLongArray() {
long[] longs = { 2L, 3L, 4L };
expression = parser.parseExpression("[0]");
assertThat(expression.getValue(longs)).isEqualTo(2L);
assertCanCompile(expression);
assertThat(expression.getValue(longs)).isEqualTo(2L);
assertThat(getAst().getExitDescriptor()).isEqualTo("J");
}
@Test
void indexIntoPrimitiveFloatArray() {
float[] floats = { 6.0f, 7.0f, 8.0f };
expression = parser.parseExpression("[0]");
assertThat(expression.getValue(floats)).isEqualTo(6.0f);
assertCanCompile(expression);
assertThat(expression.getValue(floats)).isEqualTo(6.0f);
assertThat(getAst().getExitDescriptor()).isEqualTo("F");
}
@Test
void indexIntoPrimitiveDoubleArray() {
double[] doubles = { 3.0d, 4.0d, 5.0d };
expression = parser.parseExpression("[1]");
assertThat(expression.getValue(doubles)).isEqualTo(4.0d);
assertCanCompile(expression);
assertThat(expression.getValue(doubles)).isEqualTo(4.0d);
assertThat(getAst().getExitDescriptor()).isEqualTo("D");
}
@Test
void indexIntoPrimitiveCharArray() {
char[] chars = { 'a', 'b', 'c' };
expression = parser.parseExpression("[1]");
assertThat(expression.getValue(chars)).isEqualTo('b');
assertCanCompile(expression);
assertThat(expression.getValue(chars)).isEqualTo('b');
assertThat(getAst().getExitDescriptor()).isEqualTo("C");
}
@Test
void indexIntoStringArray() {
String[] strings = { "a", "b", "c" };
expression = parser.parseExpression("[0]");
assertThat(expression.getValue(strings)).isEqualTo("a");
assertCanCompile(expression);
assertThat(expression.getValue(strings)).isEqualTo("a");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/String");
}
@Test
void indexIntoNumberArray() {
Number[] numbers = { 2, 8, 9 };
expression = parser.parseExpression("[1]");
assertThat(expression.getValue(numbers)).isEqualTo(8);
assertCanCompile(expression);
assertThat(expression.getValue(numbers)).isEqualTo(8);
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Number");
}
@Test
void indexInto2DPrimitiveIntArray() {
int[][] array = new int[][] {
{ 1, 2, 3 },
{ 4, 5, 6 }
};
expression = parser.parseExpression("[1]");
assertThat(stringify(expression.getValue(array))).isEqualTo("4 5 6");
assertCanCompile(expression);
assertThat(stringify(expression.getValue(array))).isEqualTo("4 5 6");
assertThat(getAst().getExitDescriptor()).isEqualTo("[I");
expression = parser.parseExpression("[1][2]");
assertThat(stringify(expression.getValue(array))).isEqualTo("6");
assertCanCompile(expression);
assertThat(stringify(expression.getValue(array))).isEqualTo("6");
assertThat(getAst().getExitDescriptor()).isEqualTo("I");
}
@Test
void indexInto2DStringArray() {
String[][] array = new String[][] {
{ "a", "b", "c" },
{ "d", "e", "f" }
};
expression = parser.parseExpression("[1]");
assertThat(stringify(expression.getValue(array))).isEqualTo("d e f");
assertCanCompile(expression);
assertThat(getAst().getExitDescriptor()).isEqualTo("[Ljava/lang/String");
assertThat(stringify(expression.getValue(array))).isEqualTo("d e f");
assertThat(getAst().getExitDescriptor()).isEqualTo("[Ljava/lang/String");
expression = parser.parseExpression("[1][2]");
assertThat(stringify(expression.getValue(array))).isEqualTo("f");
assertCanCompile(expression);
assertThat(stringify(expression.getValue(array))).isEqualTo("f");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/String");
}
@Test
@SuppressWarnings("unchecked")
void indexIntoArrayOfListOfString() {
List<String>[] array = new List[] {
Arrays.asList("a", "b", "c"),
Arrays.asList("d", "e", "f")
};
expression = parser.parseExpression("[1]");
assertThat(stringify(expression.getValue(array))).isEqualTo("d e f");
assertCanCompile(expression);
assertThat(stringify(expression.getValue(array))).isEqualTo("d e f");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/util/List");
expression = parser.parseExpression("[1][2]");
assertThat(stringify(expression.getValue(array))).isEqualTo("f");
assertCanCompile(expression);
assertThat(stringify(expression.getValue(array))).isEqualTo("f");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
}
@Test
@SuppressWarnings("unchecked")
void indexIntoArrayOfMap() {
Map<String, String>[] array = new Map[] { Collections.singletonMap("key", "value1") };
expression = parser.parseExpression("[0]");
assertThat(stringify(expression.getValue(array))).isEqualTo("{key=value1}");
assertCanCompile(expression);
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/util/Map");
assertThat(stringify(expression.getValue(array))).isEqualTo("{key=value1}");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/util/Map");
expression = parser.parseExpression("[0]['key']");
assertThat(stringify(expression.getValue(array))).isEqualTo("value1");
assertCanCompile(expression);
assertThat(stringify(expression.getValue(array))).isEqualTo("value1");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
}
@Test
void indexIntoListOfString() {
List<String> list = Arrays.asList("aaa", "bbb", "ccc");
expression = parser.parseExpression("[1]");
assertThat(expression.getValue(list)).isEqualTo("bbb");
assertCanCompile(expression);
assertThat(expression.getValue(list)).isEqualTo("bbb");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
}
@Test
void indexIntoListOfInteger() {
List<Integer> list = Arrays.asList(123, 456, 789);
expression = parser.parseExpression("[2]");
assertThat(expression.getValue(list)).isEqualTo(789);
assertCanCompile(expression);
assertThat(expression.getValue(list)).isEqualTo(789);
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
}
@Test
void indexIntoListOfStringArray() {
List<String[]> list = Arrays.asList(
new String[] { "a", "b", "c" },
new String[] { "d", "e", "f" }
);
expression = parser.parseExpression("[1]");
assertThat(stringify(expression.getValue(list))).isEqualTo("d e f");
assertCanCompile(expression);
assertThat(stringify(expression.getValue(list))).isEqualTo("d e f");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
expression = parser.parseExpression("[1][0]");
assertThat(stringify(expression.getValue(list))).isEqualTo("d");
assertCanCompile(expression);
assertThat(stringify(expression.getValue(list))).isEqualTo("d");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/String");
}
@Test
void indexIntoListOfIntegerArray() {
List<Integer[]> list = Arrays.asList(
new Integer[] { 1, 2, 3 },
new Integer[] { 4, 5, 6 }
);
expression = parser.parseExpression("[0]");
assertThat(stringify(expression.getValue(list))).isEqualTo("1 2 3");
assertCanCompile(expression);
assertThat(stringify(expression.getValue(list))).isEqualTo("1 2 3");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
expression = parser.parseExpression("[0][1]");
assertThat(expression.getValue(list)).isEqualTo(2);
assertCanCompile(expression);
assertThat(expression.getValue(list)).isEqualTo(2);
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Integer");
}
@Test
void indexIntoListOfListOfString() {
List<List<String>> list = Arrays.asList(
Arrays.asList("a", "b", "c"),
Arrays.asList("d", "e", "f")
);
expression = parser.parseExpression("[1]");
assertThat(stringify(expression.getValue(list))).isEqualTo("d e f");
assertCanCompile(expression);
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
assertThat(stringify(expression.getValue(list))).isEqualTo("d e f");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
expression = parser.parseExpression("[1][2]");
assertThat(stringify(expression.getValue(list))).isEqualTo("f");
assertCanCompile(expression);
assertThat(stringify(expression.getValue(list))).isEqualTo("f");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
}
@Test
void indexIntoMap() {
Map<String, Integer> map = Collections.singletonMap("aaa", 111);
expression = parser.parseExpression("['aaa']");
assertThat(expression.getValue(map)).isEqualTo(111);
assertCanCompile(expression);
assertThat(expression.getValue(map)).isEqualTo(111);
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
}
@Test
void indexIntoMapOfListOfString() {
Map<String, List<String>> map = Collections.singletonMap("foo", Arrays.asList("a", "b", "c"));
expression = parser.parseExpression("['foo']");
assertThat(stringify(expression.getValue(map))).isEqualTo("a b c");
assertCanCompile(expression);
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
assertThat(stringify(expression.getValue(map))).isEqualTo("a b c");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
expression = parser.parseExpression("['foo'][2]");
assertThat(stringify(expression.getValue(map))).isEqualTo("c");
assertCanCompile(expression);
assertThat(stringify(expression.getValue(map))).isEqualTo("c");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
}
@Test
void indexIntoObject() {
TestClass6 tc = new TestClass6();
// field access
expression = parser.parseExpression("['orange']");
assertThat(expression.getValue(tc)).isEqualTo("value1");
assertCanCompile(expression);
assertThat(expression.getValue(tc)).isEqualTo("value1");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/String");
// field access
expression = parser.parseExpression("['peach']");
assertThat(expression.getValue(tc)).isEqualTo(34L);
assertCanCompile(expression);
assertThat(expression.getValue(tc)).isEqualTo(34L);
assertThat(getAst().getExitDescriptor()).isEqualTo("J");
// property access (getter)
expression = parser.parseExpression("['banana']");
assertThat(expression.getValue(tc)).isEqualTo("value3");
assertCanCompile(expression);
assertThat(expression.getValue(tc)).isEqualTo("value3");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/String");
}
@Test // gh-32694, gh-32908
void indexIntoArrayUsingIntegerWrapper() {
context.setVariable("array", new int[] {1, 2, 3, 4});
context.setVariable("index", 2);
expression = parser.parseExpression("#array[#index]");
assertThat(expression.getValue(context)).isEqualTo(3);
assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo(3);
assertThat(getAst().getExitDescriptor()).isEqualTo("I");
}
@Test // gh-32694, gh-32908
void indexIntoListUsingIntegerWrapper() {
context.setVariable("list", Arrays.asList(1, 2, 3, 4));
context.setVariable("index", 2);
expression = parser.parseExpression("#list[#index]");
assertThat(expression.getValue(context)).isEqualTo(3);
assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo(3);
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
}
@Test // gh-32903
void indexIntoMapUsingPrimitiveLiteral() {
Map<Object, String> map = new HashMap<>();
map.put(false, "0"); // BooleanLiteral
map.put(1, "ABC"); // IntLiteral
map.put(2L, "XYZ"); // LongLiteral
map.put(9.99F, "~10"); // FloatLiteral
map.put(3.14159, "PI"); // RealLiteral
context.setVariable("map", map);
// BooleanLiteral
expression = parser.parseExpression("#map[false]");
assertThat(expression.getValue(context)).isEqualTo("0");
assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo("0");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
// IntLiteral
expression = parser.parseExpression("#map[1]");
assertThat(expression.getValue(context)).isEqualTo("ABC");
assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo("ABC");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
// LongLiteral
expression = parser.parseExpression("#map[2L]");
assertThat(expression.getValue(context)).isEqualTo("XYZ");
assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo("XYZ");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
// FloatLiteral
expression = parser.parseExpression("#map[9.99F]");
assertThat(expression.getValue(context)).isEqualTo("~10");
assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo("~10");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
// RealLiteral
expression = parser.parseExpression("#map[3.14159]");
assertThat(expression.getValue(context)).isEqualTo("PI");
assertCanCompile(expression);
assertThat(expression.getValue(context)).isEqualTo("PI");
assertThat(getAst().getExitDescriptor()).isEqualTo("Ljava/lang/Object");
}
private String stringify(Object object) {
Stream<? extends Object> stream;
if (object instanceof Collection) {
stream = ((Collection<?>) object).stream();
}
else if (object instanceof Object[]) {
stream = Arrays.stream((Object[]) object);
}
else if (object instanceof int[]) {
stream = Arrays.stream((int[]) object).mapToObj(Integer::valueOf);
}
else {
return String.valueOf(object);
}
return stream.map(Object::toString).collect(Collectors.joining(" "));
}
}
@Test
void typeReference() {
expression = parse("T(String)");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
/**
* The default implementation of Spring's {@link SqlRowSet} interface, wrapping a
* The common implementation of Spring's {@link SqlRowSet} interface, wrapping a
* {@link java.sql.ResultSet}, catching any {@link SQLException SQLExceptions} and
* translating them to a corresponding Spring {@link InvalidResultSetAccessException}.
*
@@ -43,17 +43,17 @@ import org.springframework.util.CollectionUtils;
* <p>Note: Since JDBC 4.0, it has been clarified that any methods using a String to identify
* the column should be using the column label. The column label is assigned using the ALIAS
* keyword in the SQL query string. When the query doesn't use an ALIAS, the default label is
* the column name. Most JDBC ResultSet implementations follow this new pattern but there are
* the column name. Most JDBC ResultSet implementations follow this pattern, but there are
* exceptions such as the {@code com.sun.rowset.CachedRowSetImpl} class which only uses
* the column name, ignoring any column labels. As of Spring 3.0.5, ResultSetWrappingSqlRowSet
* will translate column labels to the correct column index to provide better support for the
* the column name, ignoring any column labels. {@code ResultSetWrappingSqlRowSet}
* will translate column labels to the correct column index to provide better support for
* {@code com.sun.rowset.CachedRowSetImpl} which is the default implementation used by
* {@link org.springframework.jdbc.core.JdbcTemplate} when working with RowSets.
*
* <p>Note: This class implements the {@code java.io.Serializable} marker interface
* through the SqlRowSet interface, but is only actually serializable if the disconnected
* ResultSet/RowSet contained in it is serializable. Most CachedRowSet implementations
* are actually serializable, so this should usually work out.
* are actually serializable, so serialization should usually work.
*
* @author Thomas Risberg
* @author Juergen Hoeller
@@ -67,16 +67,18 @@ public class ResultSetWrappingSqlRowSet implements SqlRowSet {
/** use serialVersionUID from Spring 1.2 for interoperability. */
private static final long serialVersionUID = -4688694393146734764L;
@SuppressWarnings("serial")
private final ResultSet resultSet;
@SuppressWarnings("serial")
private final SqlRowSetMetaData rowSetMetaData;
@SuppressWarnings("serial")
private final Map<String, Integer> columnLabelMap;
/**
* Create a new ResultSetWrappingSqlRowSet for the given ResultSet.
* Create a new {@code ResultSetWrappingSqlRowSet} for the given {@link ResultSet}.
* @param resultSet a disconnected ResultSet to wrap
* (usually a {@code javax.sql.rowset.CachedRowSet})
* @throws InvalidResultSetAccessException if extracting
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2024 the original author 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.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.InvalidResultSetAccessException;
@@ -38,174 +37,168 @@ import static org.mockito.Mockito.mock;
/**
* @author Thomas Risberg
*/
public class ResultSetWrappingRowSetTests {
class ResultSetWrappingRowSetTests {
private ResultSet resultSet;
private final ResultSet resultSet = mock(ResultSet.class);
private ResultSetWrappingSqlRowSet rowSet;
@BeforeEach
public void setup() throws Exception {
resultSet = mock(ResultSet.class);
rowSet = new ResultSetWrappingSqlRowSet(resultSet);
}
private final ResultSetWrappingSqlRowSet rowSet = new ResultSetWrappingSqlRowSet(resultSet);
@Test
public void testGetBigDecimalInt() throws Exception {
void testGetBigDecimalInt() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getBigDecimal", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getBigDecimal", int.class);
doTest(rset, rowset, 1, BigDecimal.ONE);
}
@Test
public void testGetBigDecimalString() throws Exception {
void testGetBigDecimalString() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getBigDecimal", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getBigDecimal", String.class);
doTest(rset, rowset, "test", BigDecimal.ONE);
}
@Test
public void testGetStringInt() throws Exception {
void testGetStringInt() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getString", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getString", int.class);
doTest(rset, rowset, 1, "test");
}
@Test
public void testGetStringString() throws Exception {
void testGetStringString() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getString", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getString", String.class);
doTest(rset, rowset, "test", "test");
}
@Test
public void testGetTimestampInt() throws Exception {
void testGetTimestampInt() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getTimestamp", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getTimestamp", int.class);
doTest(rset, rowset, 1, new Timestamp(1234L));
}
@Test
public void testGetTimestampString() throws Exception {
void testGetTimestampString() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getTimestamp", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getTimestamp", String.class);
doTest(rset, rowset, "test", new Timestamp(1234L));
}
@Test
public void testGetDateInt() throws Exception {
void testGetDateInt() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getDate", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getDate", int.class);
doTest(rset, rowset, 1, new Date(1234L));
}
@Test
public void testGetDateString() throws Exception {
void testGetDateString() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getDate", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getDate", String.class);
doTest(rset, rowset, "test", new Date(1234L));
}
@Test
public void testGetTimeInt() throws Exception {
void testGetTimeInt() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getTime", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getTime", int.class);
doTest(rset, rowset, 1, new Time(1234L));
}
@Test
public void testGetTimeString() throws Exception {
void testGetTimeString() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getTime", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getTime", String.class);
doTest(rset, rowset, "test", new Time(1234L));
}
@Test
public void testGetObjectInt() throws Exception {
void testGetObjectInt() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getObject", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getObject", int.class);
doTest(rset, rowset, 1, new Object());
}
@Test
public void testGetObjectString() throws Exception {
void testGetObjectString() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getObject", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getObject", String.class);
doTest(rset, rowset, "test", new Object());
}
@Test
public void testGetIntInt() throws Exception {
void testGetIntInt() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getInt", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getInt", int.class);
doTest(rset, rowset, 1, 1);
}
@Test
public void testGetIntString() throws Exception {
void testGetIntString() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getInt", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getInt", String.class);
doTest(rset, rowset, "test", 1);
}
@Test
public void testGetFloatInt() throws Exception {
void testGetFloatInt() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getFloat", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getFloat", int.class);
doTest(rset, rowset, 1, 1.0f);
}
@Test
public void testGetFloatString() throws Exception {
void testGetFloatString() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getFloat", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getFloat", String.class);
doTest(rset, rowset, "test", 1.0f);
}
@Test
public void testGetDoubleInt() throws Exception {
void testGetDoubleInt() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getDouble", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getDouble", int.class);
doTest(rset, rowset, 1, 1.0d);
}
@Test
public void testGetDoubleString() throws Exception {
void testGetDoubleString() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getDouble", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getDouble", String.class);
doTest(rset, rowset, "test", 1.0d);
}
@Test
public void testGetLongInt() throws Exception {
void testGetLongInt() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getLong", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getLong", int.class);
doTest(rset, rowset, 1, 1L);
}
@Test
public void testGetLongString() throws Exception {
void testGetLongString() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getLong", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getLong", String.class);
doTest(rset, rowset, "test", 1L);
}
@Test
public void testGetBooleanInt() throws Exception {
void testGetBooleanInt() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getBoolean", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getBoolean", int.class);
doTest(rset, rowset, 1, true);
}
@Test
public void testGetBooleanString() throws Exception {
void testGetBooleanString() throws Exception {
Method rset = ResultSet.class.getDeclaredMethod("getBoolean", int.class);
Method rowset = ResultSetWrappingSqlRowSet.class.getDeclaredMethod("getBoolean", String.class);
doTest(rset, rowset, "test", true);
}
private void doTest(Method rsetMethod, Method rowsetMethod, Object arg, Object ret) throws Exception {
if (arg instanceof String) {
given(resultSet.findColumn((String) arg)).willReturn(1);
@@ -215,9 +208,9 @@ public class ResultSetWrappingRowSetTests {
given(rsetMethod.invoke(resultSet, arg)).willReturn(ret).willThrow(new SQLException("test"));
}
rowsetMethod.invoke(rowSet, arg);
assertThatExceptionOfType(InvocationTargetException.class).isThrownBy(() ->
rowsetMethod.invoke(rowSet, arg)).
satisfies(ex -> assertThat(ex.getTargetException()).isExactlyInstanceOf(InvalidResultSetAccessException.class));
assertThatExceptionOfType(InvocationTargetException.class)
.isThrownBy(() -> rowsetMethod.invoke(rowSet, arg))
.satisfies(ex -> assertThat(ex.getTargetException()).isExactlyInstanceOf(InvalidResultSetAccessException.class));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -189,11 +189,6 @@ public class JmsMessagingTemplate extends AbstractMessagingTemplate<Destination>
}
}
@Override
public void convertAndSend(Object payload) throws MessagingException {
convertAndSend(payload, null);
}
@Override
public void convertAndSend(Object payload, @Nullable MessagePostProcessor postProcessor) throws MessagingException {
Destination defaultDestination = getDefaultDestination();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -290,7 +290,7 @@ public interface JmsOperations {
* <p>This method should be used carefully, since it will block the thread
* until the message becomes available or until the timeout value is exceeded.
* <p>This will only work with a default destination specified!
* @return the message produced for the consumer or {@code null} if the timeout expires.
* @return the message produced for the consumer, or {@code null} if the timeout expires
* @throws JmsException checked JMSException converted to unchecked
*/
@Nullable
@@ -303,7 +303,7 @@ public interface JmsOperations {
* <p>This method should be used carefully, since it will block the thread
* until the message becomes available or until the timeout value is exceeded.
* @param destination the destination to receive a message from
* @return the message produced for the consumer or {@code null} if the timeout expires.
* @return the message produced for the consumer, or {@code null} if the timeout expires
* @throws JmsException checked JMSException converted to unchecked
*/
@Nullable
@@ -317,7 +317,7 @@ public interface JmsOperations {
* until the message becomes available or until the timeout value is exceeded.
* @param destinationName the name of the destination to send this message to
* (to be resolved to an actual destination by a DestinationResolver)
* @return the message produced for the consumer or {@code null} if the timeout expires.
* @return the message produced for the consumer, or {@code null} if the timeout expires
* @throws JmsException checked JMSException converted to unchecked
*/
@Nullable
@@ -332,7 +332,7 @@ public interface JmsOperations {
* <p>This will only work with a default destination specified!
* @param messageSelector the JMS message selector expression (or {@code null} if none).
* See the JMS specification for a detailed definition of selector expressions.
* @return the message produced for the consumer or {@code null} if the timeout expires.
* @return the message produced for the consumer, or {@code null} if the timeout expires
* @throws JmsException checked JMSException converted to unchecked
*/
@Nullable
@@ -347,7 +347,7 @@ public interface JmsOperations {
* @param destination the destination to receive a message from
* @param messageSelector the JMS message selector expression (or {@code null} if none).
* See the JMS specification for a detailed definition of selector expressions.
* @return the message produced for the consumer or {@code null} if the timeout expires.
* @return the message produced for the consumer, or {@code null} if the timeout expires
* @throws JmsException checked JMSException converted to unchecked
*/
@Nullable
@@ -363,7 +363,7 @@ public interface JmsOperations {
* (to be resolved to an actual destination by a DestinationResolver)
* @param messageSelector the JMS message selector expression (or {@code null} if none).
* See the JMS specification for a detailed definition of selector expressions.
* @return the message produced for the consumer or {@code null} if the timeout expires.
* @return the message produced for the consumer, or {@code null} if the timeout expires
* @throws JmsException checked JMSException converted to unchecked
*/
@Nullable
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -390,7 +390,9 @@ public abstract class SharedEntityManagerCreator {
else if (targetClass.isInstance(proxy)) {
return proxy;
}
break;
else {
return this.target.unwrap(targetClass);
}
case "getOutputParameterValue":
if (this.entityManager == null) {
Object key = args[0];
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -154,7 +154,7 @@ final class PersistenceUnitReader {
/**
* Validate the given stream and return a valid DOM document for parsing.
*/
protected Document buildDocument(ErrorHandler handler, InputStream stream)
Document buildDocument(ErrorHandler handler, InputStream stream)
throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
@@ -168,9 +168,7 @@ final class PersistenceUnitReader {
/**
* Parse the validated document and add entries to the given unit info list.
*/
protected List<SpringPersistenceUnitInfo> parseDocument(
Resource resource, Document document, List<SpringPersistenceUnitInfo> infos) throws IOException {
void parseDocument(Resource resource, Document document, List<SpringPersistenceUnitInfo> infos) throws IOException {
Element persistence = document.getDocumentElement();
String version = persistence.getAttribute(PERSISTENCE_VERSION);
URL rootUrl = determinePersistenceUnitRootUrl(resource);
@@ -179,14 +177,12 @@ final class PersistenceUnitReader {
for (Element unit : units) {
infos.add(parsePersistenceUnitInfo(unit, version, rootUrl));
}
return infos;
}
/**
* Parse the unit info DOM element.
*/
protected SpringPersistenceUnitInfo parsePersistenceUnitInfo(
SpringPersistenceUnitInfo parsePersistenceUnitInfo(
Element persistenceUnit, String version, @Nullable URL rootUrl) throws IOException {
SpringPersistenceUnitInfo unitInfo = new SpringPersistenceUnitInfo();
@@ -253,7 +249,7 @@ final class PersistenceUnitReader {
/**
* Parse the {@code property} XML elements.
*/
protected void parseProperties(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) {
void parseProperties(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) {
Element propRoot = DomUtils.getChildElementByTagName(persistenceUnit, PROPERTIES);
if (propRoot == null) {
return;
@@ -269,7 +265,7 @@ final class PersistenceUnitReader {
/**
* Parse the {@code class} XML elements.
*/
protected void parseManagedClasses(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) {
void parseManagedClasses(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) {
List<Element> classes = DomUtils.getChildElementsByTagName(persistenceUnit, MANAGED_CLASS_NAME);
for (Element element : classes) {
String value = DomUtils.getTextValue(element).trim();
@@ -282,7 +278,7 @@ final class PersistenceUnitReader {
/**
* Parse the {@code mapping-file} XML elements.
*/
protected void parseMappingFiles(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) {
void parseMappingFiles(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) {
List<Element> files = DomUtils.getChildElementsByTagName(persistenceUnit, MAPPING_FILE_NAME);
for (Element element : files) {
String value = DomUtils.getTextValue(element).trim();
@@ -295,7 +291,7 @@ final class PersistenceUnitReader {
/**
* Parse the {@code jar-file} XML elements.
*/
protected void parseJarFiles(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) throws IOException {
void parseJarFiles(Element persistenceUnit, SpringPersistenceUnitInfo unitInfo) throws IOException {
List<Element> jars = DomUtils.getChildElementsByTagName(persistenceUnit, JAR_FILE_URL);
for (Element element : jars) {
String value = DomUtils.getTextValue(element).trim();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,16 +38,16 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.withSettings;
/**
* Unit tests for {@link SharedEntityManagerCreator}.
* Tests for {@link SharedEntityManagerCreator}.
*
* @author Oliver Gierke
* @author Juergen Hoeller
*/
@ExtendWith(MockitoExtension.class)
public class SharedEntityManagerCreatorTests {
class SharedEntityManagerCreatorTests {
@Test
public void proxyingWorksIfInfoReturnsNullEntityManagerInterface() {
void proxyingWorksIfInfoReturnsNullEntityManagerInterface() {
EntityManagerFactory emf = mock(EntityManagerFactory.class,
withSettings().extraInterfaces(EntityManagerFactoryInfo.class));
// EntityManagerFactoryInfo.getEntityManagerInterface returns null
@@ -55,7 +55,7 @@ public class SharedEntityManagerCreatorTests {
}
@Test
public void transactionRequiredExceptionOnJoinTransaction() {
void transactionRequiredExceptionOnJoinTransaction() {
EntityManagerFactory emf = mock(EntityManagerFactory.class);
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
assertThatExceptionOfType(TransactionRequiredException.class).isThrownBy(
@@ -63,7 +63,7 @@ public class SharedEntityManagerCreatorTests {
}
@Test
public void transactionRequiredExceptionOnFlush() {
void transactionRequiredExceptionOnFlush() {
EntityManagerFactory emf = mock(EntityManagerFactory.class);
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
assertThatExceptionOfType(TransactionRequiredException.class).isThrownBy(
@@ -71,7 +71,7 @@ public class SharedEntityManagerCreatorTests {
}
@Test
public void transactionRequiredExceptionOnPersist() {
void transactionRequiredExceptionOnPersist() {
EntityManagerFactory emf = mock(EntityManagerFactory.class);
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
assertThatExceptionOfType(TransactionRequiredException.class).isThrownBy(() ->
@@ -79,7 +79,7 @@ public class SharedEntityManagerCreatorTests {
}
@Test
public void transactionRequiredExceptionOnMerge() {
void transactionRequiredExceptionOnMerge() {
EntityManagerFactory emf = mock(EntityManagerFactory.class);
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
assertThatExceptionOfType(TransactionRequiredException.class).isThrownBy(() ->
@@ -87,7 +87,7 @@ public class SharedEntityManagerCreatorTests {
}
@Test
public void transactionRequiredExceptionOnRemove() {
void transactionRequiredExceptionOnRemove() {
EntityManagerFactory emf = mock(EntityManagerFactory.class);
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
assertThatExceptionOfType(TransactionRequiredException.class).isThrownBy(() ->
@@ -95,7 +95,7 @@ public class SharedEntityManagerCreatorTests {
}
@Test
public void transactionRequiredExceptionOnRefresh() {
void transactionRequiredExceptionOnRefresh() {
EntityManagerFactory emf = mock(EntityManagerFactory.class);
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
assertThatExceptionOfType(TransactionRequiredException.class).isThrownBy(() ->
@@ -103,78 +103,98 @@ public class SharedEntityManagerCreatorTests {
}
@Test
public void deferredQueryWithUpdate() {
void deferredQueryWithUpdate() {
EntityManagerFactory emf = mock(EntityManagerFactory.class);
EntityManager targetEm = mock(EntityManager.class);
Query query = mock(Query.class);
Query targetQuery = mock(Query.class);
given(emf.createEntityManager()).willReturn(targetEm);
given(targetEm.createQuery("x")).willReturn(query);
given(targetEm.createQuery("x")).willReturn(targetQuery);
given(targetEm.isOpen()).willReturn(true);
given((Query) targetQuery.unwrap(targetQuery.getClass())).willReturn(targetQuery);
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
em.createQuery("x").executeUpdate();
Query query = em.createQuery("x");
assertThat((Query) query.unwrap(null)).isSameAs(targetQuery);
assertThat((Query) query.unwrap(targetQuery.getClass())).isSameAs(targetQuery);
assertThat(query.unwrap(Query.class)).isSameAs(query);
query.executeUpdate();
verify(query).executeUpdate();
verify(targetQuery).executeUpdate();
verify(targetEm).close();
}
@Test
public void deferredQueryWithSingleResult() {
void deferredQueryWithSingleResult() {
EntityManagerFactory emf = mock(EntityManagerFactory.class);
EntityManager targetEm = mock(EntityManager.class);
Query query = mock(Query.class);
Query targetQuery = mock(Query.class);
given(emf.createEntityManager()).willReturn(targetEm);
given(targetEm.createQuery("x")).willReturn(query);
given(targetEm.createQuery("x")).willReturn(targetQuery);
given(targetEm.isOpen()).willReturn(true);
given((Query) targetQuery.unwrap(targetQuery.getClass())).willReturn(targetQuery);
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
em.createQuery("x").getSingleResult();
Query query = em.createQuery("x");
assertThat((Query) query.unwrap(null)).isSameAs(targetQuery);
assertThat((Query) query.unwrap(targetQuery.getClass())).isSameAs(targetQuery);
assertThat(query.unwrap(Query.class)).isSameAs(query);
query.getSingleResult();
verify(query).getSingleResult();
verify(targetQuery).getSingleResult();
verify(targetEm).close();
}
@Test
public void deferredQueryWithResultList() {
void deferredQueryWithResultList() {
EntityManagerFactory emf = mock(EntityManagerFactory.class);
EntityManager targetEm = mock(EntityManager.class);
Query query = mock(Query.class);
Query targetQuery = mock(Query.class);
given(emf.createEntityManager()).willReturn(targetEm);
given(targetEm.createQuery("x")).willReturn(query);
given(targetEm.createQuery("x")).willReturn(targetQuery);
given(targetEm.isOpen()).willReturn(true);
given((Query) targetQuery.unwrap(targetQuery.getClass())).willReturn(targetQuery);
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
em.createQuery("x").getResultList();
Query query = em.createQuery("x");
assertThat((Query) query.unwrap(null)).isSameAs(targetQuery);
assertThat((Query) query.unwrap(targetQuery.getClass())).isSameAs(targetQuery);
assertThat(query.unwrap(Query.class)).isSameAs(query);
query.getResultList();
verify(query).getResultList();
verify(targetQuery).getResultList();
verify(targetEm).close();
}
@Test
public void deferredQueryWithResultStream() {
void deferredQueryWithResultStream() {
EntityManagerFactory emf = mock(EntityManagerFactory.class);
EntityManager targetEm = mock(EntityManager.class);
Query query = mock(Query.class);
Query targetQuery = mock(Query.class);
given(emf.createEntityManager()).willReturn(targetEm);
given(targetEm.createQuery("x")).willReturn(query);
given(targetEm.createQuery("x")).willReturn(targetQuery);
given(targetEm.isOpen()).willReturn(true);
given((Query) targetQuery.unwrap(targetQuery.getClass())).willReturn(targetQuery);
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
em.createQuery("x").getResultStream();
Query query = em.createQuery("x");
assertThat((Query) query.unwrap(null)).isSameAs(targetQuery);
assertThat((Query) query.unwrap(targetQuery.getClass())).isSameAs(targetQuery);
assertThat(query.unwrap(Query.class)).isSameAs(query);
query.getResultStream();
verify(query).getResultStream();
verify(targetQuery).getResultStream();
verify(targetEm).close();
}
@Test
public void deferredStoredProcedureQueryWithIndexedParameters() {
void deferredStoredProcedureQueryWithIndexedParameters() {
EntityManagerFactory emf = mock(EntityManagerFactory.class);
EntityManager targetEm = mock(EntityManager.class);
StoredProcedureQuery query = mock(StoredProcedureQuery.class);
StoredProcedureQuery targetQuery = mock(StoredProcedureQuery.class);
given(emf.createEntityManager()).willReturn(targetEm);
given(targetEm.createStoredProcedureQuery("x")).willReturn(query);
willReturn("y").given(query).getOutputParameterValue(0);
willReturn("z").given(query).getOutputParameterValue(2);
given(targetEm.createStoredProcedureQuery("x")).willReturn(targetQuery);
willReturn("y").given(targetQuery).getOutputParameterValue(0);
willReturn("z").given(targetQuery).getOutputParameterValue(2);
given(targetEm.isOpen()).willReturn(true);
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
@@ -188,24 +208,24 @@ public class SharedEntityManagerCreatorTests {
spq.getOutputParameterValue(1));
assertThat(spq.getOutputParameterValue(2)).isEqualTo("z");
verify(query).registerStoredProcedureParameter(0, String.class, ParameterMode.OUT);
verify(query).registerStoredProcedureParameter(1, Number.class, ParameterMode.IN);
verify(query).registerStoredProcedureParameter(2, Object.class, ParameterMode.INOUT);
verify(query).execute();
verify(targetQuery).registerStoredProcedureParameter(0, String.class, ParameterMode.OUT);
verify(targetQuery).registerStoredProcedureParameter(1, Number.class, ParameterMode.IN);
verify(targetQuery).registerStoredProcedureParameter(2, Object.class, ParameterMode.INOUT);
verify(targetQuery).execute();
verify(targetEm).close();
verifyNoMoreInteractions(query);
verifyNoMoreInteractions(targetQuery);
verifyNoMoreInteractions(targetEm);
}
@Test
public void deferredStoredProcedureQueryWithNamedParameters() {
void deferredStoredProcedureQueryWithNamedParameters() {
EntityManagerFactory emf = mock(EntityManagerFactory.class);
EntityManager targetEm = mock(EntityManager.class);
StoredProcedureQuery query = mock(StoredProcedureQuery.class);
StoredProcedureQuery targetQuery = mock(StoredProcedureQuery.class);
given(emf.createEntityManager()).willReturn(targetEm);
given(targetEm.createStoredProcedureQuery("x")).willReturn(query);
willReturn("y").given(query).getOutputParameterValue("a");
willReturn("z").given(query).getOutputParameterValue("c");
given(targetEm.createStoredProcedureQuery("x")).willReturn(targetQuery);
willReturn("y").given(targetQuery).getOutputParameterValue("a");
willReturn("z").given(targetQuery).getOutputParameterValue("c");
given(targetEm.isOpen()).willReturn(true);
EntityManager em = SharedEntityManagerCreator.createSharedEntityManager(emf);
@@ -219,12 +239,12 @@ public class SharedEntityManagerCreatorTests {
spq.getOutputParameterValue("b"));
assertThat(spq.getOutputParameterValue("c")).isEqualTo("z");
verify(query).registerStoredProcedureParameter("a", String.class, ParameterMode.OUT);
verify(query).registerStoredProcedureParameter("b", Number.class, ParameterMode.IN);
verify(query).registerStoredProcedureParameter("c", Object.class, ParameterMode.INOUT);
verify(query).execute();
verify(targetQuery).registerStoredProcedureParameter("a", String.class, ParameterMode.OUT);
verify(targetQuery).registerStoredProcedureParameter("b", Number.class, ParameterMode.IN);
verify(targetQuery).registerStoredProcedureParameter("c", Object.class, ParameterMode.INOUT);
verify(targetQuery).execute();
verify(targetEm).close();
verifyNoMoreInteractions(query);
verifyNoMoreInteractions(targetQuery);
verifyNoMoreInteractions(targetEm);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author 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,7 @@ public class DefaultTransactionAttribute extends DefaultTransactionDefinition im
/**
* Create a new DefaultTransactionAttribute, with default settings.
* Create a new {@code DefaultTransactionAttribute} with default settings.
* Can be modified through bean property setters.
* @see #setPropagationBehavior
* @see #setIsolationLevel
@@ -76,7 +76,7 @@ public class DefaultTransactionAttribute extends DefaultTransactionDefinition im
}
/**
* Create a new DefaultTransactionAttribute with the given
* Create a new {@code DefaultTransactionAttribute} with the given
* propagation behavior. Can be modified through bean property setters.
* @param propagationBehavior one of the propagation constants in the
* TransactionDefinition interface
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -210,11 +210,13 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran
prepareSynchronization(synchronizationManager, status, definition)).thenReturn(status);
}
// Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
// PROPAGATION_REQUIRED, PROPAGATION_SUPPORTS, PROPAGATION_MANDATORY:
// regular participation in existing transaction.
if (debugEnabled) {
logger.debug("Participating in existing transaction");
}
return Mono.just(prepareReactiveTransaction(synchronizationManager, definition, transaction, false, debugEnabled, null));
return Mono.just(prepareReactiveTransaction(
synchronizationManager, definition, transaction, false, debugEnabled, null));
}
/**
@@ -330,7 +332,7 @@ public abstract class AbstractReactiveTransactionManager implements ReactiveTran
if (resourcesHolder != null) {
Object suspendedResources = resourcesHolder.suspendedResources;
if (suspendedResources != null) {
resume = doResume(synchronizationManager, transaction, suspendedResources);
resume = doResume(synchronizationManager, transaction, suspendedResources);
}
List<TransactionSynchronization> suspendedSynchronizations = resourcesHolder.suspendedSynchronizations;
if (suspendedSynchronizations != null) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -451,7 +451,7 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
if (useSavepointForNestedTransaction()) {
// Create savepoint within existing Spring-managed transaction,
// through the SavepointManager API implemented by TransactionStatus.
// Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
// Usually uses JDBC savepoints. Never activates Spring synchronization.
DefaultTransactionStatus status =
prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
status.createAndHoldSavepoint();
@@ -465,7 +465,8 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
}
}
// Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
// PROPAGATION_REQUIRED, PROPAGATION_SUPPORTS, PROPAGATION_MANDATORY:
// regular participation in existing transaction.
if (debugEnabled) {
logger.debug("Participating in existing transaction");
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -65,7 +65,7 @@ public class DefaultTransactionDefinition implements TransactionDefinition, Seri
/**
* Create a new DefaultTransactionDefinition, with default settings.
* Create a new {@code DefaultTransactionDefinition} with default settings.
* Can be modified through bean property setters.
* @see #setPropagationBehavior
* @see #setIsolationLevel
@@ -93,7 +93,7 @@ public class DefaultTransactionDefinition implements TransactionDefinition, Seri
}
/**
* Create a new DefaultTransactionDefinition with the given
* Create a new {@code DefaultTransactionDefinition} with the given
* propagation behavior. Can be modified through bean property setters.
* @param propagationBehavior one of the propagation constants in the
* TransactionDefinition interface
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -110,7 +110,7 @@ public final class ResponseCookie extends HttpCookie {
/**
* Return {@code true} if the cookie has the "HttpOnly" attribute.
* @see <a href="https://www.owasp.org/index.php/HTTPOnly">https://www.owasp.org/index.php/HTTPOnly</a>
* @see <a href="https://owasp.org/www-community/HttpOnly">https://owasp.org/www-community/HttpOnly</a>
*/
public boolean isHttpOnly() {
return this.httpOnly;
@@ -326,7 +326,7 @@ public final class ResponseCookie extends HttpCookie {
/**
* Add the "HttpOnly" attribute to the cookie.
* @see <a href="https://www.owasp.org/index.php/HTTPOnly">https://www.owasp.org/index.php/HTTPOnly</a>
* @see <a href="https://owasp.org/www-community/HttpOnly">https://owasp.org/www-community/HttpOnly</a>
*/
ResponseCookieBuilder httpOnly(boolean httpOnly);
@@ -16,8 +16,12 @@
package org.springframework.http.client.reactive;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.core.publisher.Flux;
import org.springframework.core.io.buffer.DataBuffer;
@@ -54,16 +58,7 @@ public abstract class AbstractClientHttpResponse implements ClientHttpResponse {
this.statusCode = statusCode;
this.headers = headers;
this.cookies = cookies;
this.body = singleSubscription(body);
}
private static Flux<DataBuffer> singleSubscription(Flux<DataBuffer> body) {
AtomicBoolean subscribed = new AtomicBoolean();
return body.doOnSubscribe(s -> {
if (!subscribed.compareAndSet(false, true)) {
throw new IllegalStateException("The client response body can only be consumed once");
}
});
this.body = Flux.from(new SingleSubscriberPublisher<>(body));
}
@@ -91,4 +86,39 @@ public abstract class AbstractClientHttpResponse implements ClientHttpResponse {
public Flux<DataBuffer> getBody() {
return this.body;
}
private static final class SingleSubscriberPublisher<T> implements Publisher<T> {
private static final Subscription NO_OP_SUBSCRIPTION = new Subscription() {
@Override
public void request(long l) {
}
@Override
public void cancel() {
}
};
private final Publisher<T> delegate;
private final AtomicBoolean subscribed = new AtomicBoolean();
public SingleSubscriberPublisher(Publisher<T> delegate) {
this.delegate = delegate;
}
@Override
public void subscribe(Subscriber<? super T> subscriber) {
Objects.requireNonNull(subscriber, "Subscriber must not be null");
if (this.subscribed.compareAndSet(false, true)) {
this.delegate.subscribe(subscriber);
}
else {
subscriber.onSubscribe(NO_OP_SUBSCRIPTION);
subscriber.onError(new IllegalStateException("The client response body can only be consumed once"));
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,11 +39,10 @@ import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import static org.springframework.http.MediaType.ALL_VALUE;
/**
* {@link ClientHttpRequest} implementation for the Apache HttpComponents HttpClient 5.x.
* @author Martin Tarjányi
@@ -123,16 +122,14 @@ class HttpComponentsClientHttpRequest extends AbstractClientHttpRequest {
@Override
protected void applyHeaders() {
HttpHeaders headers = getHeaders();
headers.entrySet()
.stream()
.filter(entry -> !HttpHeaders.CONTENT_LENGTH.equals(entry.getKey()))
.forEach(entry -> entry.getValue().forEach(v -> this.httpRequest.addHeader(entry.getKey(), v)));
if (!this.httpRequest.containsHeader(HttpHeaders.ACCEPT)) {
this.httpRequest.addHeader(HttpHeaders.ACCEPT, ALL_VALUE);
this.httpRequest.addHeader(HttpHeaders.ACCEPT, MediaType.ALL_VALUE);
}
this.contentLength = headers.getContentLength();
}
@@ -143,7 +140,6 @@ class HttpComponentsClientHttpRequest extends AbstractClientHttpRequest {
}
CookieStore cookieStore = this.context.getCookieStore();
getCookies().values()
.stream()
.flatMap(Collection::stream)
@@ -81,6 +81,7 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<R
if (this.supportsReadStreaming && InputStreamResource.class == clazz) {
return new InputStreamResource(inputMessage.getBody()) {
@Override
@Nullable
public String getFilename() {
return inputMessage.getHeaders().getContentDisposition().getFilename();
}
@@ -106,6 +107,13 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<R
}
}
@Override
protected void writeInternal(Resource resource, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
writeContent(resource, outputMessage);
}
@Override
protected MediaType getDefaultContentType(Resource resource) {
return MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM);
@@ -123,15 +131,10 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<R
return (contentLength < 0 ? null : contentLength);
}
@Override
protected void writeInternal(Resource resource, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
writeContent(resource, outputMessage);
}
protected void writeContent(Resource resource, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// We cannot use try-with-resources here for the InputStream, since we have
// custom handling of the close() method in a finally-block.
try {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author 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,8 +37,8 @@ import org.springframework.util.MimeTypeUtils;
import org.springframework.util.StreamUtils;
/**
* Implementation of {@link HttpMessageConverter} that can write a single {@link ResourceRegion},
* or Collections of {@link ResourceRegion ResourceRegions}.
* Implementation of {@link HttpMessageConverter} that can write a single
* {@link ResourceRegion} or Collections of {@link ResourceRegion ResourceRegions}.
*
* @author Brian Clozel
* @author Juergen Hoeller
@@ -51,22 +51,6 @@ public class ResourceRegionHttpMessageConverter extends AbstractGenericHttpMessa
}
@Override
@SuppressWarnings("unchecked")
protected MediaType getDefaultContentType(Object object) {
Resource resource = null;
if (object instanceof ResourceRegion) {
resource = ((ResourceRegion) object).getResource();
}
else {
Collection<ResourceRegion> regions = (Collection<ResourceRegion>) object;
if (!regions.isEmpty()) {
resource = regions.iterator().next().getResource();
}
}
return MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM);
}
@Override
public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
return false;
@@ -123,7 +107,6 @@ public class ResourceRegionHttpMessageConverter extends AbstractGenericHttpMessa
}
@Override
@SuppressWarnings("unchecked")
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
@@ -131,16 +114,33 @@ public class ResourceRegionHttpMessageConverter extends AbstractGenericHttpMessa
writeResourceRegion((ResourceRegion) object, outputMessage);
}
else {
@SuppressWarnings("unchecked")
Collection<ResourceRegion> regions = (Collection<ResourceRegion>) object;
if (regions.size() == 1) {
writeResourceRegion(regions.iterator().next(), outputMessage);
}
else {
writeResourceRegionCollection((Collection<ResourceRegion>) object, outputMessage);
writeResourceRegionCollection(regions, outputMessage);
}
}
}
@Override
protected MediaType getDefaultContentType(Object object) {
Resource resource = null;
if (object instanceof ResourceRegion) {
resource = ((ResourceRegion) object).getResource();
}
else {
@SuppressWarnings("unchecked")
Collection<ResourceRegion> regions = (Collection<ResourceRegion>) object;
if (!regions.isEmpty()) {
resource = regions.iterator().next().getResource();
}
}
return MediaTypeFactory.getMediaType(resource).orElse(MediaType.APPLICATION_OCTET_STREAM);
}
protected void writeResourceRegion(ResourceRegion region, HttpOutputMessage outputMessage) throws IOException {
Assert.notNull(region, "ResourceRegion must not be null");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -93,7 +93,7 @@ public class SourceHttpMessageConverter<T extends Source> extends AbstractHttpMe
/**
* Sets the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes}
* to {@code text/xml} and {@code application/xml}, and {@code application/*-xml}.
* to {@code text/xml} and {@code application/xml}, and {@code application/*+xml}.
*/
public SourceHttpMessageConverter() {
super(MediaType.APPLICATION_XML, MediaType.TEXT_XML, new MediaType("application", "*+xml"));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -221,7 +221,7 @@ public abstract class AbstractFlashMapManager implements FlashMapManager {
@Nullable
private String decodeAndNormalizePath(@Nullable String path, HttpServletRequest request) {
if (path != null && !path.isEmpty()) {
if (StringUtils.hasLength(path)) {
path = getUrlPathHelper().decodeRequestString(request, path);
if (path.charAt(0) != '/') {
String requestUri = getUrlPathHelper().getRequestUri(request);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -248,30 +248,31 @@ public class ConcurrentWebSocketSessionDecorator extends WebSocketSessionDecorat
@Override
public void close(CloseStatus status) throws IOException {
this.closeLock.lock();
try {
if (this.closeInProgress) {
return;
}
if (!CloseStatus.SESSION_NOT_RELIABLE.equals(status)) {
try {
checkSessionLimits();
if (this.closeLock.tryLock()) {
try {
if (this.closeInProgress) {
return;
}
catch (SessionLimitExceededException ex) {
// Ignore
}
if (this.limitExceeded) {
if (logger.isDebugEnabled()) {
logger.debug("Changing close status " + status + " to SESSION_NOT_RELIABLE.");
if (!CloseStatus.SESSION_NOT_RELIABLE.equals(status)) {
try {
checkSessionLimits();
}
catch (SessionLimitExceededException ex) {
// Ignore
}
if (this.limitExceeded) {
if (logger.isDebugEnabled()) {
logger.debug("Changing close status " + status + " to SESSION_NOT_RELIABLE.");
}
status = CloseStatus.SESSION_NOT_RELIABLE;
}
status = CloseStatus.SESSION_NOT_RELIABLE;
}
this.closeInProgress = true;
super.close(status);
}
finally {
this.closeLock.unlock();
}
this.closeInProgress = true;
super.close(status);
}
finally {
this.closeLock.unlock();
}
}
+7 -29
View File
@@ -7670,35 +7670,13 @@ or reactive type such as Reactor `Mono`:
[[spring-mvc-test-vs-streaming-response]]
===== Streaming Responses
The best way to test streaming responses such as Server-Sent Events is through the
<<WebTestClient>> which can be used as a test client to connect to a `MockMvc` instance
to perform tests on Spring MVC controllers without a running server. For example:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
WebTestClient client = MockMvcWebTestClient.bindToController(new SseController()).build();
FluxExchangeResult<Person> exchangeResult = client.get()
.uri("/persons")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType("text/event-stream")
.returnResult(Person.class);
// Use StepVerifier from Project Reactor to test the streaming response
StepVerifier.create(exchangeResult.getResponseBody())
.expectNext(new Person("N0"), new Person("N1"), new Person("N2"))
.expectNextCount(4)
.consumeNextWith(person -> assertThat(person.getName()).endsWith("7"))
.thenCancel()
.verify();
----
`WebTestClient` can also connect to a live server and perform full end-to-end integration
tests. This is also supported in Spring Boot where you can
{doc-spring-boot}/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test a running server].
You can use `WebTestClient` to test <<testing/testing-webtestclient.adoc#webtestclient-stream,streaming responses>>
such as Server-Sent Events. However, `MockMvcWebTestClient` doesn't support infinite
streams because there is no way to cancel the server stream from the client side.
To test infinite streams, you'll need to
<<testing/testing-webtestclient.adoc#webtestclient-server-config,bind to>> a running server,
or when using Spring Boot,
{doc-spring-boot}/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test with a running server].
[[spring-mvc-test-server-filters]]