mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b02f4a06ca | |||
| 99ae6e70bb | |||
| c2cc55eacc | |||
| 9cff2ace97 | |||
| 3c2590d339 | |||
| 4b55333b0e | |||
| c27acad616 | |||
| 70be9afdc6 | |||
| d4450a8702 | |||
| 210e47d65e | |||
| 06411831e8 | |||
| 8c7daa807a | |||
| 46d171a8fd | |||
| 259bd5250d | |||
| cef046218c | |||
| e4bd1344e2 | |||
| 9decbf2158 | |||
| 268b7a8931 | |||
| 1240fb6b9a | |||
| 7dae3afb4b | |||
| d9de36b5ee | |||
| 4e696db922 | |||
| 0adad10595 | |||
| 540d0d9345 | |||
| 572bbeeba3 | |||
| 77cd44dd20 | |||
| 65e7b4a279 | |||
| 7d95a24573 | |||
| 1dbe0ee6db | |||
| 0211016957 | |||
| 08fe123930 | |||
| 219448796f | |||
| 0956c144c9 | |||
| 964950a8b9 | |||
| 5afd94f90f | |||
| ec5f7644e4 | |||
| 0c8ef4c671 | |||
| a851b7309a | |||
| 6bfb94a563 | |||
| ebc8265428 | |||
| 86457464d7 | |||
| be129dc171 | |||
| 8bb1b3eb44 | |||
| 6abd822e77 | |||
| 1c43a4c7ab | |||
| 423f2215c2 | |||
| 0bad69d5fb | |||
| 6b19642256 | |||
| 19bb4e96f2 | |||
| 7a2594acda | |||
| 91c58af7af | |||
| 0a1aeafe08 | |||
| 0e69bac7b0 | |||
| 0cfbf6036c | |||
| e931fdc5c2 | |||
| 3a1681357c | |||
| a93382dbbf | |||
| 79c5ef88f5 | |||
| 0c80e5f9e6 | |||
| 7ad01a94d6 | |||
| 5d6d653cbd | |||
| 9868e888a5 | |||
| 6f6eb3d996 | |||
| a94c50e294 | |||
| b8c1255ee0 | |||
| 129062034b |
+3
-1
@@ -46,4 +46,6 @@ atlassian-ide-plugin.xml
|
||||
.gradletasknamecache
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
.vscode/
|
||||
|
||||
cached-antora-playbook.yml
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=8.0.362-librca
|
||||
java=8.0.372-librca
|
||||
|
||||
+7
-7
@@ -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.90.Final"
|
||||
mavenBom "io.projectreactor:reactor-bom:2020.0.30"
|
||||
mavenBom "io.netty:netty-bom:4.1.93.Final"
|
||||
mavenBom "io.projectreactor:reactor-bom:2020.0.33"
|
||||
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR13"
|
||||
mavenBom "io.rsocket:rsocket-bom:1.1.3"
|
||||
mavenBom "org.eclipse.jetty:jetty-bom:9.4.51.v20230217"
|
||||
@@ -128,18 +128,18 @@ configure(allprojects) { project ->
|
||||
dependency "org.webjars:webjars-locator-core:0.48"
|
||||
dependency "org.webjars:underscorejs:1.8.3"
|
||||
|
||||
dependencySet(group: 'org.apache.tomcat', version: '9.0.73') {
|
||||
dependencySet(group: 'org.apache.tomcat', version: '9.0.75') {
|
||||
entry 'tomcat-util'
|
||||
entry('tomcat-websocket') {
|
||||
exclude group: "org.apache.tomcat", name: "tomcat-servlet-api"
|
||||
exclude group: "org.apache.tomcat", name: "tomcat-websocket-api"
|
||||
}
|
||||
}
|
||||
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.73') {
|
||||
dependencySet(group: 'org.apache.tomcat.embed', version: '9.0.75') {
|
||||
entry 'tomcat-embed-core'
|
||||
entry 'tomcat-embed-websocket'
|
||||
}
|
||||
dependencySet(group: 'io.undertow', version: '2.2.23.Final') {
|
||||
dependencySet(group: 'io.undertow', version: '2.2.24.Final') {
|
||||
entry 'undertow-core'
|
||||
entry('undertow-servlet') {
|
||||
exclude group: "org.jboss.spec.javax.servlet", name: "jboss-servlet-api_4.0_spec"
|
||||
@@ -238,7 +238,7 @@ configure(allprojects) { project ->
|
||||
dependency "com.ibm.websphere:uow:6.0.2.17"
|
||||
dependency "com.jamonapi:jamon:2.82"
|
||||
dependency "joda-time:joda-time:2.10.13"
|
||||
dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.10"
|
||||
dependency "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.12"
|
||||
dependency "org.javamoney:moneta:1.3"
|
||||
|
||||
dependency "com.sun.activation:javax.activation:1.2.0"
|
||||
@@ -340,7 +340,7 @@ configure([rootProject] + javaProjects) { project ->
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion = "10.9.1"
|
||||
toolVersion = "10.9.3"
|
||||
configDirectory.set(rootProject.file("src/checkstyle"))
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@ set -e
|
||||
|
||||
case "$1" in
|
||||
java8)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/8u345+1/bellsoft-jdk8u345+1-linux-amd64.tar.gz"
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/8u372+7/bellsoft-jdk8u372+7-linux-amd64.tar.gz"
|
||||
;;
|
||||
java17)
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.4+8/bellsoft-jdk17.0.4+8-linux-amd64.tar.gz"
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/17.0.7+7/bellsoft-jdk17.0.7+7-linux-amd64.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo $"Unknown java version"
|
||||
|
||||
@@ -64,12 +64,6 @@ resource_types:
|
||||
<<: *docker-resource-source
|
||||
repository: dpb587/github-status-resource
|
||||
tag: master
|
||||
- name: pull-request
|
||||
type: registry-image
|
||||
source:
|
||||
<<: *docker-resource-source
|
||||
repository: teliaoss/github-pr-resource
|
||||
tag: v0.23.0
|
||||
- name: slack-notification
|
||||
type: registry-image
|
||||
source:
|
||||
@@ -111,14 +105,6 @@ resources:
|
||||
username: ((artifactory-username))
|
||||
password: ((artifactory-password))
|
||||
build_name: ((build-name))
|
||||
- name: git-pull-request
|
||||
type: pull-request
|
||||
icon: source-pull
|
||||
source:
|
||||
access_token: ((github-ci-pull-request-token))
|
||||
repository: ((github-repo-name))
|
||||
base_branch: ((branch))
|
||||
ignore_paths: ["ci/*"]
|
||||
- name: repo-status-build
|
||||
type: github-status-resource
|
||||
icon: eye-check-outline
|
||||
@@ -259,37 +245,6 @@ jobs:
|
||||
<<: *slack-fail-params
|
||||
- put: repo-status-jdk17-build
|
||||
params: { state: "success", commit: "git-repo" }
|
||||
- name: build-pull-requests
|
||||
serial: true
|
||||
public: true
|
||||
plan:
|
||||
- get: ci-image
|
||||
- get: git-repo
|
||||
resource: git-pull-request
|
||||
trigger: true
|
||||
version: every
|
||||
- do:
|
||||
- put: git-pull-request
|
||||
params:
|
||||
path: git-repo
|
||||
status: pending
|
||||
- task: build-pr
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/build-pr.yml
|
||||
privileged: true
|
||||
timeout: ((task-timeout))
|
||||
params:
|
||||
BRANCH: ((branch))
|
||||
on_success:
|
||||
put: git-pull-request
|
||||
params:
|
||||
path: git-repo
|
||||
status: success
|
||||
on_failure:
|
||||
put: git-pull-request
|
||||
params:
|
||||
path: git-repo
|
||||
status: failure
|
||||
- name: stage-milestone
|
||||
serial: true
|
||||
plan:
|
||||
@@ -442,5 +397,3 @@ groups:
|
||||
jobs: ["stage-milestone", "stage-rc", "stage-release", "promote-milestone", "promote-rc", "promote-release", "create-github-release"]
|
||||
- name: "ci-images"
|
||||
jobs: ["build-ci-images"]
|
||||
- name: "pull-requests"
|
||||
jobs: [ "build-pull-requests" ]
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=5.3.26-SNAPSHOT
|
||||
version=5.3.28
|
||||
org.gradle.jvmargs=-Xmx2048m
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
|
||||
+3
-4
@@ -3,7 +3,7 @@ configurations {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
asciidoctorExt("io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.5.0")
|
||||
asciidoctorExt("io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.6.1")
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -73,9 +73,8 @@ pluginManager.withPlugin("kotlin") {
|
||||
}
|
||||
|
||||
task downloadResources(type: Download) {
|
||||
def version = "0.2.5"
|
||||
src "https://repo.spring.io/release/io/spring/docresources/" +
|
||||
"spring-doc-resources/$version/spring-doc-resources-${version}.zip"
|
||||
src "https://repo.spring.io/artifactory/snapshot/io/spring/docresources/" +
|
||||
"spring-doc-resources/0.2.6-SNAPSHOT/spring-doc-resources-0.2.6-20210308.231804-2.zip"
|
||||
dest project.file("$buildDir/docs/spring-doc-resources.zip")
|
||||
onlyIfModified true
|
||||
useETag "all"
|
||||
|
||||
+26
-29
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -38,6 +38,14 @@ import org.springframework.util.StringUtils;
|
||||
* for an advice method from the pointcut expression, returning, and throwing clauses.
|
||||
* If an unambiguous interpretation is not available, it returns {@code null}.
|
||||
*
|
||||
* <h3>Algorithm Summary</h3>
|
||||
* <p>If an unambiguous binding can be deduced, then it is.
|
||||
* If the advice requirements cannot possibly be satisfied, then {@code null}
|
||||
* is returned. By setting the {@link #setRaiseExceptions(boolean) raiseExceptions}
|
||||
* property to {@code true}, descriptive exceptions will be thrown instead of
|
||||
* returning {@code null} in the case that the parameter names cannot be discovered.
|
||||
*
|
||||
* <h3>Algorithm Details</h3>
|
||||
* <p>This class interprets arguments in the following way:
|
||||
* <ol>
|
||||
* <li>If the first parameter of the method is of type {@link JoinPoint}
|
||||
@@ -65,15 +73,15 @@ import org.springframework.util.StringUtils;
|
||||
* zero we proceed to the next stage. If {@code a} > 1 then an
|
||||
* {@code AmbiguousBindingException} is raised. If {@code a} == 1,
|
||||
* and there are no unbound arguments of type {@code Annotation+},
|
||||
* then an {@code IllegalArgumentException} is raised. if there is
|
||||
* then an {@code IllegalArgumentException} is raised. If there is
|
||||
* exactly one such argument, then the corresponding parameter name is
|
||||
* assigned the value from the pointcut expression.</li>
|
||||
* <li>If a returningName has been set, and there are no unbound arguments
|
||||
* <li>If a {@code returningName} has been set, and there are no unbound arguments
|
||||
* then an {@code IllegalArgumentException} is raised. If there is
|
||||
* more than one unbound argument then an
|
||||
* {@code AmbiguousBindingException} is raised. If there is exactly
|
||||
* one unbound argument then the corresponding parameter name is assigned
|
||||
* the value <returningName>.</li>
|
||||
* the value of the {@code returningName}.</li>
|
||||
* <li>If there remain unbound arguments, then the pointcut expression is
|
||||
* examined once more for {@code this}, {@code target}, and
|
||||
* {@code args} pointcut expressions used in the binding form (binding
|
||||
@@ -99,20 +107,12 @@ import org.springframework.util.StringUtils;
|
||||
* <p>The behavior on raising an {@code IllegalArgumentException} or
|
||||
* {@code AmbiguousBindingException} is configurable to allow this discoverer
|
||||
* to be used as part of a chain-of-responsibility. By default the condition will
|
||||
* be logged and the {@code getParameterNames(..)} method will simply return
|
||||
* be logged and the {@link #getParameterNames(Method)} method will simply return
|
||||
* {@code null}. If the {@link #setRaiseExceptions(boolean) raiseExceptions}
|
||||
* property is set to {@code true}, the conditions will be thrown as
|
||||
* {@code IllegalArgumentException} and {@code AmbiguousBindingException},
|
||||
* respectively.
|
||||
*
|
||||
* <p>Was that perfectly clear? ;)
|
||||
*
|
||||
* <p>Short version: If an unambiguous binding can be deduced, then it is.
|
||||
* If the advice requirements cannot possibly be satisfied, then {@code null}
|
||||
* is returned. By setting the {@link #setRaiseExceptions(boolean) raiseExceptions}
|
||||
* property to {@code true}, descriptive exceptions will be thrown instead of
|
||||
* returning {@code null} in the case that the parameter names cannot be discovered.
|
||||
*
|
||||
* @author Adrian Colyer
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.0
|
||||
@@ -158,7 +158,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
|
||||
/** The pointcut expression associated with the advice, as a simple String. */
|
||||
@Nullable
|
||||
private String pointcutExpression;
|
||||
private final String pointcutExpression;
|
||||
|
||||
private boolean raiseExceptions;
|
||||
|
||||
@@ -197,7 +197,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
|
||||
/**
|
||||
* If {@code afterReturning} advice binds the return value, the
|
||||
* returning variable name must be specified.
|
||||
* {@code returning} variable name must be specified.
|
||||
* @param returningName the name of the returning variable
|
||||
*/
|
||||
public void setReturningName(@Nullable String returningName) {
|
||||
@@ -206,18 +206,17 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
|
||||
/**
|
||||
* If {@code afterThrowing} advice binds the thrown value, the
|
||||
* throwing variable name must be specified.
|
||||
* {@code throwing} variable name must be specified.
|
||||
* @param throwingName the name of the throwing variable
|
||||
*/
|
||||
public void setThrowingName(@Nullable String throwingName) {
|
||||
this.throwingName = throwingName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deduce the parameter names for an advice method.
|
||||
* <p>See the {@link AspectJAdviceParameterNameDiscoverer class level javadoc}
|
||||
* for this class for details of the algorithm used.
|
||||
* <p>See the {@link AspectJAdviceParameterNameDiscoverer class-level javadoc}
|
||||
* for this class for details on the algorithm used.
|
||||
* @param method the target {@link Method}
|
||||
* @return the parameter names
|
||||
*/
|
||||
@@ -316,13 +315,13 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
}
|
||||
|
||||
|
||||
private void bindParameterName(int index, String name) {
|
||||
private void bindParameterName(int index, @Nullable String name) {
|
||||
this.parameterNameBindings[index] = name;
|
||||
this.numberOfRemainingUnboundArguments--;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the first parameter is of type JoinPoint or ProceedingJoinPoint,bind "thisJoinPoint" as
|
||||
* If the first parameter is of type JoinPoint or ProceedingJoinPoint, bind "thisJoinPoint" as
|
||||
* parameter name and return true, else return false.
|
||||
*/
|
||||
private boolean maybeBindThisJoinPoint() {
|
||||
@@ -367,8 +366,8 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
}
|
||||
|
||||
if (throwableIndex == -1) {
|
||||
throw new IllegalStateException("Binding of throwing parameter '" + this.throwingName
|
||||
+ "' could not be completed as no available arguments are a subtype of Throwable");
|
||||
throw new IllegalStateException("Binding of throwing parameter '" + this.throwingName +
|
||||
"' could not be completed as no available arguments are a subtype of Throwable");
|
||||
}
|
||||
else {
|
||||
bindParameterName(throwableIndex, this.throwingName);
|
||||
@@ -400,7 +399,6 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse the string pointcut expression looking for:
|
||||
* @this, @target, @args, @within, @withincode, @annotation.
|
||||
@@ -465,7 +463,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* If the token starts meets Java identifier conventions, it's in.
|
||||
*/
|
||||
@Nullable
|
||||
@@ -533,7 +531,6 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (varNames.size() > 1) {
|
||||
throw new AmbiguousBindingException("Found " + varNames.size() +
|
||||
" candidate this(), target() or args() variables but only one unbound argument slot");
|
||||
@@ -609,7 +606,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
// else varNames.size must be 0 and we have nothing to bind.
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* We've found the start of a binding pointcut at the given index into the
|
||||
* token array. Now we need to extract the pointcut body and return it.
|
||||
*/
|
||||
@@ -709,7 +706,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Return {@code true} if the given argument type is a subclass
|
||||
* of the given supertype.
|
||||
*/
|
||||
@@ -737,7 +734,7 @@ public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscov
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Find the argument index with the given type, and bind the given
|
||||
* {@code varName} in that position.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -521,6 +521,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
|
||||
copy.copyFrom(this);
|
||||
copy.targetSource = EmptyTargetSource.forClass(getTargetClass(), getTargetSource().isStatic());
|
||||
copy.advisorChainFactory = this.advisorChainFactory;
|
||||
copy.methodCache = this.methodCache;
|
||||
copy.interfaces = new ArrayList<>(this.interfaces);
|
||||
copy.advisors = new ArrayList<>(this.advisors);
|
||||
return copy;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -283,8 +283,8 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
|
||||
private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
|
||||
// Parameters used for optimization choices...
|
||||
boolean exposeProxy = this.advised.isExposeProxy();
|
||||
boolean isFrozen = this.advised.isFrozen();
|
||||
boolean exposeProxy = this.advised.isExposeProxy();
|
||||
boolean isStatic = this.advised.getTargetSource().isStatic();
|
||||
|
||||
// Choose an "aop" interceptor (used for AOP calls).
|
||||
@@ -899,9 +899,9 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
// Proxy is not yet available, but that shouldn't matter.
|
||||
List<?> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
|
||||
boolean haveAdvice = !chain.isEmpty();
|
||||
boolean isFrozen = this.advised.isFrozen();
|
||||
boolean exposeProxy = this.advised.isExposeProxy();
|
||||
boolean isStatic = this.advised.getTargetSource().isStatic();
|
||||
boolean isFrozen = this.advised.isFrozen();
|
||||
if (haveAdvice || !isFrozen) {
|
||||
// If exposing the proxy, then AOP_PROXY must be used.
|
||||
if (exposeProxy) {
|
||||
@@ -970,6 +970,9 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
if (this.advised.isExposeProxy() != otherAdvised.isExposeProxy()) {
|
||||
return false;
|
||||
}
|
||||
if (this.advised.isOpaque() != otherAdvised.isOpaque()) {
|
||||
return false;
|
||||
}
|
||||
if (this.advised.getTargetSource().isStatic() != otherAdvised.getTargetSource().isStatic()) {
|
||||
return false;
|
||||
}
|
||||
@@ -1016,10 +1019,6 @@ class CglibAopProxy implements AopProxy, Serializable {
|
||||
Advice advice = advisor.getAdvice();
|
||||
hashCode = 13 * hashCode + advice.getClass().hashCode();
|
||||
}
|
||||
hashCode = 13 * hashCode + (this.advised.isFrozen() ? 1 : 0);
|
||||
hashCode = 13 * hashCode + (this.advised.isExposeProxy() ? 1 : 0);
|
||||
hashCode = 13 * hashCode + (this.advised.isOptimize() ? 1 : 0);
|
||||
hashCode = 13 * hashCode + (this.advised.isOpaque() ? 1 : 0);
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -123,7 +123,33 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
|
||||
}
|
||||
return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
|
||||
return Proxy.newProxyInstance(determineClassLoader(classLoader), this.proxiedInterfaces, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the JDK bootstrap or platform loader has been suggested ->
|
||||
* use higher-level loader which can see Spring infrastructure classes instead.
|
||||
*/
|
||||
private ClassLoader determineClassLoader(@Nullable ClassLoader classLoader) {
|
||||
if (classLoader == null) {
|
||||
// JDK bootstrap loader -> use spring-aop ClassLoader instead.
|
||||
return getClass().getClassLoader();
|
||||
}
|
||||
if (classLoader.getParent() == null) {
|
||||
// Potentially the JDK platform loader on JDK 9+
|
||||
ClassLoader aopClassLoader = getClass().getClassLoader();
|
||||
ClassLoader aopParent = aopClassLoader.getParent();
|
||||
while (aopParent != null) {
|
||||
if (classLoader == aopParent) {
|
||||
// Suggested ClassLoader is ancestor of spring-aop ClassLoader
|
||||
// -> use spring-aop ClassLoader itself instead.
|
||||
return aopClassLoader;
|
||||
}
|
||||
aopParent = aopParent.getParent();
|
||||
}
|
||||
}
|
||||
// Regular case: use suggested ClassLoader as-is.
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+9
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -34,6 +34,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Convenient superclass for
|
||||
@@ -81,6 +82,11 @@ public abstract class AbstractBeanFactoryBasedTargetSourceCreator
|
||||
return this.beanFactory;
|
||||
}
|
||||
|
||||
private ConfigurableBeanFactory getConfigurableBeanFactory() {
|
||||
Assert.state(this.beanFactory != null, "BeanFactory not set");
|
||||
return this.beanFactory;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Implementation of the TargetSourceCreator interface
|
||||
@@ -104,7 +110,7 @@ public abstract class AbstractBeanFactoryBasedTargetSourceCreator
|
||||
// We need to override just this bean definition, as it may reference other beans
|
||||
// and we're happy to take the parent's definition for those.
|
||||
// Always use prototype scope if demanded.
|
||||
BeanDefinition bd = this.beanFactory.getMergedBeanDefinition(beanName);
|
||||
BeanDefinition bd = getConfigurableBeanFactory().getMergedBeanDefinition(beanName);
|
||||
GenericBeanDefinition bdCopy = new GenericBeanDefinition(bd);
|
||||
if (isPrototypeBased()) {
|
||||
bdCopy.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
@@ -126,7 +132,7 @@ public abstract class AbstractBeanFactoryBasedTargetSourceCreator
|
||||
protected DefaultListableBeanFactory getInternalBeanFactoryForBean(String beanName) {
|
||||
synchronized (this.internalBeanFactories) {
|
||||
return this.internalBeanFactories.computeIfAbsent(beanName,
|
||||
name -> buildInternalBeanFactory(this.beanFactory));
|
||||
name -> buildInternalBeanFactory(getConfigurableBeanFactory()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,6 +24,8 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.aop.TargetSource;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
@@ -57,15 +59,18 @@ public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSour
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
/** Name of the target bean we will create on each invocation. */
|
||||
@Nullable
|
||||
private String targetBeanName;
|
||||
|
||||
/** Class of the target. */
|
||||
@Nullable
|
||||
private volatile Class<?> targetClass;
|
||||
|
||||
/**
|
||||
* BeanFactory that owns this TargetSource. We need to hold onto this
|
||||
* reference so that we can create new prototype instances as necessary.
|
||||
*/
|
||||
@Nullable
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
|
||||
@@ -86,6 +91,7 @@ public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSour
|
||||
* Return the name of the target bean in the factory.
|
||||
*/
|
||||
public String getTargetBeanName() {
|
||||
Assert.state(this.targetBeanName != null, "Target bean name not set");
|
||||
return this.targetBeanName;
|
||||
}
|
||||
|
||||
@@ -115,11 +121,13 @@ public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSour
|
||||
* Return the owning BeanFactory.
|
||||
*/
|
||||
public BeanFactory getBeanFactory() {
|
||||
Assert.state(this.beanFactory != null, "BeanFactory not set");
|
||||
return this.beanFactory;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Class<?> getTargetClass() {
|
||||
Class<?> targetClass = this.targetClass;
|
||||
if (targetClass != null) {
|
||||
@@ -128,7 +136,7 @@ public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSour
|
||||
synchronized (this) {
|
||||
// Full check within synchronization, entering the BeanFactory interaction algorithm only once...
|
||||
targetClass = this.targetClass;
|
||||
if (targetClass == null && this.beanFactory != null) {
|
||||
if (targetClass == null && this.beanFactory != null && this.targetBeanName != null) {
|
||||
// Determine type of the target bean.
|
||||
targetClass = this.beanFactory.getType(this.targetBeanName);
|
||||
if (targetClass == null) {
|
||||
@@ -182,18 +190,16 @@ public abstract class AbstractBeanFactoryBasedTargetSource implements TargetSour
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hashCode = getClass().hashCode();
|
||||
hashCode = 13 * hashCode + ObjectUtils.nullSafeHashCode(this.beanFactory);
|
||||
hashCode = 13 * hashCode + ObjectUtils.nullSafeHashCode(this.targetBeanName);
|
||||
return hashCode;
|
||||
return getClass().hashCode() * 13 + ObjectUtils.nullSafeHashCode(this.targetBeanName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder(getClass().getSimpleName());
|
||||
sb.append(" for target bean '").append(this.targetBeanName).append('\'');
|
||||
if (this.targetClass != null) {
|
||||
sb.append(" of type [").append(this.targetClass.getName()).append(']');
|
||||
Class<?> targetClass = this.targetClass;
|
||||
if (targetClass != null) {
|
||||
sb.append(" of type [").append(targetClass.getName()).append(']');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -46,6 +46,7 @@ public abstract class AbstractLazyCreationTargetSource implements TargetSource {
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
/** The lazily initialized target object. */
|
||||
@Nullable
|
||||
private Object lazyTarget;
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -70,6 +70,7 @@ public final class EmptyTargetSource implements TargetSource, Serializable {
|
||||
// Instance implementation
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
@Nullable
|
||||
private final Class<?> targetClass;
|
||||
|
||||
private final boolean isStatic;
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -95,8 +95,7 @@ public class HotSwappableTargetSource implements TargetSource, Serializable {
|
||||
|
||||
|
||||
/**
|
||||
* Two HotSwappableTargetSources are equal if the current target
|
||||
* objects are equal.
|
||||
* Two HotSwappableTargetSources are equal if the current target objects are equal.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -82,14 +82,8 @@ public class SingletonTargetSource implements TargetSource, Serializable {
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof SingletonTargetSource)) {
|
||||
return false;
|
||||
}
|
||||
SingletonTargetSource otherTargetSource = (SingletonTargetSource) other;
|
||||
return this.target.equals(otherTargetSource.target);
|
||||
return (this == other || (other instanceof SingletonTargetSource &&
|
||||
this.target.equals(((SingletonTargetSource) other).target)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+7
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -58,7 +58,12 @@ public class ThreadLocalTargetSource extends AbstractPrototypeBasedTargetSource
|
||||
* is meant to be per thread per instance of the ThreadLocalTargetSource class.
|
||||
*/
|
||||
private final ThreadLocal<Object> targetInThread =
|
||||
new NamedThreadLocal<>("Thread-local instance of bean '" + getTargetBeanName() + "'");
|
||||
new NamedThreadLocal<Object>("Thread-local instance of bean") {
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " '" + getTargetBeanName() + "'";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set of managed targets, enabling us to keep track of the targets we've created.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
package org.springframework.aop.framework;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Savepoint;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import javax.accessibility.Accessible;
|
||||
@@ -380,6 +383,43 @@ public class ProxyFactoryTests {
|
||||
assertThat(proxy.getName()).isEqualTo("tb");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCharSequenceProxy() {
|
||||
CharSequence target = "test";
|
||||
ProxyFactory pf = new ProxyFactory(target);
|
||||
ClassLoader cl = target.getClass().getClassLoader();
|
||||
CharSequence proxy = (CharSequence) pf.getProxy(cl);
|
||||
assertThat(proxy.toString()).isEqualTo(target);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDateProxy() {
|
||||
Date target = new Date();
|
||||
ProxyFactory pf = new ProxyFactory(target);
|
||||
pf.setProxyTargetClass(true);
|
||||
ClassLoader cl = target.getClass().getClassLoader();
|
||||
Date proxy = (Date) pf.getProxy(cl);
|
||||
assertThat(proxy.getTime()).isEqualTo(target.getTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJdbcSavepointProxy() throws SQLException {
|
||||
Savepoint target = new Savepoint() {
|
||||
@Override
|
||||
public int getSavepointId() throws SQLException {
|
||||
return 1;
|
||||
}
|
||||
@Override
|
||||
public String getSavepointName() throws SQLException {
|
||||
return "sp";
|
||||
}
|
||||
};
|
||||
ProxyFactory pf = new ProxyFactory(target);
|
||||
ClassLoader cl = Savepoint.class.getClassLoader();
|
||||
Savepoint proxy = (Savepoint) pf.getProxy(cl);
|
||||
assertThat(proxy.getSavepointName()).isEqualTo("sp");
|
||||
}
|
||||
|
||||
|
||||
@Order(2)
|
||||
public static class A implements Runnable {
|
||||
@@ -391,7 +431,7 @@ public class ProxyFactoryTests {
|
||||
|
||||
|
||||
@Order(1)
|
||||
public static class B implements Runnable{
|
||||
public static class B implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -136,7 +136,10 @@ public interface BeanFactory {
|
||||
* <p>Translates aliases back to the corresponding canonical bean name.
|
||||
* <p>Will ask the parent factory if the bean cannot be found in this factory instance.
|
||||
* @param name the name of the bean to retrieve
|
||||
* @return an instance of the bean
|
||||
* @return an instance of the bean.
|
||||
* Note that the return value will never be {@code null} but possibly a stub for
|
||||
* {@code null} returned from a factory method, to be checked via {@code equals(null)}.
|
||||
* Consider using {@link #getBeanProvider(Class)} for resolving optional dependencies.
|
||||
* @throws NoSuchBeanDefinitionException if there is no bean with the specified name
|
||||
* @throws BeansException if the bean could not be obtained
|
||||
*/
|
||||
@@ -152,7 +155,11 @@ public interface BeanFactory {
|
||||
* <p>Will ask the parent factory if the bean cannot be found in this factory instance.
|
||||
* @param name the name of the bean to retrieve
|
||||
* @param requiredType type the bean must match; can be an interface or superclass
|
||||
* @return an instance of the bean
|
||||
* @return an instance of the bean.
|
||||
* Note that the return value will never be {@code null}. In case of a stub for
|
||||
* {@code null} from a factory method having been resolved for the requested bean, a
|
||||
* {@code BeanNotOfRequiredTypeException} against the NullBean stub will be raised.
|
||||
* Consider using {@link #getBeanProvider(Class)} for resolving optional dependencies.
|
||||
* @throws NoSuchBeanDefinitionException if there is no such bean definition
|
||||
* @throws BeanNotOfRequiredTypeException if the bean is not of the required type
|
||||
* @throws BeansException if the bean could not be created
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -665,7 +665,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
if (value != null || this.required) {
|
||||
cachedFieldValue = desc;
|
||||
registerDependentBeans(beanName, autowiredBeanNames);
|
||||
if (autowiredBeanNames.size() == 1) {
|
||||
if (value != null && autowiredBeanNames.size() == 1) {
|
||||
String autowiredBeanName = autowiredBeanNames.iterator().next();
|
||||
if (beanFactory.containsBean(autowiredBeanName) &&
|
||||
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
|
||||
@@ -778,7 +778,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
Class<?>[] paramTypes = method.getParameterTypes();
|
||||
for (int i = 0; i < paramTypes.length; i++) {
|
||||
String autowiredBeanName = it.next();
|
||||
if (beanFactory.containsBean(autowiredBeanName) &&
|
||||
if (arguments[i] != null && beanFactory.containsBean(autowiredBeanName) &&
|
||||
beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
|
||||
cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
|
||||
descriptors[i], autowiredBeanName, paramTypes[i]);
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -102,7 +102,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
private boolean singletonsCurrentlyInDestruction = false;
|
||||
|
||||
/** Disposable bean instances: bean name to disposable instance. */
|
||||
private final Map<String, Object> disposableBeans = new LinkedHashMap<>();
|
||||
private final Map<String, DisposableBean> disposableBeans = new LinkedHashMap<>();
|
||||
|
||||
/** Map between containing bean names: bean name to Set of bean names that the bean contains. */
|
||||
private final Map<String, Set<String>> containedBeanMap = new ConcurrentHashMap<>(16);
|
||||
@@ -340,7 +340,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
* (within the entire factory).
|
||||
* @param beanName the name of the bean
|
||||
*/
|
||||
public boolean isSingletonCurrentlyInCreation(String beanName) {
|
||||
public boolean isSingletonCurrentlyInCreation(@Nullable String beanName) {
|
||||
return this.singletonsCurrentlyInCreation.contains(beanName);
|
||||
}
|
||||
|
||||
@@ -554,7 +554,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
|
||||
// Destroy the corresponding DisposableBean instance.
|
||||
DisposableBean disposableBean;
|
||||
synchronized (this.disposableBeans) {
|
||||
disposableBean = (DisposableBean) this.disposableBeans.remove(beanName);
|
||||
disposableBean = this.disposableBeans.remove(beanName);
|
||||
}
|
||||
destroyBean(beanName, disposableBean);
|
||||
}
|
||||
|
||||
+1
-1
@@ -68,8 +68,8 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
|
||||
|
||||
private static final String SHUTDOWN_METHOD_NAME = "shutdown";
|
||||
|
||||
private static final Log logger = LogFactory.getLog(DisposableBeanAdapter.class);
|
||||
|
||||
private static final Log logger = LogFactory.getLog(DisposableBeanAdapter.class);
|
||||
|
||||
private final Object bean;
|
||||
|
||||
|
||||
+21
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -133,6 +133,26 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
||||
assertThat(bean.getTestBean2()).isSameAs(tb);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResourceInjectionWithNullBean() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(NonPublicResourceInjectionBean.class);
|
||||
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("annotatedBean", bd);
|
||||
RootBeanDefinition tb = new RootBeanDefinition(NullFactoryMethods.class);
|
||||
tb.setFactoryMethodName("createTestBean");
|
||||
bf.registerBeanDefinition("testBean", tb);
|
||||
|
||||
NonPublicResourceInjectionBean bean = (NonPublicResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isNull();
|
||||
assertThat(bean.getTestBean2()).isNull();
|
||||
assertThat(bean.getTestBean3()).isNull();
|
||||
|
||||
bean = (NonPublicResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertThat(bean.getTestBean()).isNull();
|
||||
assertThat(bean.getTestBean2()).isNull();
|
||||
assertThat(bean.getTestBean3()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtendedResourceInjection() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(TypedExtendedResourceInjectionBean.class);
|
||||
|
||||
+5
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -47,7 +47,7 @@ public class NameMatchCacheOperationSource implements CacheOperationSource, Seri
|
||||
|
||||
|
||||
/** Keys are method names; values are TransactionAttributes. */
|
||||
private Map<String, Collection<CacheOperation>> nameMap = new LinkedHashMap<>();
|
||||
private final Map<String, Collection<CacheOperation>> nameMap = new LinkedHashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
@@ -117,8 +117,8 @@ public class NameMatchCacheOperationSource implements CacheOperationSource, Seri
|
||||
if (!(other instanceof NameMatchCacheOperationSource)) {
|
||||
return false;
|
||||
}
|
||||
NameMatchCacheOperationSource otherTas = (NameMatchCacheOperationSource) other;
|
||||
return ObjectUtils.nullSafeEquals(this.nameMap, otherTas.nameMap);
|
||||
NameMatchCacheOperationSource otherCos = (NameMatchCacheOperationSource) other;
|
||||
return ObjectUtils.nullSafeEquals(this.nameMap, otherCos.nameMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,4 +130,5 @@ public class NameMatchCacheOperationSource implements CacheOperationSource, Seri
|
||||
public String toString() {
|
||||
return getClass().getName() + ": " + this.nameMap;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-1
@@ -274,7 +274,7 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
|
||||
handleAsyncError(ex);
|
||||
}
|
||||
else if (event != null) {
|
||||
publishEvent(event);
|
||||
publishEvents(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -468,6 +468,9 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inner class to avoid a hard dependency on the Reactive Streams API at runtime.
|
||||
*/
|
||||
private class ReactiveResultHandler {
|
||||
|
||||
public boolean subscribeToPublisher(Object result) {
|
||||
@@ -481,6 +484,9 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reactive Streams Subscriber for publishing follow-up events.
|
||||
*/
|
||||
private class EventPublicationSubscriber implements Subscriber<Object> {
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -447,7 +447,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
|
||||
+10
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -117,9 +117,12 @@ public @interface Scheduled {
|
||||
* last invocation and the start of the next.
|
||||
* <p>The time unit is milliseconds by default but can be overridden via
|
||||
* {@link #timeUnit}.
|
||||
* <p>This attribute variant supports Spring-style "${...}" placeholders
|
||||
* as well as SpEL expressions.
|
||||
* @return the delay as a String value — for example, a placeholder
|
||||
* or a {@link java.time.Duration#parse java.time.Duration} compliant value
|
||||
* @since 3.2.2
|
||||
* @see #fixedDelay()
|
||||
*/
|
||||
String fixedDelayString() default "";
|
||||
|
||||
@@ -135,9 +138,12 @@ public @interface Scheduled {
|
||||
* Execute the annotated method with a fixed period between invocations.
|
||||
* <p>The time unit is milliseconds by default but can be overridden via
|
||||
* {@link #timeUnit}.
|
||||
* <p>This attribute variant supports Spring-style "${...}" placeholders
|
||||
* as well as SpEL expressions.
|
||||
* @return the period as a String value — for example, a placeholder
|
||||
* or a {@link java.time.Duration#parse java.time.Duration} compliant value
|
||||
* @since 3.2.2
|
||||
* @see #fixedRate()
|
||||
*/
|
||||
String fixedRateString() default "";
|
||||
|
||||
@@ -156,9 +162,12 @@ public @interface Scheduled {
|
||||
* {@link #fixedRate} or {@link #fixedDelay} task.
|
||||
* <p>The time unit is milliseconds by default but can be overridden via
|
||||
* {@link #timeUnit}.
|
||||
* <p>This attribute variant supports Spring-style "${...}" placeholders
|
||||
* as well as SpEL expressions.
|
||||
* @return the initial delay as a String value — for example, a placeholder
|
||||
* or a {@link java.time.Duration#parse java.time.Duration} compliant value
|
||||
* @since 3.2.2
|
||||
* @see #initialDelay()
|
||||
*/
|
||||
String initialDelayString() default "";
|
||||
|
||||
|
||||
+11
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -83,6 +83,9 @@ public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, Sche
|
||||
|
||||
private TaskExecutorAdapter adaptedExecutor;
|
||||
|
||||
@Nullable
|
||||
private TaskDecorator taskDecorator;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new ConcurrentTaskExecutor, using a single thread executor as default.
|
||||
@@ -138,6 +141,7 @@ public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, Sche
|
||||
* @since 4.3
|
||||
*/
|
||||
public final void setTaskDecorator(TaskDecorator taskDecorator) {
|
||||
this.taskDecorator = taskDecorator;
|
||||
this.adaptedExecutor.setTaskDecorator(taskDecorator);
|
||||
}
|
||||
|
||||
@@ -174,11 +178,15 @@ public class ConcurrentTaskExecutor implements AsyncListenableTaskExecutor, Sche
|
||||
}
|
||||
|
||||
|
||||
private static TaskExecutorAdapter getAdaptedExecutor(Executor concurrentExecutor) {
|
||||
private TaskExecutorAdapter getAdaptedExecutor(Executor concurrentExecutor) {
|
||||
if (managedExecutorServiceClass != null && managedExecutorServiceClass.isInstance(concurrentExecutor)) {
|
||||
return new ManagedTaskExecutorAdapter(concurrentExecutor);
|
||||
}
|
||||
return new TaskExecutorAdapter(concurrentExecutor);
|
||||
TaskExecutorAdapter adapter = new TaskExecutorAdapter(concurrentExecutor);
|
||||
if (this.taskDecorator != null) {
|
||||
adapter.setTaskDecorator(this.taskDecorator);
|
||||
}
|
||||
return adapter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+5
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -100,7 +100,7 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
|
||||
*/
|
||||
public ConcurrentTaskScheduler() {
|
||||
super();
|
||||
this.scheduledExecutor = initScheduledExecutor(null);
|
||||
initScheduledExecutor(null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,7 +115,7 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
|
||||
*/
|
||||
public ConcurrentTaskScheduler(ScheduledExecutorService scheduledExecutor) {
|
||||
super(scheduledExecutor);
|
||||
this.scheduledExecutor = initScheduledExecutor(scheduledExecutor);
|
||||
initScheduledExecutor(scheduledExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,11 +131,11 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
|
||||
*/
|
||||
public ConcurrentTaskScheduler(Executor concurrentExecutor, ScheduledExecutorService scheduledExecutor) {
|
||||
super(concurrentExecutor);
|
||||
this.scheduledExecutor = initScheduledExecutor(scheduledExecutor);
|
||||
initScheduledExecutor(scheduledExecutor);
|
||||
}
|
||||
|
||||
|
||||
private ScheduledExecutorService initScheduledExecutor(@Nullable ScheduledExecutorService scheduledExecutor) {
|
||||
private void initScheduledExecutor(@Nullable ScheduledExecutorService scheduledExecutor) {
|
||||
if (scheduledExecutor != null) {
|
||||
this.scheduledExecutor = scheduledExecutor;
|
||||
this.enterpriseConcurrentScheduler = (managedScheduledExecutorServiceClass != null &&
|
||||
@@ -145,7 +145,6 @@ public class ConcurrentTaskScheduler extends ConcurrentTaskExecutor implements T
|
||||
this.scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
this.enterpriseConcurrentScheduler = false;
|
||||
}
|
||||
return this.scheduledExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+6
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -36,7 +36,10 @@ import org.springframework.lang.Nullable;
|
||||
* Base class for setting up a {@link java.util.concurrent.ExecutorService}
|
||||
* (typically a {@link java.util.concurrent.ThreadPoolExecutor} or
|
||||
* {@link java.util.concurrent.ScheduledThreadPoolExecutor}).
|
||||
* Defines common configuration settings and common lifecycle handling.
|
||||
*
|
||||
* <p>Defines common configuration settings and common lifecycle handling,
|
||||
* inheriting thread customization options (name, priority, etc) from
|
||||
* {@link org.springframework.util.CustomizableThreadCreator}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
@@ -199,8 +202,7 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac
|
||||
|
||||
|
||||
/**
|
||||
* Calls {@code shutdown} when the BeanFactory destroys
|
||||
* the task executor instance.
|
||||
* Calls {@code shutdown} when the BeanFactory destroys the executor instance.
|
||||
* @see #shutdown()
|
||||
*/
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -125,7 +125,7 @@ public class FieldError extends ObjectError {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Field error in object '" + getObjectName() + "' on field '" + this.field +
|
||||
"': rejected value [" + ObjectUtils.nullSafeToString(this.rejectedValue) + "]; " +
|
||||
"': rejected value [" + ObjectUtils.nullSafeConciseToString(this.rejectedValue) + "]; " +
|
||||
resolvableToString();
|
||||
}
|
||||
|
||||
|
||||
+44
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,6 +28,7 @@ import org.springframework.aop.MethodMatcher;
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.aop.support.DefaultPointcutAdvisor;
|
||||
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
|
||||
import org.springframework.aop.testfixture.advice.CountingBeforeAdvice;
|
||||
import org.springframework.aop.testfixture.interceptor.NopInterceptor;
|
||||
import org.springframework.beans.testfixture.beans.ITestBean;
|
||||
@@ -35,6 +36,8 @@ import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextException;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@@ -87,8 +90,7 @@ public class CglibProxyTests extends AbstractAopProxyTests implements Serializab
|
||||
AdvisedSupport pc = new AdvisedSupport(ITestBean.class);
|
||||
pc.addAdvice(new NopInterceptor());
|
||||
AopProxy aop = createAopProxy(pc);
|
||||
assertThatExceptionOfType(AopConfigException.class).isThrownBy(
|
||||
aop::getProxy);
|
||||
assertThatExceptionOfType(AopConfigException.class).isThrownBy(aop::getProxy);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -136,8 +138,8 @@ public class CglibProxyTests extends AbstractAopProxyTests implements Serializab
|
||||
|
||||
Object proxy = aop.getProxy();
|
||||
assertThat(AopUtils.isCglibProxy(proxy)).isTrue();
|
||||
assertThat(proxy instanceof ITestBean).isTrue();
|
||||
assertThat(proxy instanceof TestBean).isTrue();
|
||||
assertThat(proxy).isInstanceOf(ITestBean.class);
|
||||
assertThat(proxy).isInstanceOf(TestBean.class);
|
||||
|
||||
TestBean tb = (TestBean) proxy;
|
||||
assertThat(tb.getAge()).isEqualTo(32);
|
||||
@@ -304,6 +306,8 @@ public class CglibProxyTests extends AbstractAopProxyTests implements Serializab
|
||||
CglibAopProxy cglib = new CglibAopProxy(as);
|
||||
|
||||
ITestBean proxy1 = (ITestBean) cglib.getProxy();
|
||||
ITestBean proxy1a = (ITestBean) cglib.getProxy();
|
||||
assertThat(proxy1a.getClass()).isSameAs(proxy1.getClass());
|
||||
|
||||
mockTargetSource.setTarget(proxy1);
|
||||
as = new AdvisedSupport(new Class<?>[]{});
|
||||
@@ -312,7 +316,40 @@ public class CglibProxyTests extends AbstractAopProxyTests implements Serializab
|
||||
cglib = new CglibAopProxy(as);
|
||||
|
||||
ITestBean proxy2 = (ITestBean) cglib.getProxy();
|
||||
assertThat(proxy2 instanceof Serializable).isTrue();
|
||||
assertThat(proxy2).isInstanceOf(Serializable.class);
|
||||
assertThat(proxy2.getClass()).isNotSameAs(proxy1.getClass());
|
||||
|
||||
ITestBean proxy2a = (ITestBean) cglib.getProxy();
|
||||
assertThat(proxy2a).isInstanceOf(Serializable.class);
|
||||
assertThat(proxy2a.getClass()).isSameAs(proxy2.getClass());
|
||||
|
||||
mockTargetSource.setTarget(proxy1);
|
||||
as = new AdvisedSupport(new Class<?>[]{});
|
||||
as.setTargetSource(mockTargetSource);
|
||||
as.addAdvisor(new DefaultPointcutAdvisor(new AnnotationMatchingPointcut(Nullable.class), new NopInterceptor()));
|
||||
cglib = new CglibAopProxy(as);
|
||||
|
||||
ITestBean proxy3 = (ITestBean) cglib.getProxy();
|
||||
assertThat(proxy3).isInstanceOf(Serializable.class);
|
||||
assertThat(proxy3.getClass()).isNotSameAs(proxy2.getClass());
|
||||
|
||||
ITestBean proxy3a = (ITestBean) cglib.getProxy();
|
||||
assertThat(proxy3a).isInstanceOf(Serializable.class);
|
||||
assertThat(proxy3a.getClass()).isSameAs(proxy3.getClass());
|
||||
|
||||
mockTargetSource.setTarget(proxy1);
|
||||
as = new AdvisedSupport(new Class<?>[]{});
|
||||
as.setTargetSource(mockTargetSource);
|
||||
as.addAdvisor(new DefaultPointcutAdvisor(new AnnotationMatchingPointcut(NonNull.class), new NopInterceptor()));
|
||||
cglib = new CglibAopProxy(as);
|
||||
|
||||
ITestBean proxy4 = (ITestBean) cglib.getProxy();
|
||||
assertThat(proxy4).isInstanceOf(Serializable.class);
|
||||
assertThat(proxy4.getClass()).isNotSameAs(proxy3.getClass());
|
||||
|
||||
ITestBean proxy4a = (ITestBean) cglib.getProxy();
|
||||
assertThat(proxy4a).isInstanceOf(Serializable.class);
|
||||
assertThat(proxy4a.getClass()).isSameAs(proxy4.getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -331,7 +368,7 @@ public class CglibProxyTests extends AbstractAopProxyTests implements Serializab
|
||||
proxy.doTest();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
assertThat(ex instanceof ApplicationContextException).as("Invalid exception class").isTrue();
|
||||
assertThat(ex).as("Invalid exception class").isInstanceOf(ApplicationContextException.class);
|
||||
}
|
||||
|
||||
assertThat(proxy.isCatchInvoked()).as("Catch was not invoked").isTrue();
|
||||
|
||||
+16
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -41,6 +41,7 @@ class ImportTests {
|
||||
|
||||
private DefaultListableBeanFactory processConfigurationClasses(Class<?>... classes) {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
beanFactory.setAllowBeanDefinitionOverriding(false);
|
||||
for (Class<?> clazz : classes) {
|
||||
beanFactory.registerBeanDefinition(clazz.getSimpleName(), new RootBeanDefinition(clazz));
|
||||
}
|
||||
@@ -56,9 +57,10 @@ class ImportTests {
|
||||
for (Class<?> clazz : classes) {
|
||||
beanFactory.getBean(clazz);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void testProcessImportsWithAsm() {
|
||||
int configClasses = 2;
|
||||
@@ -158,6 +160,13 @@ class ImportTests {
|
||||
assertBeanDefinitionCount(configClasses + beansInClasses, FirstLevel.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testImportAnnotationWithThreeLevelRecursionAndDoubleImport() {
|
||||
int configClasses = 5;
|
||||
int beansInClasses = 5;
|
||||
assertBeanDefinitionCount(configClasses + beansInClasses, FirstLevel.class, FirstLevelPlus.class);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
@@ -167,7 +176,6 @@ class ImportTests {
|
||||
assertBeanDefinitionCount((configClasses + beansInClasses), WithMultipleArgumentsToImportAnnotation.class);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testImportAnnotationWithMultipleArgumentsResultingInOverriddenBeanDefinition() {
|
||||
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
@@ -245,6 +253,11 @@ class ImportTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import(ThirdLevel.class)
|
||||
static class FirstLevelPlus {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Import({ThirdLevel.class, InitBean.class})
|
||||
static class SecondLevel {
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -133,7 +133,7 @@ public class DateFormattingTests {
|
||||
assertThat(exception)
|
||||
.hasMessageContaining("for property 'styleDate'")
|
||||
.hasCauseInstanceOf(ConversionFailedException.class).cause()
|
||||
.hasMessageContaining("for value '99/01/01'")
|
||||
.hasMessageContaining("for value [99/01/01]")
|
||||
.hasCauseInstanceOf(IllegalArgumentException.class).cause()
|
||||
.hasMessageContaining("Parse attempt failed for value [99/01/01]")
|
||||
.hasCauseInstanceOf(ParseException.class).cause()
|
||||
@@ -353,7 +353,7 @@ public class DateFormattingTests {
|
||||
assertThat(fieldError.unwrap(TypeMismatchException.class))
|
||||
.hasMessageContaining("for property 'patternDateWithFallbackPatterns'")
|
||||
.hasCauseInstanceOf(ConversionFailedException.class).cause()
|
||||
.hasMessageContaining("for value '210302'")
|
||||
.hasMessageContaining("for value [210302]")
|
||||
.hasCauseInstanceOf(IllegalArgumentException.class).cause()
|
||||
.hasMessageContaining("Parse attempt failed for value [210302]")
|
||||
.hasCauseInstanceOf(ParseException.class).cause()
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -333,7 +333,7 @@ class DateTimeFormattingTests {
|
||||
assertThat(fieldError.unwrap(TypeMismatchException.class))
|
||||
.hasMessageContaining("for property 'isoLocalDate'")
|
||||
.hasCauseInstanceOf(ConversionFailedException.class).cause()
|
||||
.hasMessageContaining("for value '2009-31-10'")
|
||||
.hasMessageContaining("for value [2009-31-10]")
|
||||
.hasCauseInstanceOf(IllegalArgumentException.class).cause()
|
||||
.hasMessageContaining("Parse attempt failed for value [2009-31-10]")
|
||||
.hasCauseInstanceOf(DateTimeParseException.class).cause()
|
||||
@@ -540,7 +540,7 @@ class DateTimeFormattingTests {
|
||||
assertThat(fieldError.unwrap(TypeMismatchException.class))
|
||||
.hasMessageContaining("for property 'patternLocalDateWithFallbackPatterns'")
|
||||
.hasCauseInstanceOf(ConversionFailedException.class).cause()
|
||||
.hasMessageContaining("for value '210302'")
|
||||
.hasMessageContaining("for value [210302]")
|
||||
.hasCauseInstanceOf(IllegalArgumentException.class).cause()
|
||||
.hasMessageContaining("Parse attempt failed for value [210302]")
|
||||
.hasCauseInstanceOf(DateTimeParseException.class).cause()
|
||||
|
||||
+36
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.scheduling.concurrent;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RunnableFuture;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
@@ -26,6 +27,8 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.task.AsyncListenableTaskExecutor;
|
||||
import org.springframework.core.task.NoOpRunnable;
|
||||
import org.springframework.core.task.TaskDecorator;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
|
||||
@@ -69,10 +72,40 @@ class ConcurrentTaskExecutorTests extends AbstractSchedulingTaskExecutorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void passingNullExecutorToSetterResultsInDefaultTaskExecutorBeingUsed() {
|
||||
void earlySetConcurrentExecutorCallRespectsConfiguredTaskDecorator() {
|
||||
ConcurrentTaskExecutor executor = new ConcurrentTaskExecutor();
|
||||
executor.setConcurrentExecutor(null);
|
||||
executor.setConcurrentExecutor(new DecoratedExecutor());
|
||||
executor.setTaskDecorator(new RunnableDecorator());
|
||||
assertThatCode(() -> executor.execute(new NoOpRunnable())).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
void lateSetConcurrentExecutorCallRespectsConfiguredTaskDecorator() {
|
||||
ConcurrentTaskExecutor executor = new ConcurrentTaskExecutor();
|
||||
executor.setTaskDecorator(new RunnableDecorator());
|
||||
executor.setConcurrentExecutor(new DecoratedExecutor());
|
||||
assertThatCode(() -> executor.execute(new NoOpRunnable())).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
|
||||
private static class DecoratedRunnable implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
}
|
||||
}
|
||||
|
||||
private static class RunnableDecorator implements TaskDecorator {
|
||||
@Override
|
||||
public Runnable decorate(Runnable runnable) {
|
||||
return new DecoratedRunnable();
|
||||
}
|
||||
}
|
||||
|
||||
private static class DecoratedExecutor implements Executor {
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
Assert.state(command instanceof DecoratedRunnable, "TaskDecorator not applied");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -68,6 +68,7 @@ public final class CollectionFactory {
|
||||
approximableCollectionTypes.add(SortedSet.class);
|
||||
approximableCollectionTypes.add(NavigableSet.class);
|
||||
approximableMapTypes.add(Map.class);
|
||||
approximableMapTypes.add(MultiValueMap.class);
|
||||
approximableMapTypes.add(SortedMap.class);
|
||||
approximableMapTypes.add(NavigableMap.class);
|
||||
|
||||
@@ -80,6 +81,7 @@ public final class CollectionFactory {
|
||||
approximableCollectionTypes.add(EnumSet.class);
|
||||
approximableMapTypes.add(HashMap.class);
|
||||
approximableMapTypes.add(LinkedHashMap.class);
|
||||
approximableMapTypes.add(LinkedMultiValueMap.class);
|
||||
approximableMapTypes.add(TreeMap.class);
|
||||
approximableMapTypes.add(EnumMap.class);
|
||||
}
|
||||
@@ -121,13 +123,7 @@ public final class CollectionFactory {
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked", "cast"})
|
||||
public static <E> Collection<E> createApproximateCollection(@Nullable Object collection, int capacity) {
|
||||
if (collection instanceof LinkedList) {
|
||||
return new LinkedList<>();
|
||||
}
|
||||
else if (collection instanceof List) {
|
||||
return new ArrayList<>(capacity);
|
||||
}
|
||||
else if (collection instanceof EnumSet) {
|
||||
if (collection instanceof EnumSet) {
|
||||
// Cast is necessary for compilation in Eclipse 4.4.1.
|
||||
Collection<E> enumSet = (Collection<E>) EnumSet.copyOf((EnumSet) collection);
|
||||
enumSet.clear();
|
||||
@@ -136,6 +132,12 @@ public final class CollectionFactory {
|
||||
else if (collection instanceof SortedSet) {
|
||||
return new TreeSet<>(((SortedSet<E>) collection).comparator());
|
||||
}
|
||||
if (collection instanceof LinkedList) {
|
||||
return new LinkedList<>();
|
||||
}
|
||||
else if (collection instanceof List) {
|
||||
return new ArrayList<>(capacity);
|
||||
}
|
||||
else {
|
||||
return new LinkedHashSet<>(capacity);
|
||||
}
|
||||
@@ -181,7 +183,7 @@ public final class CollectionFactory {
|
||||
@SuppressWarnings({"unchecked", "cast"})
|
||||
public static <E> Collection<E> createCollection(Class<?> collectionType, @Nullable Class<?> elementType, int capacity) {
|
||||
Assert.notNull(collectionType, "Collection type must not be null");
|
||||
if (LinkedHashSet.class == collectionType || HashSet.class == collectionType ||
|
||||
if (LinkedHashSet.class == collectionType ||
|
||||
Set.class == collectionType || Collection.class == collectionType) {
|
||||
return new LinkedHashSet<>(capacity);
|
||||
}
|
||||
@@ -191,8 +193,8 @@ public final class CollectionFactory {
|
||||
else if (LinkedList.class == collectionType) {
|
||||
return new LinkedList<>();
|
||||
}
|
||||
else if (TreeSet.class == collectionType || NavigableSet.class == collectionType
|
||||
|| SortedSet.class == collectionType) {
|
||||
else if (TreeSet.class == collectionType || NavigableSet.class == collectionType ||
|
||||
SortedSet.class == collectionType) {
|
||||
return new TreeSet<>();
|
||||
}
|
||||
else if (EnumSet.class.isAssignableFrom(collectionType)) {
|
||||
@@ -200,6 +202,9 @@ public final class CollectionFactory {
|
||||
// Cast is necessary for compilation in Eclipse 4.4.1.
|
||||
return (Collection<E>) EnumSet.noneOf(asEnumType(elementType));
|
||||
}
|
||||
else if (HashSet.class == collectionType) {
|
||||
return new HashSet<>(capacity);
|
||||
}
|
||||
else {
|
||||
if (collectionType.isInterface() || !Collection.class.isAssignableFrom(collectionType)) {
|
||||
throw new IllegalArgumentException("Unsupported Collection type: " + collectionType.getName());
|
||||
@@ -251,6 +256,9 @@ public final class CollectionFactory {
|
||||
else if (map instanceof SortedMap) {
|
||||
return new TreeMap<>(((SortedMap<K, V>) map).comparator());
|
||||
}
|
||||
else if (map instanceof MultiValueMap) {
|
||||
return new LinkedMultiValueMap(capacity);
|
||||
}
|
||||
else {
|
||||
return new LinkedHashMap<>(capacity);
|
||||
}
|
||||
@@ -297,26 +305,24 @@ public final class CollectionFactory {
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public static <K, V> Map<K, V> createMap(Class<?> mapType, @Nullable Class<?> keyType, int capacity) {
|
||||
Assert.notNull(mapType, "Map type must not be null");
|
||||
if (mapType.isInterface()) {
|
||||
if (Map.class == mapType) {
|
||||
return new LinkedHashMap<>(capacity);
|
||||
}
|
||||
else if (SortedMap.class == mapType || NavigableMap.class == mapType) {
|
||||
return new TreeMap<>();
|
||||
}
|
||||
else if (MultiValueMap.class == mapType) {
|
||||
return new LinkedMultiValueMap();
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported Map interface: " + mapType.getName());
|
||||
}
|
||||
if (LinkedHashMap.class == mapType || Map.class == mapType) {
|
||||
return new LinkedHashMap<>(capacity);
|
||||
}
|
||||
else if (LinkedMultiValueMap.class == mapType || MultiValueMap.class == mapType) {
|
||||
return new LinkedMultiValueMap();
|
||||
}
|
||||
else if (TreeMap.class == mapType || SortedMap.class == mapType || NavigableMap.class == mapType) {
|
||||
return new TreeMap<>();
|
||||
}
|
||||
else if (EnumMap.class == mapType) {
|
||||
Assert.notNull(keyType, "Cannot create EnumMap for unknown key type");
|
||||
return new EnumMap(asEnumType(keyType));
|
||||
}
|
||||
else if (HashMap.class == mapType) {
|
||||
return new HashMap<>(capacity);
|
||||
}
|
||||
else {
|
||||
if (!Map.class.isAssignableFrom(mapType)) {
|
||||
if (mapType.isInterface() || !Map.class.isAssignableFrom(mapType)) {
|
||||
throw new IllegalArgumentException("Unsupported Map type: " + mapType.getName());
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -781,6 +781,7 @@ public class MethodParameter {
|
||||
return new MethodParameter(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new MethodParameter for the given method or constructor.
|
||||
* <p>This is a convenience factory method for scenarios where a
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -49,7 +49,7 @@ public class ConversionFailedException extends ConversionException {
|
||||
@Nullable Object value, Throwable cause) {
|
||||
|
||||
super("Failed to convert from type [" + sourceType + "] to type [" + targetType +
|
||||
"] for value '" + ObjectUtils.nullSafeToString(value) + "'", cause);
|
||||
"] for value [" + ObjectUtils.nullSafeConciseToString(value) + "]", cause);
|
||||
this.sourceType = sourceType;
|
||||
this.targetType = targetType;
|
||||
this.value = value;
|
||||
|
||||
@@ -57,6 +57,8 @@ package org.springframework.core.env;
|
||||
* of property sources prior to application context {@code refresh()}.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Phillip Webb
|
||||
* @author Sam Brannen
|
||||
* @since 3.1
|
||||
* @see PropertyResolver
|
||||
* @see EnvironmentCapable
|
||||
@@ -95,25 +97,56 @@ public interface Environment extends PropertyResolver {
|
||||
String[] getDefaultProfiles();
|
||||
|
||||
/**
|
||||
* Return whether one or more of the given profiles is active or, in the case of no
|
||||
* explicit active profiles, whether one or more of the given profiles is included in
|
||||
* the set of default profiles. If a profile begins with '!' the logic is inverted,
|
||||
* i.e. the method will return {@code true} if the given profile is <em>not</em> active.
|
||||
* For example, {@code env.acceptsProfiles("p1", "!p2")} will return {@code true} if
|
||||
* profile 'p1' is active or 'p2' is not active.
|
||||
* @throws IllegalArgumentException if called with zero arguments
|
||||
* or if any profile is {@code null}, empty, or whitespace only
|
||||
* Determine whether one or more of the given profiles is active — or
|
||||
* in the case of no explicit {@linkplain #getActiveProfiles() active profiles},
|
||||
* whether one or more of the given profiles is included in the set of
|
||||
* {@linkplain #getDefaultProfiles() default profiles}.
|
||||
* <p>If a profile begins with '!' the logic is inverted, meaning this method
|
||||
* will return {@code true} if the given profile is <em>not</em> active. For
|
||||
* example, {@code env.acceptsProfiles("p1", "!p2")} will return {@code true}
|
||||
* if profile 'p1' is active or 'p2' is not active.
|
||||
* @throws IllegalArgumentException if called with a {@code null} array, an
|
||||
* empty array, zero arguments or if any profile is {@code null}, empty, or
|
||||
* whitespace only
|
||||
* @see #getActiveProfiles
|
||||
* @see #getDefaultProfiles
|
||||
* @see #matchesProfiles(String...)
|
||||
* @see #acceptsProfiles(Profiles)
|
||||
* @deprecated as of 5.1 in favor of {@link #acceptsProfiles(Profiles)}
|
||||
* @deprecated as of 5.1 in favor of {@link #acceptsProfiles(Profiles)} or
|
||||
* {@link #matchesProfiles(String...)}
|
||||
*/
|
||||
@Deprecated
|
||||
boolean acceptsProfiles(String... profiles);
|
||||
|
||||
/**
|
||||
* Return whether the {@linkplain #getActiveProfiles() active profiles}
|
||||
* match the given {@link Profiles} predicate.
|
||||
* Determine whether one of the given profile expressions matches the
|
||||
* {@linkplain #getActiveProfiles() active profiles} — or in the case
|
||||
* of no explicit active profiles, whether one of the given profile expressions
|
||||
* matches the {@linkplain #getDefaultProfiles() default profiles}.
|
||||
* <p>Profile expressions allow for complex, boolean profile logic to be
|
||||
* expressed — for example {@code "p1 & p2"}, {@code "(p1 & p2) | p3"},
|
||||
* etc. See {@link Profiles#of(String...)} for details on the supported
|
||||
* expression syntax.
|
||||
* <p>This method is a convenient shortcut for
|
||||
* {@code env.acceptsProfiles(Profiles.of(profileExpressions))}.
|
||||
* @since 5.3.28
|
||||
* @see Profiles#of(String...)
|
||||
* @see #acceptsProfiles(Profiles)
|
||||
*/
|
||||
default boolean matchesProfiles(String... profileExpressions) {
|
||||
return acceptsProfiles(Profiles.of(profileExpressions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given {@link Profiles} predicate matches the
|
||||
* {@linkplain #getActiveProfiles() active profiles} — or in the case
|
||||
* of no explicit active profiles, whether the given {@code Profiles} predicate
|
||||
* matches the {@linkplain #getDefaultProfiles() default profiles}.
|
||||
* <p>If you wish provide profile expressions directly as strings, use
|
||||
* {@link #matchesProfiles(String...)} instead.
|
||||
* @since 5.1
|
||||
* @see #matchesProfiles(String...)
|
||||
* @see Profiles#of(String...)
|
||||
*/
|
||||
boolean acceptsProfiles(Profiles profiles);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -43,32 +43,32 @@ public interface Profiles {
|
||||
|
||||
/**
|
||||
* Create a new {@link Profiles} instance that checks for matches against
|
||||
* the given <em>profile strings</em>.
|
||||
* the given <em>profile expressions</em>.
|
||||
* <p>The returned instance will {@linkplain Profiles#matches(Predicate) match}
|
||||
* if any one of the given profile strings matches.
|
||||
* <p>A profile string may contain a simple profile name (for example
|
||||
* {@code "production"}) or a profile expression. A profile expression allows
|
||||
* if any one of the given profile expressions matches.
|
||||
* <p>A profile expression may contain a simple profile name (for example
|
||||
* {@code "production"}) or a compound expression. A compound expression allows
|
||||
* for more complicated profile logic to be expressed, for example
|
||||
* {@code "production & cloud"}.
|
||||
* <p>The following operators are supported in profile expressions.
|
||||
* <ul>
|
||||
* <li>{@code !} - A logical <em>NOT</em> of the profile or profile expression</li>
|
||||
* <li>{@code &} - A logical <em>AND</em> of the profiles or profile expressions</li>
|
||||
* <li>{@code |} - A logical <em>OR</em> of the profiles or profile expressions</li>
|
||||
* <li>{@code !} - A logical <em>NOT</em> of the profile name or compound expression</li>
|
||||
* <li>{@code &} - A logical <em>AND</em> of the profile names or compound expressions</li>
|
||||
* <li>{@code |} - A logical <em>OR</em> of the profile names or compound expressions</li>
|
||||
* </ul>
|
||||
* <p>Please note that the {@code &} and {@code |} operators may not be mixed
|
||||
* without using parentheses. For example {@code "a & b | c"} is not a valid
|
||||
* expression; it must be expressed as {@code "(a & b) | c"} or
|
||||
* without using parentheses. For example, {@code "a & b | c"} is not a valid
|
||||
* expression: it must be expressed as {@code "(a & b) | c"} or
|
||||
* {@code "a & (b | c)"}.
|
||||
* <p>As of Spring Framework 5.1.17, two {@code Profiles} instances returned
|
||||
* by this method are considered equivalent to each other (in terms of
|
||||
* {@code equals()} and {@code hashCode()} semantics) if they are created
|
||||
* with identical <em>profile strings</em>.
|
||||
* @param profiles the <em>profile strings</em> to include
|
||||
* with identical <em>profile expressions</em>.
|
||||
* @param profileExpressions the <em>profile expressions</em> to include
|
||||
* @return a new {@link Profiles} instance
|
||||
*/
|
||||
static Profiles of(String... profiles) {
|
||||
return ProfilesParser.parse(profiles);
|
||||
static Profiles of(String... profileExpressions) {
|
||||
return ProfilesParser.parse(profileExpressions);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+11
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -43,7 +43,7 @@ final class ProfilesParser {
|
||||
|
||||
|
||||
static Profiles parse(String... expressions) {
|
||||
Assert.notEmpty(expressions, "Must specify at least one profile");
|
||||
Assert.notEmpty(expressions, "Must specify at least one profile expression");
|
||||
Profiles[] parsed = new Profiles[expressions.length];
|
||||
for (int i = 0; i < expressions.length; i++) {
|
||||
parsed[i] = parseExpression(expressions[i]);
|
||||
@@ -71,8 +71,8 @@ final class ProfilesParser {
|
||||
}
|
||||
switch (token) {
|
||||
case "(":
|
||||
Profiles contents = parseTokens(expression, tokens, Context.BRACKET);
|
||||
if (context == Context.INVERT) {
|
||||
Profiles contents = parseTokens(expression, tokens, Context.PARENTHESIS);
|
||||
if (context == Context.NEGATE) {
|
||||
return contents;
|
||||
}
|
||||
elements.add(contents);
|
||||
@@ -86,11 +86,11 @@ final class ProfilesParser {
|
||||
operator = Operator.OR;
|
||||
break;
|
||||
case "!":
|
||||
elements.add(not(parseTokens(expression, tokens, Context.INVERT)));
|
||||
elements.add(not(parseTokens(expression, tokens, Context.NEGATE)));
|
||||
break;
|
||||
case ")":
|
||||
Profiles merged = merge(expression, elements, operator);
|
||||
if (context == Context.BRACKET) {
|
||||
if (context == Context.PARENTHESIS) {
|
||||
return merged;
|
||||
}
|
||||
elements.clear();
|
||||
@@ -99,7 +99,7 @@ final class ProfilesParser {
|
||||
break;
|
||||
default:
|
||||
Profiles value = equals(token);
|
||||
if (context == Context.INVERT) {
|
||||
if (context == Context.NEGATE) {
|
||||
return value;
|
||||
}
|
||||
elements.add(value);
|
||||
@@ -137,15 +137,14 @@ final class ProfilesParser {
|
||||
return activeProfile -> activeProfile.test(profile);
|
||||
}
|
||||
|
||||
private static Predicate<Profiles> isMatch(Predicate<String> activeProfile) {
|
||||
return profiles -> profiles.matches(activeProfile);
|
||||
private static Predicate<Profiles> isMatch(Predicate<String> activeProfiles) {
|
||||
return profiles -> profiles.matches(activeProfiles);
|
||||
}
|
||||
|
||||
|
||||
private enum Operator {AND, OR}
|
||||
private enum Operator { AND, OR }
|
||||
|
||||
|
||||
private enum Context {NONE, INVERT, BRACKET}
|
||||
private enum Context { NONE, NEGATE, PARENTHESIS }
|
||||
|
||||
|
||||
private static class ParsedProfiles implements Profiles {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,6 +23,7 @@ import org.apache.commons.logging.Log;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Utility methods for formatting and logging messages.
|
||||
@@ -78,7 +79,7 @@ public abstract class LogFormatUtils {
|
||||
result = ObjectUtils.nullSafeToString(ex);
|
||||
}
|
||||
if (maxLength != -1) {
|
||||
result = (result.length() > maxLength ? result.substring(0, maxLength) + " (truncated)..." : result);
|
||||
result = StringUtils.truncate(result, maxLength);
|
||||
}
|
||||
if (replaceNewlinesAndControlCharacters) {
|
||||
result = NEWLINE_PATTERN.matcher(result).replaceAll("<EOL>");
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -50,6 +50,7 @@ public class DefaultDeserializer implements Deserializer<Object> {
|
||||
/**
|
||||
* Create a {@code DefaultDeserializer} for using an {@link ObjectInputStream}
|
||||
* with the given {@code ClassLoader}.
|
||||
* @param classLoader the ClassLoader to use
|
||||
* @since 4.2.1
|
||||
* @see ConfigurableObjectInputStream#ConfigurableObjectInputStream(InputStream, ClassLoader)
|
||||
*/
|
||||
|
||||
+4
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,6 +21,7 @@ import java.io.ByteArrayInputStream;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.serializer.DefaultDeserializer;
|
||||
import org.springframework.core.serializer.Deserializer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -50,10 +51,11 @@ public class DeserializingConverter implements Converter<byte[], Object> {
|
||||
/**
|
||||
* Create a {@code DeserializingConverter} for using an {@link java.io.ObjectInputStream}
|
||||
* with the given {@code ClassLoader}.
|
||||
* @param classLoader the ClassLoader to use
|
||||
* @since 4.2.1
|
||||
* @see DefaultDeserializer#DefaultDeserializer(ClassLoader)
|
||||
*/
|
||||
public DeserializingConverter(ClassLoader classLoader) {
|
||||
public DeserializingConverter(@Nullable ClassLoader classLoader) {
|
||||
this.deserializer = new DefaultDeserializer(classLoader);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -68,10 +68,10 @@ public abstract class FileSystemUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the supplied {@link File} — for directories,
|
||||
* Delete the supplied {@link Path} — for directories,
|
||||
* recursively delete any nested directories or files as well.
|
||||
* @param root the root {@code File} to delete
|
||||
* @return {@code true} if the {@code File} existed and was deleted,
|
||||
* @param root the root {@code Path} to delete
|
||||
* @return {@code true} if the {@code Path} existed and was deleted,
|
||||
* or {@code false} if it did not exist
|
||||
* @throws IOException in the case of I/O errors
|
||||
* @since 5.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,11 +17,17 @@
|
||||
package org.springframework.util;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.time.temporal.Temporal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -653,6 +659,7 @@ public abstract class ObjectUtils {
|
||||
* Returns a {@code "null"} String if {@code obj} is {@code null}.
|
||||
* @param obj the object to build a String representation for
|
||||
* @return a String representation of {@code obj}
|
||||
* @see #nullSafeConciseToString(Object)
|
||||
*/
|
||||
public static String nullSafeToString(@Nullable Object obj) {
|
||||
if (obj == null) {
|
||||
@@ -908,4 +915,70 @@ public abstract class ObjectUtils {
|
||||
return stringJoiner.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a null-safe, concise string representation of the supplied object
|
||||
* as described below.
|
||||
* <p>Favor this method over {@link #nullSafeToString(Object)} when you need
|
||||
* the length of the generated string to be limited.
|
||||
* <p>Returns:
|
||||
* <ul>
|
||||
* <li>{@code "null"} if {@code obj} is {@code null}</li>
|
||||
* <li>{@linkplain Class#getName() Class name} if {@code obj} is a {@link Class}</li>
|
||||
* <li>Potentially {@linkplain StringUtils#truncate(CharSequence) truncated string}
|
||||
* if {@code obj} is a {@link String} or {@link CharSequence}</li>
|
||||
* <li>Potentially {@linkplain StringUtils#truncate(CharSequence) truncated string}
|
||||
* if {@code obj} is a <em>simple value type</em> whose {@code toString()} method
|
||||
* returns a non-null value.</li>
|
||||
* <li>Otherwise, a string representation of the object's type name concatenated
|
||||
* with {@code @} and a hex string form of the object's identity hash code</li>
|
||||
* </ul>
|
||||
* <p>In the context of this method, a <em>simple value type</em> is any of the following:
|
||||
* a primitive wrapper (excluding {@code Void}), an {@code Enum}, a {@code Number},
|
||||
* a {@code Date}, a {@code Temporal}, a {@code UUID}, a {@code URI}, a {@code URL},
|
||||
* or a {@code Locale}.
|
||||
* @param obj the object to build a string representation for
|
||||
* @return a concise string representation of the supplied object
|
||||
* @since 5.3.27
|
||||
* @see #nullSafeToString(Object)
|
||||
* @see StringUtils#truncate(CharSequence)
|
||||
*/
|
||||
public static String nullSafeConciseToString(@Nullable Object obj) {
|
||||
if (obj == null) {
|
||||
return "null";
|
||||
}
|
||||
if (obj instanceof Class<?>) {
|
||||
return ((Class<?>) obj).getName();
|
||||
}
|
||||
if (obj instanceof CharSequence) {
|
||||
return StringUtils.truncate((CharSequence) obj);
|
||||
}
|
||||
Class<?> type = obj.getClass();
|
||||
if (isSimpleValueType(type)) {
|
||||
String str = obj.toString();
|
||||
if (str != null) {
|
||||
return StringUtils.truncate(str);
|
||||
}
|
||||
}
|
||||
return type.getTypeName() + "@" + getIdentityHexString(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derived from {@link org.springframework.beans.BeanUtils#isSimpleValueType}.
|
||||
* As of 5.3.28, considering {@code UUID} in addition to the bean-level check.
|
||||
*/
|
||||
private static boolean isSimpleValueType(Class<?> type) {
|
||||
return (Void.class != type && void.class != type &&
|
||||
(ClassUtils.isPrimitiveOrWrapper(type) ||
|
||||
Enum.class.isAssignableFrom(type) ||
|
||||
CharSequence.class.isAssignableFrom(type) ||
|
||||
Number.class.isAssignableFrom(type) ||
|
||||
Date.class.isAssignableFrom(type) ||
|
||||
Temporal.class.isAssignableFrom(type) ||
|
||||
UUID.class == type ||
|
||||
URI.class == type ||
|
||||
URL.class == type ||
|
||||
Locale.class == type ||
|
||||
Class.class == type));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -75,6 +75,10 @@ public abstract class StringUtils {
|
||||
|
||||
private static final char EXTENSION_SEPARATOR = '.';
|
||||
|
||||
private static final int DEFAULT_TRUNCATION_THRESHOLD = 100;
|
||||
|
||||
private static final String TRUNCATION_SUFFIX = " (truncated)...";
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// General convenience methods for working with Strings
|
||||
@@ -1388,4 +1392,40 @@ public abstract class StringUtils {
|
||||
return arrayToDelimitedString(arr, ",");
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate the supplied {@link CharSequence}.
|
||||
* <p>Delegates to {@link #truncate(CharSequence, int)}, supplying {@code 100}
|
||||
* as the threshold.
|
||||
* @param charSequence the {@code CharSequence} to truncate
|
||||
* @return a truncated string, or a string representation of the original
|
||||
* {@code CharSequence} if its length does not exceed the threshold
|
||||
* @since 5.3.27
|
||||
*/
|
||||
public static String truncate(CharSequence charSequence) {
|
||||
return truncate(charSequence, DEFAULT_TRUNCATION_THRESHOLD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate the supplied {@link CharSequence}.
|
||||
* <p>If the length of the {@code CharSequence} is greater than the threshold,
|
||||
* this method returns a {@linkplain CharSequence#subSequence(int, int)
|
||||
* subsequence} of the {@code CharSequence} (up to the threshold) appended
|
||||
* with the suffix {@code " (truncated)..."}. Otherwise, this method returns
|
||||
* {@code charSequence.toString()}.
|
||||
* @param charSequence the {@code CharSequence} to truncate
|
||||
* @param threshold the maximum length after which to truncate; must be a
|
||||
* positive number
|
||||
* @return a truncated string, or a string representation of the original
|
||||
* {@code CharSequence} if its length does not exceed the threshold
|
||||
* @since 5.3.27
|
||||
*/
|
||||
public static String truncate(CharSequence charSequence, int threshold) {
|
||||
Assert.isTrue(threshold > 0,
|
||||
() -> "Truncation threshold must be a positive number: " + threshold);
|
||||
if (charSequence.length() > threshold) {
|
||||
return charSequence.subSequence(0, threshold) + TRUNCATION_SUFFIX;
|
||||
}
|
||||
return charSequence.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -209,21 +209,23 @@ class CollectionFactoryTests {
|
||||
@Test
|
||||
void createsCollectionsCorrectly() {
|
||||
// interfaces
|
||||
assertThat(createCollection(List.class, 0)).isInstanceOf(ArrayList.class);
|
||||
assertThat(createCollection(Set.class, 0)).isInstanceOf(LinkedHashSet.class);
|
||||
assertThat(createCollection(Collection.class, 0)).isInstanceOf(LinkedHashSet.class);
|
||||
assertThat(createCollection(SortedSet.class, 0)).isInstanceOf(TreeSet.class);
|
||||
assertThat(createCollection(NavigableSet.class, 0)).isInstanceOf(TreeSet.class);
|
||||
|
||||
assertThat(createCollection(List.class, String.class, 0)).isInstanceOf(ArrayList.class);
|
||||
assertThat(createCollection(Set.class, String.class, 0)).isInstanceOf(LinkedHashSet.class);
|
||||
assertThat(createCollection(Collection.class, String.class, 0)).isInstanceOf(LinkedHashSet.class);
|
||||
assertThat(createCollection(SortedSet.class, String.class, 0)).isInstanceOf(TreeSet.class);
|
||||
assertThat(createCollection(NavigableSet.class, String.class, 0)).isInstanceOf(TreeSet.class);
|
||||
testCollection(List.class, ArrayList.class);
|
||||
testCollection(Set.class, LinkedHashSet.class);
|
||||
testCollection(Collection.class, LinkedHashSet.class);
|
||||
testCollection(SortedSet.class, TreeSet.class);
|
||||
testCollection(NavigableSet.class, TreeSet.class);
|
||||
|
||||
// concrete types
|
||||
assertThat(createCollection(HashSet.class, 0)).isInstanceOf(HashSet.class);
|
||||
assertThat(createCollection(HashSet.class, String.class, 0)).isInstanceOf(HashSet.class);
|
||||
testCollection(ArrayList.class, ArrayList.class);
|
||||
testCollection(HashSet.class, HashSet.class);
|
||||
testCollection(LinkedHashSet.class, LinkedHashSet.class);
|
||||
testCollection(TreeSet.class, TreeSet.class);
|
||||
}
|
||||
|
||||
private void testCollection(Class<?> collectionType, Class<?> resultType) {
|
||||
assertThat(CollectionFactory.isApproximableCollectionType(collectionType)).isTrue();
|
||||
assertThat(createCollection(collectionType, 0)).isExactlyInstanceOf(resultType);
|
||||
assertThat(createCollection(collectionType, String.class, 0)).isExactlyInstanceOf(resultType);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -258,20 +260,22 @@ class CollectionFactoryTests {
|
||||
@Test
|
||||
void createsMapsCorrectly() {
|
||||
// interfaces
|
||||
assertThat(createMap(Map.class, 0)).isInstanceOf(LinkedHashMap.class);
|
||||
assertThat(createMap(SortedMap.class, 0)).isInstanceOf(TreeMap.class);
|
||||
assertThat(createMap(NavigableMap.class, 0)).isInstanceOf(TreeMap.class);
|
||||
assertThat(createMap(MultiValueMap.class, 0)).isInstanceOf(LinkedMultiValueMap.class);
|
||||
|
||||
assertThat(createMap(Map.class, String.class, 0)).isInstanceOf(LinkedHashMap.class);
|
||||
assertThat(createMap(SortedMap.class, String.class, 0)).isInstanceOf(TreeMap.class);
|
||||
assertThat(createMap(NavigableMap.class, String.class, 0)).isInstanceOf(TreeMap.class);
|
||||
assertThat(createMap(MultiValueMap.class, String.class, 0)).isInstanceOf(LinkedMultiValueMap.class);
|
||||
testMap(Map.class, LinkedHashMap.class);
|
||||
testMap(SortedMap.class, TreeMap.class);
|
||||
testMap(NavigableMap.class, TreeMap.class);
|
||||
testMap(MultiValueMap.class, LinkedMultiValueMap.class);
|
||||
|
||||
// concrete types
|
||||
assertThat(createMap(HashMap.class, 0)).isInstanceOf(HashMap.class);
|
||||
testMap(HashMap.class, HashMap.class);
|
||||
testMap(LinkedHashMap.class, LinkedHashMap.class);
|
||||
testMap(TreeMap.class, TreeMap.class);
|
||||
testMap(LinkedMultiValueMap.class, LinkedMultiValueMap.class);
|
||||
}
|
||||
|
||||
assertThat(createMap(HashMap.class, String.class, 0)).isInstanceOf(HashMap.class);
|
||||
private void testMap(Class<?> mapType, Class<?> resultType) {
|
||||
assertThat(CollectionFactory.isApproximableMapType(mapType)).isTrue();
|
||||
assertThat(createMap(mapType, 0)).isExactlyInstanceOf(resultType);
|
||||
assertThat(createMap(mapType, String.class, 0)).isExactlyInstanceOf(resultType);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+12
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -40,30 +40,30 @@ class ProfilesTests {
|
||||
|
||||
@Test
|
||||
void ofWhenNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
Profiles.of((String[]) null))
|
||||
.withMessageContaining("Must specify at least one profile");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> Profiles.of((String[]) null))
|
||||
.withMessage("Must specify at least one profile expression");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWhenEmptyThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(Profiles::of)
|
||||
.withMessageContaining("Must specify at least one profile");
|
||||
.withMessage("Must specify at least one profile expression");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofNullElement() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
Profiles.of((String) null))
|
||||
.withMessageContaining("must contain text");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> Profiles.of((String) null))
|
||||
.withMessage("Invalid profile expression [null]: must contain text");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofEmptyElement() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
Profiles.of(" "))
|
||||
.withMessageContaining("must contain text");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> Profiles.of(" "))
|
||||
.withMessage("Invalid profile expression [ ]: must contain text");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -356,7 +356,7 @@ class ProfilesTests {
|
||||
private static void assertMalformed(Supplier<Profiles> supplier) {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(supplier::get)
|
||||
.withMessageContaining("Malformed");
|
||||
.withMessageStartingWith("Malformed profile expression");
|
||||
}
|
||||
|
||||
private static Predicate<String> activeProfiles(String... profiles) {
|
||||
|
||||
+344
-190
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,9 +18,9 @@ package org.springframework.core.env;
|
||||
|
||||
import java.security.AccessControlException;
|
||||
import java.security.Permission;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.SpringProperties;
|
||||
@@ -40,8 +40,7 @@ import static org.springframework.core.env.AbstractEnvironment.RESERVED_DEFAULT_
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class StandardEnvironmentTests {
|
||||
class StandardEnvironmentTests {
|
||||
|
||||
private static final String ALLOWED_PROPERTY_NAME = "theanswer";
|
||||
private static final String ALLOWED_PROPERTY_VALUE = "42";
|
||||
@@ -81,8 +80,8 @@ public class StandardEnvironmentTests {
|
||||
assertThat(parent.getProperty("parentKey")).isEqualTo("parentVal");
|
||||
assertThat(parent.getProperty("bothKey")).isEqualTo("parentBothVal");
|
||||
|
||||
assertThat(child.getActiveProfiles()).isEqualTo(new String[]{"c1","c2"});
|
||||
assertThat(parent.getActiveProfiles()).isEqualTo(new String[]{"p1","p2"});
|
||||
assertThat(child.getActiveProfiles()).containsExactly("c1", "c2");
|
||||
assertThat(parent.getActiveProfiles()).containsExactly("p1", "p2");
|
||||
|
||||
child.merge(parent);
|
||||
|
||||
@@ -94,8 +93,8 @@ public class StandardEnvironmentTests {
|
||||
assertThat(parent.getProperty("parentKey")).isEqualTo("parentVal");
|
||||
assertThat(parent.getProperty("bothKey")).isEqualTo("parentBothVal");
|
||||
|
||||
assertThat(child.getActiveProfiles()).isEqualTo(new String[]{"c1","c2","p1","p2"});
|
||||
assertThat(parent.getActiveProfiles()).isEqualTo(new String[]{"p1","p2"});
|
||||
assertThat(child.getActiveProfiles()).containsExactly("c1", "c2", "p1", "p2");
|
||||
assertThat(parent.getActiveProfiles()).containsExactly("p1", "p2");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -121,16 +120,14 @@ public class StandardEnvironmentTests {
|
||||
|
||||
@Test
|
||||
void defaultProfilesContainsDefaultProfileByDefault() {
|
||||
assertThat(environment.getDefaultProfiles().length).isEqualTo(1);
|
||||
assertThat(environment.getDefaultProfiles()[0]).isEqualTo("default");
|
||||
assertThat(environment.getDefaultProfiles()).containsExactly("default");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setActiveProfiles() {
|
||||
environment.setActiveProfiles("local", "embedded");
|
||||
String[] activeProfiles = environment.getActiveProfiles();
|
||||
assertThat(activeProfiles).contains("local", "embedded");
|
||||
assertThat(activeProfiles.length).isEqualTo(2);
|
||||
assertThat(activeProfiles).containsExactly("local", "embedded");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -177,15 +174,12 @@ public class StandardEnvironmentTests {
|
||||
void addActiveProfile() {
|
||||
assertThat(environment.getActiveProfiles().length).isEqualTo(0);
|
||||
environment.setActiveProfiles("local", "embedded");
|
||||
assertThat(environment.getActiveProfiles()).contains("local", "embedded");
|
||||
assertThat(environment.getActiveProfiles().length).isEqualTo(2);
|
||||
assertThat(environment.getActiveProfiles()).containsExactly("local", "embedded");
|
||||
environment.addActiveProfile("p1");
|
||||
assertThat(environment.getActiveProfiles()).contains("p1");
|
||||
assertThat(environment.getActiveProfiles().length).isEqualTo(3);
|
||||
assertThat(environment.getActiveProfiles()).containsExactly("local", "embedded", "p1");
|
||||
environment.addActiveProfile("p2");
|
||||
environment.addActiveProfile("p3");
|
||||
assertThat(environment.getActiveProfiles()).contains("p2", "p3");
|
||||
assertThat(environment.getActiveProfiles().length).isEqualTo(5);
|
||||
assertThat(environment.getActiveProfiles()).containsExactly("local", "embedded", "p1", "p2", "p3");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -195,24 +189,17 @@ public class StandardEnvironmentTests {
|
||||
env.getPropertySources().addFirst(new MockPropertySource().withProperty(ACTIVE_PROFILES_PROPERTY_NAME, "p1"));
|
||||
assertThat(env.getProperty(ACTIVE_PROFILES_PROPERTY_NAME)).isEqualTo("p1");
|
||||
env.addActiveProfile("p2");
|
||||
assertThat(env.getActiveProfiles()).contains("p1", "p2");
|
||||
assertThat(env.getActiveProfiles()).containsExactly("p1", "p2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void reservedDefaultProfile() {
|
||||
assertThat(environment.getDefaultProfiles()).isEqualTo(new String[]{RESERVED_DEFAULT_PROFILE_NAME});
|
||||
System.setProperty(DEFAULT_PROFILES_PROPERTY_NAME, "d0");
|
||||
assertThat(environment.getDefaultProfiles()).isEqualTo(new String[]{"d0"});
|
||||
environment.setDefaultProfiles("d1", "d2");
|
||||
assertThat(environment.getDefaultProfiles()).isEqualTo(new String[]{"d1","d2"});
|
||||
System.clearProperty(DEFAULT_PROFILES_PROPERTY_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultProfileWithCircularPlaceholder() {
|
||||
assertThat(environment.getDefaultProfiles()).containsExactly(RESERVED_DEFAULT_PROFILE_NAME);
|
||||
try {
|
||||
System.setProperty(DEFAULT_PROFILES_PROPERTY_NAME, "${spring.profiles.default}");
|
||||
assertThatIllegalArgumentException().isThrownBy(environment::getDefaultProfiles);
|
||||
System.setProperty(DEFAULT_PROFILES_PROPERTY_NAME, "d0");
|
||||
assertThat(environment.getDefaultProfiles()).containsExactly("d0");
|
||||
environment.setDefaultProfiles("d1", "d2");
|
||||
assertThat(environment.getDefaultProfiles()).containsExactly("d1","d2");
|
||||
}
|
||||
finally {
|
||||
System.clearProperty(DEFAULT_PROFILES_PROPERTY_NAME);
|
||||
@@ -220,40 +207,23 @@ public class StandardEnvironmentTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void getActiveProfiles_systemPropertiesEmpty() {
|
||||
assertThat(environment.getActiveProfiles().length).isEqualTo(0);
|
||||
System.setProperty(ACTIVE_PROFILES_PROPERTY_NAME, "");
|
||||
assertThat(environment.getActiveProfiles().length).isEqualTo(0);
|
||||
System.clearProperty(ACTIVE_PROFILES_PROPERTY_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getActiveProfiles_fromSystemProperties() {
|
||||
System.setProperty(ACTIVE_PROFILES_PROPERTY_NAME, "foo");
|
||||
assertThat(Arrays.asList(environment.getActiveProfiles())).contains("foo");
|
||||
System.clearProperty(ACTIVE_PROFILES_PROPERTY_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getActiveProfiles_fromSystemProperties_withMultipleProfiles() {
|
||||
System.setProperty(ACTIVE_PROFILES_PROPERTY_NAME, "foo,bar");
|
||||
assertThat(environment.getActiveProfiles()).contains("foo", "bar");
|
||||
System.clearProperty(ACTIVE_PROFILES_PROPERTY_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getActiveProfiles_fromSystemProperties_withMultipleProfiles_withWhitespace() {
|
||||
System.setProperty(ACTIVE_PROFILES_PROPERTY_NAME, " bar , baz "); // notice whitespace
|
||||
assertThat(environment.getActiveProfiles()).contains("bar", "baz");
|
||||
System.clearProperty(ACTIVE_PROFILES_PROPERTY_NAME);
|
||||
void defaultProfileWithCircularPlaceholder() {
|
||||
try {
|
||||
System.setProperty(DEFAULT_PROFILES_PROPERTY_NAME, "${spring.profiles.default}");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(environment::getDefaultProfiles)
|
||||
.withMessage("Circular placeholder reference 'spring.profiles.default' in property definitions");
|
||||
}
|
||||
finally {
|
||||
System.clearProperty(DEFAULT_PROFILES_PROPERTY_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDefaultProfiles() {
|
||||
assertThat(environment.getDefaultProfiles()).isEqualTo(new String[] {RESERVED_DEFAULT_PROFILE_NAME});
|
||||
assertThat(environment.getDefaultProfiles()).containsExactly(RESERVED_DEFAULT_PROFILE_NAME);
|
||||
environment.getPropertySources().addFirst(new MockPropertySource().withProperty(DEFAULT_PROFILES_PROPERTY_NAME, "pd1"));
|
||||
assertThat(environment.getDefaultProfiles().length).isEqualTo(1);
|
||||
assertThat(Arrays.asList(environment.getDefaultProfiles())).contains("pd1");
|
||||
assertThat(environment.getDefaultProfiles()).containsExactly("pd1");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -261,82 +231,9 @@ public class StandardEnvironmentTests {
|
||||
environment.setDefaultProfiles();
|
||||
assertThat(environment.getDefaultProfiles().length).isEqualTo(0);
|
||||
environment.setDefaultProfiles("pd1");
|
||||
assertThat(Arrays.asList(environment.getDefaultProfiles())).contains("pd1");
|
||||
assertThat(environment.getDefaultProfiles()).containsExactly("pd1");
|
||||
environment.setDefaultProfiles("pd2", "pd3");
|
||||
assertThat(environment.getDefaultProfiles()).doesNotContain("pd1");
|
||||
assertThat(environment.getDefaultProfiles()).contains("pd2", "pd3");
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsProfiles_withEmptyArgumentList() {
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
environment::acceptsProfiles);
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsProfiles_withNullArgumentList() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> environment.acceptsProfiles((String[]) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsProfiles_withNullArgument() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> environment.acceptsProfiles((String) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsProfiles_withEmptyArgument() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> environment.acceptsProfiles(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsProfiles_activeProfileSetProgrammatically() {
|
||||
assertThat(environment.acceptsProfiles("p1", "p2")).isFalse();
|
||||
environment.setActiveProfiles("p1");
|
||||
assertThat(environment.acceptsProfiles("p1", "p2")).isTrue();
|
||||
environment.setActiveProfiles("p2");
|
||||
assertThat(environment.acceptsProfiles("p1", "p2")).isTrue();
|
||||
environment.setActiveProfiles("p1", "p2");
|
||||
assertThat(environment.acceptsProfiles("p1", "p2")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsProfiles_activeProfileSetViaProperty() {
|
||||
assertThat(environment.acceptsProfiles("p1")).isFalse();
|
||||
environment.getPropertySources().addFirst(new MockPropertySource().withProperty(ACTIVE_PROFILES_PROPERTY_NAME, "p1"));
|
||||
assertThat(environment.acceptsProfiles("p1")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsProfiles_defaultProfile() {
|
||||
assertThat(environment.acceptsProfiles("pd")).isFalse();
|
||||
environment.setDefaultProfiles("pd");
|
||||
assertThat(environment.acceptsProfiles("pd")).isTrue();
|
||||
environment.setActiveProfiles("p1");
|
||||
assertThat(environment.acceptsProfiles("pd")).isFalse();
|
||||
assertThat(environment.acceptsProfiles("p1")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsProfiles_withNotOperator() {
|
||||
assertThat(environment.acceptsProfiles("p1")).isFalse();
|
||||
assertThat(environment.acceptsProfiles("!p1")).isTrue();
|
||||
environment.addActiveProfile("p1");
|
||||
assertThat(environment.acceptsProfiles("p1")).isTrue();
|
||||
assertThat(environment.acceptsProfiles("!p1")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsProfiles_withInvalidNotOperator() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> environment.acceptsProfiles("p1", "!"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptsProfiles_withProfileExpression() {
|
||||
assertThat(environment.acceptsProfiles(Profiles.of("p1 & p2"))).isFalse();
|
||||
environment.addActiveProfile("p1");
|
||||
assertThat(environment.acceptsProfiles(Profiles.of("p1 & p2"))).isFalse();
|
||||
environment.addActiveProfile("p2");
|
||||
assertThat(environment.acceptsProfiles(Profiles.of("p1 & p2"))).isTrue();
|
||||
assertThat(environment.getDefaultProfiles()).containsExactly("pd2", "pd3");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -346,88 +243,99 @@ public class StandardEnvironmentTests {
|
||||
protected void validateProfile(String profile) {
|
||||
super.validateProfile(profile);
|
||||
if (profile.contains("-")) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid profile [" + profile + "]: must not contain dash character");
|
||||
throw new IllegalArgumentException("Invalid profile [" + profile + "]: must not contain dash character");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
env.addActiveProfile("validProfile"); // succeeds
|
||||
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
env.addActiveProfile("invalid-profile"))
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> env.addActiveProfile("invalid-profile"))
|
||||
.withMessage("Invalid profile [invalid-profile]: must not contain dash character");
|
||||
}
|
||||
|
||||
@Test
|
||||
void suppressGetenvAccessThroughSystemProperty() {
|
||||
System.setProperty("spring.getenv.ignore", "true");
|
||||
assertThat(environment.getSystemEnvironment().isEmpty()).isTrue();
|
||||
System.clearProperty("spring.getenv.ignore");
|
||||
try {
|
||||
System.setProperty("spring.getenv.ignore", "true");
|
||||
assertThat(environment.getSystemEnvironment()).isEmpty();
|
||||
}
|
||||
finally {
|
||||
System.clearProperty("spring.getenv.ignore");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void suppressGetenvAccessThroughSpringProperty() {
|
||||
SpringProperties.setProperty("spring.getenv.ignore", "true");
|
||||
assertThat(environment.getSystemEnvironment().isEmpty()).isTrue();
|
||||
SpringProperties.setProperty("spring.getenv.ignore", null);
|
||||
try {
|
||||
SpringProperties.setProperty("spring.getenv.ignore", "true");
|
||||
assertThat(environment.getSystemEnvironment()).isEmpty();
|
||||
}
|
||||
finally {
|
||||
SpringProperties.setProperty("spring.getenv.ignore", null);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void suppressGetenvAccessThroughSpringFlag() {
|
||||
SpringProperties.setFlag("spring.getenv.ignore");
|
||||
assertThat(environment.getSystemEnvironment().isEmpty()).isTrue();
|
||||
SpringProperties.setProperty("spring.getenv.ignore", null);
|
||||
try {
|
||||
SpringProperties.setFlag("spring.getenv.ignore");
|
||||
assertThat(environment.getSystemEnvironment()).isEmpty();
|
||||
}
|
||||
finally {
|
||||
SpringProperties.setProperty("spring.getenv.ignore", null);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getSystemProperties_withAndWithoutSecurityManager() {
|
||||
System.setProperty(ALLOWED_PROPERTY_NAME, ALLOWED_PROPERTY_VALUE);
|
||||
System.setProperty(DISALLOWED_PROPERTY_NAME, DISALLOWED_PROPERTY_VALUE);
|
||||
System.getProperties().put(STRING_PROPERTY_NAME, NON_STRING_PROPERTY_VALUE);
|
||||
System.getProperties().put(NON_STRING_PROPERTY_NAME, STRING_PROPERTY_VALUE);
|
||||
|
||||
{
|
||||
Map<?, ?> systemProperties = environment.getSystemProperties();
|
||||
assertThat(systemProperties).isNotNull();
|
||||
assertThat(System.getProperties()).isSameAs(systemProperties);
|
||||
assertThat(systemProperties.get(ALLOWED_PROPERTY_NAME)).isEqualTo(ALLOWED_PROPERTY_VALUE);
|
||||
assertThat(systemProperties.get(DISALLOWED_PROPERTY_NAME)).isEqualTo(DISALLOWED_PROPERTY_VALUE);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
SecurityManager oldSecurityManager = System.getSecurityManager();
|
||||
SecurityManager securityManager = new SecurityManager() {
|
||||
@Override
|
||||
public void checkPropertiesAccess() {
|
||||
// see https://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html#getProperties()
|
||||
throw new AccessControlException("Accessing the system properties is disallowed");
|
||||
}
|
||||
@Override
|
||||
public void checkPropertyAccess(String key) {
|
||||
// see https://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html#getProperty(java.lang.String)
|
||||
if (DISALLOWED_PROPERTY_NAME.equals(key)) {
|
||||
throw new AccessControlException(
|
||||
String.format("Accessing the system property [%s] is disallowed", DISALLOWED_PROPERTY_NAME));
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void checkPermission(Permission perm) {
|
||||
// allow everything else
|
||||
}
|
||||
};
|
||||
|
||||
SecurityManager originalSecurityManager = System.getSecurityManager();
|
||||
try {
|
||||
System.setProperty(ALLOWED_PROPERTY_NAME, ALLOWED_PROPERTY_VALUE);
|
||||
System.setProperty(DISALLOWED_PROPERTY_NAME, DISALLOWED_PROPERTY_VALUE);
|
||||
System.getProperties().put(STRING_PROPERTY_NAME, NON_STRING_PROPERTY_VALUE);
|
||||
System.getProperties().put(NON_STRING_PROPERTY_NAME, STRING_PROPERTY_VALUE);
|
||||
|
||||
{
|
||||
Map<?, ?> systemProperties = environment.getSystemProperties();
|
||||
assertThat(systemProperties).isNotNull();
|
||||
assertThat(System.getProperties()).isSameAs(systemProperties);
|
||||
assertThat(systemProperties.get(ALLOWED_PROPERTY_NAME)).isEqualTo(ALLOWED_PROPERTY_VALUE);
|
||||
assertThat(systemProperties.get(DISALLOWED_PROPERTY_NAME)).isEqualTo(DISALLOWED_PROPERTY_VALUE);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
SecurityManager securityManager = new SecurityManager() {
|
||||
@Override
|
||||
public void checkPropertiesAccess() {
|
||||
// see https://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html#getProperties()
|
||||
throw new AccessControlException("Accessing the system properties is disallowed");
|
||||
}
|
||||
@Override
|
||||
public void checkPropertyAccess(String key) {
|
||||
// see https://download.oracle.com/javase/1.5.0/docs/api/java/lang/System.html#getProperty(java.lang.String)
|
||||
if (DISALLOWED_PROPERTY_NAME.equals(key)) {
|
||||
throw new AccessControlException(
|
||||
String.format("Accessing the system property [%s] is disallowed", DISALLOWED_PROPERTY_NAME));
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void checkPermission(Permission perm) {
|
||||
// allow everything else
|
||||
}
|
||||
};
|
||||
|
||||
System.setSecurityManager(securityManager);
|
||||
|
||||
{
|
||||
Map<?, ?> systemProperties = environment.getSystemProperties();
|
||||
assertThat(systemProperties).isNotNull();
|
||||
assertThat(systemProperties).isInstanceOf(ReadOnlySystemAttributesMap.class);
|
||||
assertThat((String)systemProperties.get(ALLOWED_PROPERTY_NAME)).isEqualTo(ALLOWED_PROPERTY_VALUE);
|
||||
assertThat(systemProperties.get(ALLOWED_PROPERTY_NAME)).isEqualTo(ALLOWED_PROPERTY_VALUE);
|
||||
assertThat(systemProperties.get(DISALLOWED_PROPERTY_NAME)).isNull();
|
||||
|
||||
// nothing we can do here in terms of warning the user that there was
|
||||
@@ -441,12 +349,12 @@ public class StandardEnvironmentTests {
|
||||
// the user that under these very special conditions (non-object key +
|
||||
// SecurityManager that disallows access to system properties), they
|
||||
// cannot do what they're attempting.
|
||||
assertThatIllegalArgumentException().as("searching with non-string key against ReadOnlySystemAttributesMap").isThrownBy(() ->
|
||||
systemProperties.get(NON_STRING_PROPERTY_NAME));
|
||||
assertThatIllegalArgumentException().as("searching with non-string key against ReadOnlySystemAttributesMap")
|
||||
.isThrownBy(() -> systemProperties.get(NON_STRING_PROPERTY_NAME));
|
||||
}
|
||||
}
|
||||
finally {
|
||||
System.setSecurityManager(oldSecurityManager);
|
||||
System.setSecurityManager(originalSecurityManager);
|
||||
System.clearProperty(ALLOWED_PROPERTY_NAME);
|
||||
System.clearProperty(DISALLOWED_PROPERTY_NAME);
|
||||
System.getProperties().remove(STRING_PROPERTY_NAME);
|
||||
@@ -499,4 +407,250 @@ public class StandardEnvironmentTests {
|
||||
EnvironmentTestUtils.getModifiableSystemEnvironment().remove(DISALLOWED_PROPERTY_NAME);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class GetActiveProfiles {
|
||||
|
||||
@Test
|
||||
void systemPropertiesEmpty() {
|
||||
assertThat(environment.getActiveProfiles()).isEmpty();
|
||||
try {
|
||||
System.setProperty(ACTIVE_PROFILES_PROPERTY_NAME, "");
|
||||
assertThat(environment.getActiveProfiles()).isEmpty();
|
||||
}
|
||||
finally {
|
||||
System.clearProperty(ACTIVE_PROFILES_PROPERTY_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromSystemProperties() {
|
||||
try {
|
||||
System.setProperty(ACTIVE_PROFILES_PROPERTY_NAME, "foo");
|
||||
assertThat(environment.getActiveProfiles()).containsExactly("foo");
|
||||
}
|
||||
finally {
|
||||
System.clearProperty(ACTIVE_PROFILES_PROPERTY_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromSystemProperties_withMultipleProfiles() {
|
||||
try {
|
||||
System.setProperty(ACTIVE_PROFILES_PROPERTY_NAME, "foo,bar");
|
||||
assertThat(environment.getActiveProfiles()).containsExactly("foo", "bar");
|
||||
}
|
||||
finally {
|
||||
System.clearProperty(ACTIVE_PROFILES_PROPERTY_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromSystemProperties_withMultipleProfiles_withWhitespace() {
|
||||
try {
|
||||
System.setProperty(ACTIVE_PROFILES_PROPERTY_NAME, " bar , baz "); // notice whitespace
|
||||
assertThat(environment.getActiveProfiles()).containsExactly("bar", "baz");
|
||||
}
|
||||
finally {
|
||||
System.clearProperty(ACTIVE_PROFILES_PROPERTY_NAME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class AcceptsProfilesTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void withEmptyArgumentList() {
|
||||
assertThatIllegalArgumentException().isThrownBy(environment::acceptsProfiles);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void withNullArgumentList() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> environment.acceptsProfiles((String[]) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void withNullArgument() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> environment.acceptsProfiles((String) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void withEmptyArgument() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> environment.acceptsProfiles(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void activeProfileSetProgrammatically() {
|
||||
assertThat(environment.acceptsProfiles("p1", "p2")).isFalse();
|
||||
environment.setActiveProfiles("p1");
|
||||
assertThat(environment.acceptsProfiles("p1", "p2")).isTrue();
|
||||
environment.setActiveProfiles("p2");
|
||||
assertThat(environment.acceptsProfiles("p1", "p2")).isTrue();
|
||||
environment.setActiveProfiles("p1", "p2");
|
||||
assertThat(environment.acceptsProfiles("p1", "p2")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void activeProfileSetViaProperty() {
|
||||
assertThat(environment.acceptsProfiles("p1")).isFalse();
|
||||
environment.getPropertySources().addFirst(new MockPropertySource().withProperty(ACTIVE_PROFILES_PROPERTY_NAME, "p1"));
|
||||
assertThat(environment.acceptsProfiles("p1")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void defaultProfile() {
|
||||
assertThat(environment.acceptsProfiles("pd")).isFalse();
|
||||
environment.setDefaultProfiles("pd");
|
||||
assertThat(environment.acceptsProfiles("pd")).isTrue();
|
||||
environment.setActiveProfiles("p1");
|
||||
assertThat(environment.acceptsProfiles("pd")).isFalse();
|
||||
assertThat(environment.acceptsProfiles("p1")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void withNotOperator() {
|
||||
assertThat(environment.acceptsProfiles("p1")).isFalse();
|
||||
assertThat(environment.acceptsProfiles("!p1")).isTrue();
|
||||
environment.addActiveProfile("p1");
|
||||
assertThat(environment.acceptsProfiles("p1")).isTrue();
|
||||
assertThat(environment.acceptsProfiles("!p1")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void withInvalidNotOperator() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> environment.acceptsProfiles("p1", "!"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void withProfileExpression() {
|
||||
assertThat(environment.acceptsProfiles(Profiles.of("p1 & p2"))).isFalse();
|
||||
environment.addActiveProfile("p1");
|
||||
assertThat(environment.acceptsProfiles(Profiles.of("p1 & p2"))).isFalse();
|
||||
environment.addActiveProfile("p2");
|
||||
assertThat(environment.acceptsProfiles(Profiles.of("p1 & p2"))).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class MatchesProfilesTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void withEmptyArgumentList() {
|
||||
assertThatIllegalArgumentException().isThrownBy(environment::matchesProfiles);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void withNullArgumentList() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> environment.matchesProfiles((String[]) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void withNullArgument() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> environment.matchesProfiles((String) null));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> environment.matchesProfiles("p1", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void withEmptyArgument() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> environment.matchesProfiles(""));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> environment.matchesProfiles("p1", ""));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> environment.matchesProfiles("p1", " "));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void withInvalidNotOperator() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> environment.matchesProfiles("p1", "!"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void withInvalidCompoundExpressionGrouping() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> environment.matchesProfiles("p1 | p2 & p3"));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> environment.matchesProfiles("p1 & p2 | p3"));
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> environment.matchesProfiles("p1 & (p2 | p3) | p4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void activeProfileSetProgrammatically() {
|
||||
assertThat(environment.matchesProfiles("p1", "p2")).isFalse();
|
||||
|
||||
environment.setActiveProfiles("p1");
|
||||
assertThat(environment.matchesProfiles("p1", "p2")).isTrue();
|
||||
|
||||
environment.setActiveProfiles("p2");
|
||||
assertThat(environment.matchesProfiles("p1", "p2")).isTrue();
|
||||
|
||||
environment.setActiveProfiles("p1", "p2");
|
||||
assertThat(environment.matchesProfiles("p1", "p2")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void activeProfileSetViaProperty() {
|
||||
assertThat(environment.matchesProfiles("p1")).isFalse();
|
||||
|
||||
environment.getPropertySources().addFirst(new MockPropertySource().withProperty(ACTIVE_PROFILES_PROPERTY_NAME, "p1"));
|
||||
assertThat(environment.matchesProfiles("p1")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void defaultProfile() {
|
||||
assertThat(environment.matchesProfiles("pd")).isFalse();
|
||||
|
||||
environment.setDefaultProfiles("pd");
|
||||
assertThat(environment.matchesProfiles("pd")).isTrue();
|
||||
|
||||
environment.setActiveProfiles("p1");
|
||||
assertThat(environment.matchesProfiles("pd")).isFalse();
|
||||
assertThat(environment.matchesProfiles("p1")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void withNotOperator() {
|
||||
assertThat(environment.matchesProfiles("p1")).isFalse();
|
||||
assertThat(environment.matchesProfiles("!p1")).isTrue();
|
||||
|
||||
environment.addActiveProfile("p1");
|
||||
assertThat(environment.matchesProfiles("p1")).isTrue();
|
||||
assertThat(environment.matchesProfiles("!p1")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void withProfileExpressions() {
|
||||
assertThat(environment.matchesProfiles("p1 & p2")).isFalse();
|
||||
|
||||
environment.addActiveProfile("p1");
|
||||
assertThat(environment.matchesProfiles("p1 | p2")).isTrue();
|
||||
assertThat(environment.matchesProfiles("p1 & p2")).isFalse();
|
||||
|
||||
environment.addActiveProfile("p2");
|
||||
assertThat(environment.matchesProfiles("p1 & p2")).isTrue();
|
||||
assertThat(environment.matchesProfiles("p1 | p2")).isTrue();
|
||||
assertThat(environment.matchesProfiles("foo | p1", "p2")).isTrue();
|
||||
assertThat(environment.matchesProfiles("foo | p2", "p1")).isTrue();
|
||||
assertThat(environment.matchesProfiles("foo | (p2 & p1)")).isTrue();
|
||||
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-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,15 +17,26 @@
|
||||
package org.springframework.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
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.springframework.util.ObjectUtils.isEmpty;
|
||||
|
||||
@@ -816,13 +827,156 @@ class ObjectUtilsTests {
|
||||
.withMessage("Constant [bogus] does not exist in enum type org.springframework.util.ObjectUtilsTests$Tropes");
|
||||
}
|
||||
|
||||
private void assertEqualHashCodes(int expected, Object array) {
|
||||
|
||||
private static void assertEqualHashCodes(int expected, Object array) {
|
||||
int actual = ObjectUtils.nullSafeHashCode(array);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
assertThat(array.hashCode() != actual).isTrue();
|
||||
assertThat(array.hashCode()).isNotEqualTo(actual);
|
||||
}
|
||||
|
||||
|
||||
enum Tropes {FOO, BAR, baz}
|
||||
|
||||
|
||||
@Nested
|
||||
class NullSafeConciseToStringTests {
|
||||
|
||||
private final String truncated = " (truncated)...";
|
||||
private final int truncatedLength = 100 + truncated.length();
|
||||
|
||||
@Test
|
||||
void nullSafeConciseToStringForNull() {
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(null)).isEqualTo("null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullSafeConciseToStringForClass() {
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(String.class)).isEqualTo("java.lang.String");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullSafeConciseToStringForStrings() {
|
||||
String repeat100 = repeat("X", 100);
|
||||
String repeat101 = repeat("X", 101);
|
||||
|
||||
assertThat(ObjectUtils.nullSafeConciseToString("")).isEqualTo("");
|
||||
assertThat(ObjectUtils.nullSafeConciseToString("foo")).isEqualTo("foo");
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(repeat100)).isEqualTo(repeat100);
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(repeat101)).hasSize(truncatedLength).endsWith(truncated);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullSafeConciseToStringForStringBuilders() {
|
||||
String repeat100 = repeat("X", 100);
|
||||
String repeat101 = repeat("X", 101);
|
||||
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(new StringBuilder("foo"))).isEqualTo("foo");
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(new StringBuilder(repeat100))).isEqualTo(repeat100);
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(new StringBuilder(repeat101))).hasSize(truncatedLength).endsWith(truncated);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullSafeConciseToStringForEnum() {
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(Tropes.FOO)).isEqualTo("FOO");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullSafeConciseToStringForNumber() {
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(42L)).isEqualTo("42");
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(99.1234D)).isEqualTo("99.1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullSafeConciseToStringForDate() {
|
||||
Date date = new Date();
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(date)).isEqualTo(date.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullSafeConciseToStringForTemporal() {
|
||||
LocalDate localDate = LocalDate.now();
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(localDate)).isEqualTo(localDate.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullSafeConciseToStringForUUID() {
|
||||
UUID id = UUID.randomUUID();
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(id)).isEqualTo(id.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullSafeConciseToStringForURI() {
|
||||
String uri = "https://www.example.com/?foo=1&bar=2&baz=3";
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(URI.create(uri))).isEqualTo(uri);
|
||||
|
||||
uri += "&qux=" + repeat("4", 60);
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(URI.create(uri)))
|
||||
.hasSize(truncatedLength)
|
||||
.startsWith(uri.subSequence(0, 100))
|
||||
.endsWith(truncated);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullSafeConciseToStringForURL() throws Exception {
|
||||
String url = "https://www.example.com/?foo=1&bar=2&baz=3";
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(new URL(url))).isEqualTo(url);
|
||||
|
||||
url += "&qux=" + repeat("4", 60);
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(new URL(url)))
|
||||
.hasSize(truncatedLength)
|
||||
.startsWith(url.subSequence(0, 100))
|
||||
.endsWith(truncated);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullSafeConciseToStringForLocale() {
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(Locale.GERMANY)).isEqualTo("de_DE");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullSafeConciseToStringForArraysAndCollections() {
|
||||
List<String> list = Arrays.asList("a", "b", "c");
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(new int[][] {{1, 2}, {3, 4}})).startsWith(prefix(int[][].class));
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(list.toArray(new Object[0]))).startsWith(prefix(Object[].class));
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(list.toArray(new String[0]))).startsWith(prefix(String[].class));
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(new ArrayList<>(list))).startsWith(prefix(ArrayList.class));
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(new HashSet<>(list))).startsWith(prefix(HashSet.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullSafeConciseToStringForCustomTypes() {
|
||||
class ExplosiveType {
|
||||
@Override
|
||||
public String toString() {
|
||||
throw new UnsupportedOperationException("no-go");
|
||||
}
|
||||
}
|
||||
ExplosiveType explosiveType = new ExplosiveType();
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(explosiveType::toString);
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(explosiveType)).startsWith(prefix(ExplosiveType.class));
|
||||
|
||||
class WordyType {
|
||||
@Override
|
||||
public String toString() {
|
||||
return repeat("blah blah", 20);
|
||||
}
|
||||
}
|
||||
WordyType wordyType = new WordyType();
|
||||
assertThat(wordyType).asString().hasSizeGreaterThanOrEqualTo(180 /* 9x20 */);
|
||||
assertThat(ObjectUtils.nullSafeConciseToString(wordyType)).startsWith(prefix(WordyType.class));
|
||||
}
|
||||
|
||||
private String repeat(String str, int count) {
|
||||
String result = "";
|
||||
for (int i = 0; i < count; i++) {
|
||||
result += str;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String prefix(Class<?> clazz) {
|
||||
return clazz.getTypeName() + "@";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,8 @@ import java.util.Locale;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
@@ -785,4 +787,26 @@ class StringUtilsTests {
|
||||
assertThat(StringUtils.collectionToCommaDelimitedString(Collections.singletonList(null))).isEqualTo("null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void truncatePreconditions() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> StringUtils.truncate("foo", 0))
|
||||
.withMessage("Truncation threshold must be a positive number: 0");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> StringUtils.truncate("foo", -99))
|
||||
.withMessage("Truncation threshold must be a positive number: -99");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource(delimiterString = "-->", value = {
|
||||
"'' --> ''",
|
||||
"aardvark --> aardvark",
|
||||
"aardvark12 --> aardvark12",
|
||||
"aardvark123 --> aardvark12 (truncated)...",
|
||||
"aardvark, bird, cat --> aardvark, (truncated)..."
|
||||
})
|
||||
void truncate(String text, String truncated) {
|
||||
assertThat(StringUtils.truncate(text, 10)).isEqualTo(truncated);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+39
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.expression;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -24,12 +25,21 @@ import org.springframework.lang.Nullable;
|
||||
* Expressions are executed in an evaluation context. It is in this context that
|
||||
* references are resolved when encountered during expression evaluation.
|
||||
*
|
||||
* <p>There is a default implementation of this EvaluationContext interface:
|
||||
* {@link org.springframework.expression.spel.support.StandardEvaluationContext}
|
||||
* which can be extended, rather than having to implement everything manually.
|
||||
* <p>There are two default implementations of this interface.
|
||||
* <ul>
|
||||
* <li>{@link org.springframework.expression.spel.support.SimpleEvaluationContext
|
||||
* SimpleEvaluationContext}: a simpler builder-style {@code EvaluationContext}
|
||||
* variant for data-binding purposes, which allows for opting into several SpEL
|
||||
* features as needed.</li>
|
||||
* <li>{@link org.springframework.expression.spel.support.StandardEvaluationContext
|
||||
* StandardEvaluationContext}: a powerful and highly configurable {@code EvaluationContext}
|
||||
* implementation, which can be extended, rather than having to implement everything
|
||||
* manually.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface EvaluationContext {
|
||||
@@ -85,7 +95,30 @@ public interface EvaluationContext {
|
||||
OperatorOverloader getOperatorOverloader();
|
||||
|
||||
/**
|
||||
* Set a named variable within this evaluation context to a specified value.
|
||||
* Assign the value created by the specified {@link Supplier} to a named variable
|
||||
* within this evaluation context.
|
||||
* <p>In contrast to {@link #setVariable(String, Object)}, this method should only
|
||||
* be invoked to support the assignment operator ({@code =}) within an expression.
|
||||
* <p>By default, this method delegates to {@code setVariable(String, Object)},
|
||||
* providing the value created by the {@code valueSupplier}. Concrete implementations
|
||||
* may override this <em>default</em> method to provide different semantics.
|
||||
* @param name the name of the variable to assign
|
||||
* @param valueSupplier the supplier of the value to be assigned to the variable
|
||||
* @return a {@link TypedValue} wrapping the assigned value
|
||||
* @since 5.2.24
|
||||
*/
|
||||
default TypedValue assignVariable(String name, Supplier<TypedValue> valueSupplier) {
|
||||
TypedValue typedValue = valueSupplier.get();
|
||||
setVariable(name, typedValue.getValue());
|
||||
return typedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a named variable in this evaluation context to a specified value.
|
||||
* <p>In contrast to {@link #assignVariable(String, Supplier)}, this method
|
||||
* should only be invoked programmatically when interacting directly with the
|
||||
* {@code EvaluationContext} — for example, to provide initial
|
||||
* configuration for the context.
|
||||
* @param name the name of the variable to set
|
||||
* @param value the value to be placed in the variable
|
||||
*/
|
||||
@@ -93,7 +126,7 @@ public interface EvaluationContext {
|
||||
|
||||
/**
|
||||
* Look up a named variable within this evaluation context.
|
||||
* @param name variable to lookup
|
||||
* @param name the name of the variable to look up
|
||||
* @return the value of the variable, or {@code null} if not found
|
||||
*/
|
||||
@Nullable
|
||||
|
||||
+5
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,6 +26,7 @@ import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.ParseException;
|
||||
import org.springframework.expression.ParserContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An expression parser that understands templates. It can be subclassed by expression
|
||||
@@ -34,6 +35,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author Keith Donald
|
||||
* @author Juergen Hoeller
|
||||
* @author Andy Clement
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
*/
|
||||
public abstract class TemplateAwareExpressionParser implements ExpressionParser {
|
||||
@@ -46,9 +48,11 @@ public abstract class TemplateAwareExpressionParser implements ExpressionParser
|
||||
@Override
|
||||
public Expression parseExpression(String expressionString, @Nullable ParserContext context) throws ParseException {
|
||||
if (context != null && context.isTemplate()) {
|
||||
Assert.notNull(expressionString, "'expressionString' must not be null");
|
||||
return parseTemplate(expressionString, context);
|
||||
}
|
||||
else {
|
||||
Assert.hasText(expressionString, "'expressionString' must not be null or blank");
|
||||
return doParseExpression(expressionString, context);
|
||||
}
|
||||
}
|
||||
|
||||
+29
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,6 +23,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
@@ -38,18 +39,19 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* An ExpressionState is for maintaining per-expression-evaluation state, any changes to
|
||||
* it are not seen by other expressions but it gives a place to hold local variables and
|
||||
* ExpressionState is for maintaining per-expression-evaluation state: any changes to
|
||||
* it are not seen by other expressions, but it gives a place to hold local variables and
|
||||
* for component expressions in a compound expression to communicate state. This is in
|
||||
* contrast to the EvaluationContext, which is shared amongst expression evaluations, and
|
||||
* any changes to it will be seen by other expressions or any code that chooses to ask
|
||||
* questions of the context.
|
||||
*
|
||||
* <p>It also acts as a place for to define common utility routines that the various AST
|
||||
* <p>It also acts as a place to define common utility routines that the various AST
|
||||
* nodes might need.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
*/
|
||||
public class ExpressionState {
|
||||
@@ -138,6 +140,29 @@ public class ExpressionState {
|
||||
return this.scopeRootObjects.element();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign the value created by the specified {@link Supplier} to a named variable
|
||||
* within the evaluation context.
|
||||
* <p>In contrast to {@link #setVariable(String, Object)}, this method should
|
||||
* only be invoked to support assignment within an expression.
|
||||
* @param name the name of the variable to assign
|
||||
* @param valueSupplier the supplier of the value to be assigned to the variable
|
||||
* @return a {@link TypedValue} wrapping the assigned value
|
||||
* @since 5.2.24
|
||||
* @see EvaluationContext#assignVariable(String, Supplier)
|
||||
*/
|
||||
public TypedValue assignVariable(String name, Supplier<TypedValue> valueSupplier) {
|
||||
return this.relatedContext.assignVariable(name, valueSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a named variable in the evaluation context to a specified value.
|
||||
* <p>In contrast to {@link #assignVariable(String, Supplier)}, this method
|
||||
* should only be invoked programmatically.
|
||||
* @param name the name of the variable to set
|
||||
* @param value the value to be placed in the variable
|
||||
* @see EvaluationContext#setVariable(String, Object)
|
||||
*/
|
||||
public void setVariable(String name, @Nullable Object value) {
|
||||
this.relatedContext.setVariable(name, value);
|
||||
}
|
||||
|
||||
+16
-4
@@ -266,13 +266,25 @@ public enum SpelMessage {
|
||||
MAX_ARRAY_ELEMENTS_THRESHOLD_EXCEEDED(Kind.ERROR, 1075,
|
||||
"Array declares too many elements, exceeding the threshold of ''{0}''"),
|
||||
|
||||
/** @since 5.3.26 */
|
||||
/** @since 5.2.23 */
|
||||
MAX_REPEATED_TEXT_SIZE_EXCEEDED(Kind.ERROR, 1076,
|
||||
"Repeated text results in too many characters, exceeding the threshold of ''{0}''"),
|
||||
"Repeated text is too long, exceeding the threshold of ''{0}'' characters"),
|
||||
|
||||
/** @since 5.3.26 */
|
||||
/** @since 5.2.23 */
|
||||
MAX_REGEX_LENGTH_EXCEEDED(Kind.ERROR, 1077,
|
||||
"Regular expression contains too many characters, exceeding the threshold of ''{0}''");
|
||||
"Regular expression is too long, exceeding the threshold of ''{0}'' characters"),
|
||||
|
||||
/** @since 5.2.24 */
|
||||
MAX_CONCATENATED_STRING_LENGTH_EXCEEDED(Kind.ERROR, 1078,
|
||||
"Concatenated string is too long, exceeding the threshold of ''{0}'' characters"),
|
||||
|
||||
/** @since 5.2.24 */
|
||||
MAX_EXPRESSION_LENGTH_EXCEEDED(Kind.ERROR, 1079,
|
||||
"SpEL expression is too long, exceeding the threshold of ''{0}'' characters"),
|
||||
|
||||
/** @since 5.2.24 */
|
||||
VARIABLE_ASSIGNMENT_NOT_SUPPORTED(Kind.ERROR, 1080,
|
||||
"Assignment to variable ''{0}'' is not supported");
|
||||
|
||||
|
||||
private final Kind kind;
|
||||
|
||||
+36
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -30,6 +30,12 @@ import org.springframework.lang.Nullable;
|
||||
*/
|
||||
public class SpelParserConfiguration {
|
||||
|
||||
/**
|
||||
* Default maximum length permitted for a SpEL expression.
|
||||
* @since 5.2.24
|
||||
*/
|
||||
private static final int DEFAULT_MAX_EXPRESSION_LENGTH = 10_000;
|
||||
|
||||
/** System property to configure the default compiler mode for SpEL expression parsers: {@value}. */
|
||||
public static final String SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME = "spring.expression.compiler.mode";
|
||||
|
||||
@@ -54,6 +60,8 @@ public class SpelParserConfiguration {
|
||||
|
||||
private final int maximumAutoGrowSize;
|
||||
|
||||
private final int maximumExpressionLength;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code SpelParserConfiguration} instance with default settings.
|
||||
@@ -102,11 +110,30 @@ public class SpelParserConfiguration {
|
||||
public SpelParserConfiguration(@Nullable SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader,
|
||||
boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize) {
|
||||
|
||||
this(compilerMode, compilerClassLoader, autoGrowNullReferences, autoGrowCollections,
|
||||
maximumAutoGrowSize, DEFAULT_MAX_EXPRESSION_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@code SpelParserConfiguration} instance.
|
||||
* @param compilerMode the compiler mode that parsers using this configuration object should use
|
||||
* @param compilerClassLoader the ClassLoader to use as the basis for expression compilation
|
||||
* @param autoGrowNullReferences if null references should automatically grow
|
||||
* @param autoGrowCollections if collections should automatically grow
|
||||
* @param maximumAutoGrowSize the maximum size that a collection can auto grow
|
||||
* @param maximumExpressionLength the maximum length of a SpEL expression;
|
||||
* must be a positive number
|
||||
* @since 5.2.25
|
||||
*/
|
||||
public SpelParserConfiguration(@Nullable SpelCompilerMode compilerMode, @Nullable ClassLoader compilerClassLoader,
|
||||
boolean autoGrowNullReferences, boolean autoGrowCollections, int maximumAutoGrowSize, int maximumExpressionLength) {
|
||||
|
||||
this.compilerMode = (compilerMode != null ? compilerMode : defaultCompilerMode);
|
||||
this.compilerClassLoader = compilerClassLoader;
|
||||
this.autoGrowNullReferences = autoGrowNullReferences;
|
||||
this.autoGrowCollections = autoGrowCollections;
|
||||
this.maximumAutoGrowSize = maximumAutoGrowSize;
|
||||
this.maximumExpressionLength = maximumExpressionLength;
|
||||
}
|
||||
|
||||
|
||||
@@ -146,4 +173,12 @@ public class SpelParserConfiguration {
|
||||
return this.maximumAutoGrowSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum number of characters that a SpEL expression can contain.
|
||||
* @since 5.2.25
|
||||
*/
|
||||
public int getMaximumExpressionLength() {
|
||||
return this.maximumExpressionLength;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,6 +27,7 @@ import org.springframework.expression.spel.ExpressionState;
|
||||
* <p>Example: 'someNumberProperty=42'
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
*/
|
||||
public class Assign extends SpelNodeImpl {
|
||||
@@ -38,9 +39,7 @@ public class Assign extends SpelNodeImpl {
|
||||
|
||||
@Override
|
||||
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
|
||||
TypedValue newValue = this.children[1].getValueInternal(state);
|
||||
getChild(0).setValue(state, newValue.getValue());
|
||||
return newValue;
|
||||
return this.children[0].setValueInternal(state, () -> this.children[1].getValueInternal(state));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+9
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.expression.spel.ast;
|
||||
|
||||
import java.util.StringJoiner;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.asm.MethodVisitor;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
@@ -24,13 +25,13 @@ import org.springframework.expression.TypedValue;
|
||||
import org.springframework.expression.spel.CodeFlow;
|
||||
import org.springframework.expression.spel.ExpressionState;
|
||||
import org.springframework.expression.spel.SpelEvaluationException;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a DOT separated expression sequence, such as
|
||||
* {@code 'property1.property2.methodOne()'}.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
*/
|
||||
public class CompoundExpression extends SpelNodeImpl {
|
||||
@@ -95,8 +96,12 @@ public class CompoundExpression extends SpelNodeImpl {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(ExpressionState state, @Nullable Object value) throws EvaluationException {
|
||||
getValueRef(state).setValue(value);
|
||||
public TypedValue setValueInternal(ExpressionState state, Supplier<TypedValue> valueSupplier)
|
||||
throws EvaluationException {
|
||||
|
||||
TypedValue typedValue = valueSupplier.get();
|
||||
getValueRef(state).setValue(typedValue.getValue());
|
||||
return typedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+10
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,6 +25,7 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.asm.MethodVisitor;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
@@ -45,11 +46,12 @@ import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* An Indexer can index into some proceeding structure to access a particular piece of it.
|
||||
* Supported structures are: strings / collections (lists/sets) / arrays.
|
||||
* <p>Supported structures are: strings / collections (lists/sets) / arrays.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
*/
|
||||
// TODO support multidimensional arrays
|
||||
@@ -102,8 +104,12 @@ public class Indexer extends SpelNodeImpl {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(ExpressionState state, @Nullable Object newValue) throws EvaluationException {
|
||||
getValueRef(state).setValue(newValue);
|
||||
public TypedValue setValueInternal(ExpressionState state, Supplier<TypedValue> valueSupplier)
|
||||
throws EvaluationException {
|
||||
|
||||
TypedValue typedValue = valueSupplier.get();
|
||||
getValueRef(state).setValue(typedValue.getValue());
|
||||
return typedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ public class OpMultiply extends Operator {
|
||||
|
||||
/**
|
||||
* Maximum number of characters permitted in repeated text.
|
||||
* @since 5.3.26
|
||||
* @since 5.2.23
|
||||
*/
|
||||
private static final int MAX_REPEATED_TEXT_SIZE = 256;
|
||||
|
||||
|
||||
+39
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,6 +27,8 @@ import org.springframework.expression.TypeConverter;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.expression.spel.CodeFlow;
|
||||
import org.springframework.expression.spel.ExpressionState;
|
||||
import org.springframework.expression.spel.SpelEvaluationException;
|
||||
import org.springframework.expression.spel.SpelMessage;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.NumberUtils;
|
||||
@@ -46,10 +48,18 @@ import org.springframework.util.NumberUtils;
|
||||
* @author Juergen Hoeller
|
||||
* @author Ivo Smid
|
||||
* @author Giovanni Dall'Oglio Risso
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
*/
|
||||
public class OpPlus extends Operator {
|
||||
|
||||
/**
|
||||
* Maximum number of characters permitted in a concatenated string.
|
||||
* @since 5.2.24
|
||||
*/
|
||||
private static final int MAX_CONCATENATED_STRING_LENGTH = 100_000;
|
||||
|
||||
|
||||
public OpPlus(int startPos, int endPos, SpelNodeImpl... operands) {
|
||||
super("+", startPos, endPos, operands);
|
||||
Assert.notEmpty(operands, "Operands must not be empty");
|
||||
@@ -123,22 +133,45 @@ public class OpPlus extends Operator {
|
||||
|
||||
if (leftOperand instanceof String && rightOperand instanceof String) {
|
||||
this.exitTypeDescriptor = "Ljava/lang/String";
|
||||
return new TypedValue((String) leftOperand + rightOperand);
|
||||
String leftString = (String) leftOperand;
|
||||
String rightString = (String) rightOperand;
|
||||
checkStringLength(leftString);
|
||||
checkStringLength(rightString);
|
||||
return concatenate(leftString, rightString);
|
||||
}
|
||||
|
||||
if (leftOperand instanceof String) {
|
||||
return new TypedValue(
|
||||
leftOperand + (rightOperand == null ? "null" : convertTypedValueToString(operandTwoValue, state)));
|
||||
String leftString = (String) leftOperand;
|
||||
checkStringLength(leftString);
|
||||
String rightString = (rightOperand == null ? "null" : convertTypedValueToString(operandTwoValue, state));
|
||||
checkStringLength(rightString);
|
||||
return concatenate(leftString, rightString);
|
||||
}
|
||||
|
||||
if (rightOperand instanceof String) {
|
||||
return new TypedValue(
|
||||
(leftOperand == null ? "null" : convertTypedValueToString(operandOneValue, state)) + rightOperand);
|
||||
String rightString = (String) rightOperand;
|
||||
checkStringLength(rightString);
|
||||
String leftString = (leftOperand == null ? "null" : convertTypedValueToString(operandOneValue, state));
|
||||
checkStringLength(leftString);
|
||||
return concatenate(leftString, rightString);
|
||||
}
|
||||
|
||||
return state.operate(Operation.ADD, leftOperand, rightOperand);
|
||||
}
|
||||
|
||||
private void checkStringLength(String string) {
|
||||
if (string.length() > MAX_CONCATENATED_STRING_LENGTH) {
|
||||
throw new SpelEvaluationException(getStartPosition(),
|
||||
SpelMessage.MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, MAX_CONCATENATED_STRING_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
private TypedValue concatenate(String leftString, String rightString) {
|
||||
String result = leftString + rightString;
|
||||
checkStringLength(result);
|
||||
return new TypedValue(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toStringAST() {
|
||||
if (this.children.length < 2) { // unary plus
|
||||
|
||||
+4
-4
@@ -45,16 +45,16 @@ public class OperatorMatches extends Operator {
|
||||
|
||||
/**
|
||||
* Maximum number of characters permitted in a regular expression.
|
||||
* @since 5.3.26
|
||||
* @since 5.2.23
|
||||
*/
|
||||
private static final int MAX_REGEX_LENGTH = 256;
|
||||
private static final int MAX_REGEX_LENGTH = 1000;
|
||||
|
||||
private final ConcurrentMap<String, Pattern> patternCache;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link OperatorMatches} instance.
|
||||
* @deprecated as of Spring Framework 5.3.26 in favor of invoking
|
||||
* @deprecated as of Spring Framework 5.2.23 in favor of invoking
|
||||
* {@link #OperatorMatches(ConcurrentMap, int, int, SpelNodeImpl...)}
|
||||
* with a shared pattern cache instead
|
||||
*/
|
||||
@@ -65,7 +65,7 @@ public class OperatorMatches extends Operator {
|
||||
|
||||
/**
|
||||
* Create a new {@link OperatorMatches} instance with a shared pattern cache.
|
||||
* @since 5.3.26
|
||||
* @since 5.2.23
|
||||
*/
|
||||
public OperatorMatches(ConcurrentMap<String, Pattern> patternCache, int startPos, int endPos, SpelNodeImpl... operands) {
|
||||
super("matches", startPos, endPos, operands);
|
||||
|
||||
+9
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,6 +21,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.asm.Label;
|
||||
import org.springframework.asm.MethodVisitor;
|
||||
@@ -46,6 +47,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @author Clark Duplichien
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
*/
|
||||
public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||
@@ -147,8 +149,12 @@ public class PropertyOrFieldReference extends SpelNodeImpl {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(ExpressionState state, @Nullable Object newValue) throws EvaluationException {
|
||||
writeProperty(state.getActiveContextObject(), state.getEvaluationContext(), this.name, newValue);
|
||||
public TypedValue setValueInternal(ExpressionState state, Supplier<TypedValue> valueSupplier)
|
||||
throws EvaluationException {
|
||||
|
||||
TypedValue typedValue = valueSupplier.get();
|
||||
writeProperty(state.getActiveContextObject(), state.getEvaluationContext(), this.name, typedValue.getValue());
|
||||
return typedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+28
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.expression.spel.ast;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.asm.MethodVisitor;
|
||||
import org.springframework.asm.Opcodes;
|
||||
@@ -40,6 +41,7 @@ import org.springframework.util.ObjectUtils;
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
*/
|
||||
public abstract class SpelNodeImpl implements SpelNode, Opcodes {
|
||||
@@ -64,7 +66,7 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
|
||||
* <p>The descriptor is like the bytecode form but is slightly easier to work with.
|
||||
* It does not include the trailing semicolon (for non array reference types).
|
||||
* Some examples: Ljava/lang/String, I, [I
|
||||
*/
|
||||
*/
|
||||
@Nullable
|
||||
protected volatile String exitTypeDescriptor;
|
||||
|
||||
@@ -83,8 +85,8 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
|
||||
|
||||
|
||||
/**
|
||||
* Return {@code true} if the next child is one of the specified classes.
|
||||
*/
|
||||
* Return {@code true} if the next child is one of the specified classes.
|
||||
*/
|
||||
protected boolean nextChildIs(Class<?>... classes) {
|
||||
if (this.parent != null) {
|
||||
SpelNodeImpl[] peers = this.parent.children;
|
||||
@@ -125,6 +127,28 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
|
||||
|
||||
@Override
|
||||
public void setValue(ExpressionState expressionState, @Nullable Object newValue) throws EvaluationException {
|
||||
setValueInternal(expressionState, () -> new TypedValue(newValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the expression to a node and then set the new value created by the
|
||||
* specified {@link Supplier} on that node.
|
||||
* <p>For example, if the expression evaluates to a property reference, then the
|
||||
* property will be set to the new value.
|
||||
* <p>Favor this method over {@link #setValue(ExpressionState, Object)} when
|
||||
* the value should be lazily computed.
|
||||
* <p>By default, this method throws a {@link SpelEvaluationException},
|
||||
* effectively disabling this feature. Subclasses may override this method to
|
||||
* provide an actual implementation.
|
||||
* @param expressionState the current expression state (includes the context)
|
||||
* @param valueSupplier a supplier of the new value
|
||||
* @throws EvaluationException if any problem occurs evaluating the expression or
|
||||
* setting the new value
|
||||
* @since 5.2.24
|
||||
*/
|
||||
public TypedValue setValueInternal(ExpressionState expressionState, Supplier<TypedValue> valueSupplier)
|
||||
throws EvaluationException {
|
||||
|
||||
throw new SpelEvaluationException(getStartPosition(), SpelMessage.SETVALUE_NOT_SUPPORTED, getClass());
|
||||
}
|
||||
|
||||
|
||||
+13
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,9 +17,11 @@
|
||||
package org.springframework.expression.spel.ast;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.asm.MethodVisitor;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.expression.spel.CodeFlow;
|
||||
import org.springframework.expression.spel.ExpressionState;
|
||||
@@ -27,10 +29,11 @@ import org.springframework.expression.spel.SpelEvaluationException;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a variable reference, eg. #someVar. Note this is different to a *local*
|
||||
* variable like $someVar
|
||||
* Represents a variable reference — for example, {@code #someVar}. Note
|
||||
* that this is different than a <em>local</em> variable like {@code $someVar}.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
*/
|
||||
public class VariableReference extends SpelNodeImpl {
|
||||
@@ -53,14 +56,14 @@ public class VariableReference extends SpelNodeImpl {
|
||||
@Override
|
||||
public ValueRef getValueRef(ExpressionState state) throws SpelEvaluationException {
|
||||
if (this.name.equals(THIS)) {
|
||||
return new ValueRef.TypedValueHolderValueRef(state.getActiveContextObject(),this);
|
||||
return new ValueRef.TypedValueHolderValueRef(state.getActiveContextObject(), this);
|
||||
}
|
||||
if (this.name.equals(ROOT)) {
|
||||
return new ValueRef.TypedValueHolderValueRef(state.getRootContextObject(),this);
|
||||
return new ValueRef.TypedValueHolderValueRef(state.getRootContextObject(), this);
|
||||
}
|
||||
TypedValue result = state.lookupVariable(this.name);
|
||||
// a null value will mean either the value was null or the variable was not found
|
||||
return new VariableRef(this.name,result,state.getEvaluationContext());
|
||||
return new VariableRef(this.name, result, state.getEvaluationContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -90,8 +93,10 @@ public class VariableReference extends SpelNodeImpl {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(ExpressionState state, @Nullable Object value) throws SpelEvaluationException {
|
||||
state.setVariable(this.name, value);
|
||||
public TypedValue setValueInternal(ExpressionState state, Supplier<TypedValue> valueSupplier)
|
||||
throws EvaluationException {
|
||||
|
||||
return state.assignVariable(this.name, valueSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+10
-1
@@ -29,6 +29,7 @@ import org.springframework.expression.ParseException;
|
||||
import org.springframework.expression.ParserContext;
|
||||
import org.springframework.expression.common.TemplateAwareExpressionParser;
|
||||
import org.springframework.expression.spel.InternalParseException;
|
||||
import org.springframework.expression.spel.SpelEvaluationException;
|
||||
import org.springframework.expression.spel.SpelMessage;
|
||||
import org.springframework.expression.spel.SpelParseException;
|
||||
import org.springframework.expression.spel.SpelParserConfiguration;
|
||||
@@ -92,7 +93,6 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
|
||||
private static final Pattern VALID_QUALIFIED_ID_PATTERN = Pattern.compile("[\\p{L}\\p{N}_$]+");
|
||||
|
||||
|
||||
private final SpelParserConfiguration configuration;
|
||||
|
||||
// For rules that build nodes, they are stacked here for return
|
||||
@@ -127,6 +127,8 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
protected SpelExpression doParseExpression(String expressionString, @Nullable ParserContext context)
|
||||
throws ParseException {
|
||||
|
||||
checkExpressionLength(expressionString);
|
||||
|
||||
try {
|
||||
this.expressionString = expressionString;
|
||||
Tokenizer tokenizer = new Tokenizer(expressionString);
|
||||
@@ -148,6 +150,13 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkExpressionLength(String string) {
|
||||
int maxLength = this.configuration.getMaximumExpressionLength();
|
||||
if (string.length() > maxLength) {
|
||||
throw new SpelEvaluationException(SpelMessage.MAX_EXPRESSION_LENGTH_EXCEEDED, maxLength);
|
||||
}
|
||||
}
|
||||
|
||||
// expression
|
||||
// : logicalOrExpression
|
||||
// ( (ASSIGN^ logicalOrExpression)
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,6 +28,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
*/
|
||||
public class SpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
@@ -53,6 +54,7 @@ public class SpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
|
||||
|
||||
public SpelExpression parseRaw(String expressionString) throws ParseException {
|
||||
Assert.hasText(expressionString, "'expressionString' must not be null or blank");
|
||||
return doParseExpression(expressionString, null);
|
||||
}
|
||||
|
||||
|
||||
+14
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,6 +21,7 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
@@ -78,6 +79,7 @@ import org.springframework.lang.Nullable;
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 4.3.15
|
||||
* @see #forPropertyAccessors
|
||||
* @see #forReadOnlyDataBinding()
|
||||
@@ -200,6 +202,17 @@ public final class SimpleEvaluationContext implements EvaluationContext {
|
||||
return this.operatorOverloader;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code SimpleEvaluationContext} does not support variable assignment within
|
||||
* expressions.
|
||||
* @throws SpelEvaluationException with {@link SpelMessage#VARIABLE_ASSIGNMENT_NOT_SUPPORTED}
|
||||
* @since 5.2.24
|
||||
*/
|
||||
@Override
|
||||
public TypedValue assignVariable(String name, Supplier<TypedValue> valueSupplier) {
|
||||
throw new SpelEvaluationException(SpelMessage.VARIABLE_ASSIGNMENT_NOT_SUPPORTED, "#" + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVariable(String name, @Nullable Object value) {
|
||||
this.variables.put(name, value);
|
||||
|
||||
+18
@@ -164,6 +164,24 @@ public abstract class AbstractExpressionTests {
|
||||
*/
|
||||
protected void evaluateAndCheckError(String expression, Class<?> expectedReturnType, SpelMessage expectedMessage,
|
||||
Object... otherProperties) {
|
||||
|
||||
evaluateAndCheckError(this.parser, expression, expectedReturnType, expectedMessage, otherProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the specified expression and ensure the expected message comes out.
|
||||
* The message may have inserts and they will be checked if otherProperties is specified.
|
||||
* The first entry in otherProperties should always be the position.
|
||||
* @param parser the expression parser to use
|
||||
* @param expression the expression to evaluate
|
||||
* @param expectedReturnType ask the expression return value to be of this type if possible
|
||||
* ({@code null} indicates don't ask for conversion)
|
||||
* @param expectedMessage the expected message
|
||||
* @param otherProperties the expected inserts within the message
|
||||
*/
|
||||
protected void evaluateAndCheckError(ExpressionParser parser, String expression, Class<?> expectedReturnType, SpelMessage expectedMessage,
|
||||
Object... otherProperties) {
|
||||
|
||||
assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() -> {
|
||||
Expression expr = parser.parseExpression(expression);
|
||||
assertThat(expr).as("expression").isNotNull();
|
||||
|
||||
+68
-8
@@ -25,6 +25,8 @@ import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.BeanResolver;
|
||||
@@ -36,6 +38,7 @@ import org.springframework.expression.MethodFilter;
|
||||
import org.springframework.expression.ParseException;
|
||||
import org.springframework.expression.spel.standard.SpelExpression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.SimpleEvaluationContext;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.expression.spel.support.StandardTypeLocator;
|
||||
import org.springframework.expression.spel.testresources.TestPerson;
|
||||
@@ -60,6 +63,40 @@ class EvaluationTests extends AbstractExpressionTests {
|
||||
@Nested
|
||||
class MiscellaneousTests {
|
||||
|
||||
@Test
|
||||
void expressionLength() {
|
||||
String expression = String.format("'X' + '%s'", repeat(" ", 9_992));
|
||||
assertThat(expression).hasSize(10_000);
|
||||
Expression expr = parser.parseExpression(expression);
|
||||
String result = expr.getValue(context, String.class);
|
||||
assertThat(result).hasSize(9_993);
|
||||
assertThat(result.trim()).isEqualTo("X");
|
||||
|
||||
expression = String.format("'X' + '%s'", repeat(" ", 9_993));
|
||||
assertThat(expression).hasSize(10_001);
|
||||
evaluateAndCheckError(expression, String.class, SpelMessage.MAX_EXPRESSION_LENGTH_EXCEEDED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxExpressionLengthIsConfigurable() {
|
||||
int maximumExpressionLength = 20_000;
|
||||
|
||||
String expression = String.format("'%s'", repeat("Y", 19_998));
|
||||
assertThat(expression).hasSize(maximumExpressionLength);
|
||||
|
||||
SpelParserConfiguration configuration =
|
||||
new SpelParserConfiguration(null, null, false, false, 0, maximumExpressionLength);
|
||||
ExpressionParser parser = new SpelExpressionParser(configuration);
|
||||
|
||||
Expression expr = parser.parseExpression(expression);
|
||||
String result = expr.getValue(String.class);
|
||||
assertThat(result).hasSize(19_998);
|
||||
|
||||
expression = String.format("'%s'", repeat("Y", 25_000));
|
||||
assertThat(expression).hasSize(25_002);
|
||||
evaluateAndCheckError(parser, expression, String.class, SpelMessage.MAX_EXPRESSION_LENGTH_EXCEEDED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createListsOnAttemptToIndexNull01() throws EvaluationException, ParseException {
|
||||
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||
@@ -135,8 +172,24 @@ class EvaluationTests extends AbstractExpressionTests {
|
||||
|
||||
// assignment
|
||||
@Test
|
||||
void assignmentToVariables() {
|
||||
evaluate("#var1='value1'", "value1", String.class);
|
||||
void assignmentToVariableWithStandardEvaluationContext() {
|
||||
evaluate("#var1 = 'value1'", "value1", String.class);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource(delimiterString = "->", value = {
|
||||
"'#var1 = \"value1\"' -> #var1",
|
||||
"'true ? #myVar = 4 : 0' -> #myVar"
|
||||
})
|
||||
void assignmentToVariableWithSimpleEvaluationContext(String expression, String varName) {
|
||||
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
|
||||
Expression expr = parser.parseExpression(expression);
|
||||
assertThatExceptionOfType(SpelEvaluationException.class)
|
||||
.isThrownBy(() -> expr.getValue(context))
|
||||
.satisfies(ex -> {
|
||||
assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.VARIABLE_ASSIGNMENT_NOT_SUPPORTED);
|
||||
assertThat(ex.getInserts()).as("inserts").containsExactly(varName);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -482,15 +535,13 @@ class EvaluationTests extends AbstractExpressionTests {
|
||||
|
||||
@Test
|
||||
void matchesWithPatternLengthThreshold() {
|
||||
String pattern = "(0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
|
||||
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
|
||||
"01234567890123456789012345678901234567890123456789|abc)";
|
||||
assertThat(pattern).hasSize(256);
|
||||
Expression expr = parser.parseExpression("'abc' matches '" + pattern + "'");
|
||||
String pattern = String.format("^(%s|X)", repeat("12345", 199));
|
||||
assertThat(pattern).hasSize(1000);
|
||||
Expression expr = parser.parseExpression("'X' matches '" + pattern + "'");
|
||||
assertThat(expr.getValue(context, Boolean.class)).isTrue();
|
||||
|
||||
pattern += "?";
|
||||
assertThat(pattern).hasSize(257);
|
||||
assertThat(pattern).hasSize(1001);
|
||||
evaluateAndCheckError("'abc' matches '" + pattern + "'", Boolean.class, SpelMessage.MAX_REGEX_LENGTH_EXCEEDED);
|
||||
}
|
||||
|
||||
@@ -1468,6 +1519,15 @@ class EvaluationTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
|
||||
private static String repeat(String str, int count) {
|
||||
String result = "";
|
||||
for (int i = 0; i < count; i++) {
|
||||
result += str;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
static class TestClass {
|
||||
|
||||
|
||||
+55
-10
@@ -26,6 +26,7 @@ import org.springframework.expression.spel.ast.Operator;
|
||||
import org.springframework.expression.spel.standard.SpelExpression;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.expression.spel.SpelMessage.MAX_CONCATENATED_STRING_LENGTH_EXCEEDED;
|
||||
import static org.springframework.expression.spel.SpelMessage.MAX_REPEATED_TEXT_SIZE_EXCEEDED;
|
||||
|
||||
/**
|
||||
@@ -391,11 +392,7 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
evaluate("3.0f + 5.0f", 8.0f, Float.class);
|
||||
evaluate("3.0d + 5.0d", 8.0d, Double.class);
|
||||
evaluate("3 + new java.math.BigDecimal('5')", new BigDecimal("8"), BigDecimal.class);
|
||||
|
||||
evaluate("'ab' + 2", "ab2", String.class);
|
||||
evaluate("2 + 'a'", "2a", String.class);
|
||||
evaluate("'ab' + null", "abnull", String.class);
|
||||
evaluate("null + 'ab'", "nullab", String.class);
|
||||
evaluate("5 + new Integer('37')", 42, Integer.class);
|
||||
|
||||
// AST:
|
||||
SpelExpression expr = (SpelExpression) parser.parseExpression("+3");
|
||||
@@ -409,11 +406,6 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
evaluate("+5", 5, Integer.class);
|
||||
evaluate("+new java.math.BigDecimal('5')", new BigDecimal("5"), BigDecimal.class);
|
||||
evaluateAndCheckError("+'abc'", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
|
||||
|
||||
// string concatenation
|
||||
evaluate("'abc'+'def'", "abcdef", String.class);
|
||||
|
||||
evaluate("5 + new Integer('37')", 42, Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -587,6 +579,59 @@ class OperatorTests extends AbstractExpressionTests {
|
||||
evaluateAndCheckError("'a' * 257", String.class, MAX_REPEATED_TEXT_SIZE_EXCEEDED, 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void stringConcatenation() {
|
||||
evaluate("'' + ''", "", String.class);
|
||||
evaluate("'' + null", "null", String.class);
|
||||
evaluate("null + ''", "null", String.class);
|
||||
evaluate("'ab' + null", "abnull", String.class);
|
||||
evaluate("null + 'ab'", "nullab", String.class);
|
||||
evaluate("'ab' + 2", "ab2", String.class);
|
||||
evaluate("2 + 'ab'", "2ab", String.class);
|
||||
evaluate("'abc' + 'def'", "abcdef", String.class);
|
||||
|
||||
// Text is big but not too big
|
||||
final int maxSize = 100_000;
|
||||
context.setVariable("text1", createString(maxSize));
|
||||
Expression expr = parser.parseExpression("#text1 + ''");
|
||||
assertThat(expr.getValue(context, String.class)).hasSize(maxSize);
|
||||
|
||||
expr = parser.parseExpression("'' + #text1");
|
||||
assertThat(expr.getValue(context, String.class)).hasSize(maxSize);
|
||||
|
||||
context.setVariable("text1", createString(maxSize / 2));
|
||||
expr = parser.parseExpression("#text1 + #text1");
|
||||
assertThat(expr.getValue(context, String.class)).hasSize(maxSize);
|
||||
|
||||
// Text is too big
|
||||
context.setVariable("text1", createString(maxSize + 1));
|
||||
evaluateAndCheckError("#text1 + ''", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 7);
|
||||
evaluateAndCheckError("#text1 + true", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 7);
|
||||
evaluateAndCheckError("'' + #text1", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 3);
|
||||
evaluateAndCheckError("true + #text1", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 5);
|
||||
|
||||
context.setVariable("text1", createString(maxSize / 2));
|
||||
context.setVariable("text2", createString((maxSize / 2) + 1));
|
||||
evaluateAndCheckError("#text1 + #text2", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 7);
|
||||
evaluateAndCheckError("#text1 + #text2 + true", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 7);
|
||||
evaluateAndCheckError("#text1 + true + #text2", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 14);
|
||||
evaluateAndCheckError("true + #text1 + #text2", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 14);
|
||||
|
||||
evaluateAndCheckError("#text2 + #text1", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 7);
|
||||
evaluateAndCheckError("#text2 + #text1 + true", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 7);
|
||||
evaluateAndCheckError("#text2 + true + #text1", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 14);
|
||||
evaluateAndCheckError("true + #text2 + #text1", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 14);
|
||||
|
||||
context.setVariable("text1", createString((maxSize / 3) + 1));
|
||||
evaluateAndCheckError("#text1 + #text1 + #text1", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 16);
|
||||
evaluateAndCheckError("(#text1 + #text1) + #text1", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 18);
|
||||
evaluateAndCheckError("#text1 + (#text1 + #text1)", String.class, MAX_CONCATENATED_STRING_LENGTH_EXCEEDED, 7);
|
||||
}
|
||||
|
||||
private static String createString(int size) {
|
||||
return new String(new char[size]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void longs() {
|
||||
evaluate("3L == 4L", false, Boolean.class);
|
||||
|
||||
+26
-21
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -30,14 +30,16 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class TemplateExpressionParsingTests extends AbstractExpressionTests {
|
||||
class TemplateExpressionParsingTests extends AbstractExpressionTests {
|
||||
|
||||
public static final ParserContext DEFAULT_TEMPLATE_PARSER_CONTEXT = new ParserContext() {
|
||||
static final ParserContext DEFAULT_TEMPLATE_PARSER_CONTEXT = new ParserContext() {
|
||||
@Override
|
||||
public String getExpressionPrefix() {
|
||||
return "${";
|
||||
@@ -52,7 +54,7 @@ public class TemplateExpressionParsingTests extends AbstractExpressionTests {
|
||||
}
|
||||
};
|
||||
|
||||
public static final ParserContext HASH_DELIMITED_PARSER_CONTEXT = new ParserContext() {
|
||||
static final ParserContext HASH_DELIMITED_PARSER_CONTEXT = new ParserContext() {
|
||||
@Override
|
||||
public String getExpressionPrefix() {
|
||||
return "#{";
|
||||
@@ -68,25 +70,32 @@ public class TemplateExpressionParsingTests extends AbstractExpressionTests {
|
||||
};
|
||||
|
||||
|
||||
private final SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
|
||||
@Test
|
||||
public void testParsingSimpleTemplateExpression01() throws Exception {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
void nullTemplateExpressionIsRejected() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> parser.parseExpression(null, DEFAULT_TEMPLATE_PARSER_CONTEXT))
|
||||
.withMessage("'expressionString' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parsingSimpleTemplateExpression01() {
|
||||
Expression expr = parser.parseExpression("hello ${'world'}", DEFAULT_TEMPLATE_PARSER_CONTEXT);
|
||||
Object o = expr.getValue();
|
||||
assertThat(o.toString()).isEqualTo("hello world");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingSimpleTemplateExpression02() throws Exception {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
void parsingSimpleTemplateExpression02() {
|
||||
Expression expr = parser.parseExpression("hello ${'to'} you", DEFAULT_TEMPLATE_PARSER_CONTEXT);
|
||||
Object o = expr.getValue();
|
||||
assertThat(o.toString()).isEqualTo("hello to you");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingSimpleTemplateExpression03() throws Exception {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
void parsingSimpleTemplateExpression03() {
|
||||
Expression expr = parser.parseExpression("The quick ${'brown'} fox jumped over the ${'lazy'} dog",
|
||||
DEFAULT_TEMPLATE_PARSER_CONTEXT);
|
||||
Object o = expr.getValue();
|
||||
@@ -94,8 +103,7 @@ public class TemplateExpressionParsingTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingSimpleTemplateExpression04() throws Exception {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
void parsingSimpleTemplateExpression04() {
|
||||
Expression expr = parser.parseExpression("${'hello'} world", DEFAULT_TEMPLATE_PARSER_CONTEXT);
|
||||
Object o = expr.getValue();
|
||||
assertThat(o.toString()).isEqualTo("hello world");
|
||||
@@ -114,8 +122,7 @@ public class TemplateExpressionParsingTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompositeStringExpression() throws Exception {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
void compositeStringExpression() {
|
||||
Expression ex = parser.parseExpression("hello ${'world'}", DEFAULT_TEMPLATE_PARSER_CONTEXT);
|
||||
assertThat(ex.getValue()).isInstanceOf(String.class).isEqualTo("hello world");
|
||||
assertThat(ex.getValue(String.class)).isInstanceOf(String.class).isEqualTo("hello world");
|
||||
@@ -154,8 +161,7 @@ public class TemplateExpressionParsingTests extends AbstractExpressionTests {
|
||||
static class Rooty {}
|
||||
|
||||
@Test
|
||||
public void testNestedExpressions() throws Exception {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
void nestedExpressions() {
|
||||
// treat the nested ${..} as a part of the expression
|
||||
Expression ex = parser.parseExpression("hello ${listOfNumbersUpToTen.$[#this<5]} world",DEFAULT_TEMPLATE_PARSER_CONTEXT);
|
||||
String s = ex.getValue(TestScenarioCreator.getTestEvaluationContext(),String.class);
|
||||
@@ -185,8 +191,7 @@ public class TemplateExpressionParsingTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
public void testClashingWithSuffixes() throws Exception {
|
||||
void clashingWithSuffixes() {
|
||||
// Just wanting to use the prefix or suffix within the template:
|
||||
Expression ex = parser.parseExpression("hello ${3+4} world",DEFAULT_TEMPLATE_PARSER_CONTEXT);
|
||||
String s = ex.getValue(TestScenarioCreator.getTestEvaluationContext(),String.class);
|
||||
@@ -202,13 +207,13 @@ public class TemplateExpressionParsingTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsingNormalExpressionThroughTemplateParser() throws Exception {
|
||||
void parsingNormalExpressionThroughTemplateParser() {
|
||||
Expression expr = parser.parseExpression("1+2+3");
|
||||
assertThat(expr.getValue()).isEqualTo(6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testErrorCases() throws Exception {
|
||||
void errorCases() {
|
||||
assertThatExceptionOfType(ParseException.class).isThrownBy(() ->
|
||||
parser.parseExpression("hello ${'world'", DEFAULT_TEMPLATE_PARSER_CONTEXT))
|
||||
.satisfies(pex -> {
|
||||
@@ -224,7 +229,7 @@ public class TemplateExpressionParsingTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTemplateParserContext() {
|
||||
void testTemplateParserContext() {
|
||||
TemplateParserContext tpc = new TemplateParserContext("abc","def");
|
||||
assertThat(tpc.getExpressionPrefix()).isEqualTo("abc");
|
||||
assertThat(tpc.getExpressionSuffix()).isEqualTo("def");
|
||||
|
||||
+113
-103
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,6 +18,8 @@ package org.springframework.expression.spel.standard;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
|
||||
import org.assertj.core.api.ThrowableAssertAlternative;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
@@ -31,16 +33,55 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
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.springframework.expression.spel.SpelMessage.MISSING_CONSTRUCTOR_ARGS;
|
||||
import static org.springframework.expression.spel.SpelMessage.NON_TERMINATING_DOUBLE_QUOTED_STRING;
|
||||
import static org.springframework.expression.spel.SpelMessage.NON_TERMINATING_QUOTED_STRING;
|
||||
import static org.springframework.expression.spel.SpelMessage.NOT_AN_INTEGER;
|
||||
import static org.springframework.expression.spel.SpelMessage.NOT_A_LONG;
|
||||
import static org.springframework.expression.spel.SpelMessage.REAL_CANNOT_BE_LONG;
|
||||
import static org.springframework.expression.spel.SpelMessage.RUN_OUT_OF_ARGUMENTS;
|
||||
import static org.springframework.expression.spel.SpelMessage.UNEXPECTED_DATA_AFTER_DOT;
|
||||
import static org.springframework.expression.spel.SpelMessage.UNEXPECTED_ESCAPE_CHAR;
|
||||
|
||||
/**
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class SpelParserTests {
|
||||
|
||||
private final SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
|
||||
@Test
|
||||
void nullExpressionIsRejected() {
|
||||
assertNullOrEmptyExpressionIsRejected(() -> parser.parseExpression(null));
|
||||
assertNullOrEmptyExpressionIsRejected(() -> parser.parseRaw(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyExpressionIsRejected() {
|
||||
assertNullOrEmptyExpressionIsRejected(() -> parser.parseExpression(""));
|
||||
assertNullOrEmptyExpressionIsRejected(() -> parser.parseRaw(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void blankExpressionIsRejected() {
|
||||
assertNullOrEmptyExpressionIsRejected(() -> parser.parseExpression(" "));
|
||||
assertNullOrEmptyExpressionIsRejected(() -> parser.parseExpression("\t\n"));
|
||||
assertNullOrEmptyExpressionIsRejected(() -> parser.parseRaw(" "));
|
||||
assertNullOrEmptyExpressionIsRejected(() -> parser.parseRaw("\t\n"));
|
||||
}
|
||||
|
||||
private static void assertNullOrEmptyExpressionIsRejected(ThrowingCallable throwingCallable) {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(throwingCallable)
|
||||
.withMessage("'expressionString' must not be null or blank");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theMostBasic() {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
SpelExpression expr = parser.parseRaw("2");
|
||||
assertThat(expr).isNotNull();
|
||||
assertThat(expr.getAST()).isNotNull();
|
||||
@@ -51,7 +92,6 @@ class SpelParserTests {
|
||||
|
||||
@Test
|
||||
void valueType() {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
EvaluationContext ctx = new StandardEvaluationContext();
|
||||
Class<?> c = parser.parseRaw("2").getValueType();
|
||||
assertThat(c).isEqualTo(Integer.class);
|
||||
@@ -67,7 +107,6 @@ class SpelParserTests {
|
||||
|
||||
@Test
|
||||
void whitespace() {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
SpelExpression expr = parser.parseRaw("2 + 3");
|
||||
assertThat(expr.getValue()).isEqualTo(5);
|
||||
expr = parser.parseRaw("2 + 3");
|
||||
@@ -80,7 +119,6 @@ class SpelParserTests {
|
||||
|
||||
@Test
|
||||
void arithmeticPlus1() {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
SpelExpression expr = parser.parseRaw("2+2");
|
||||
assertThat(expr).isNotNull();
|
||||
assertThat(expr.getAST()).isNotNull();
|
||||
@@ -89,14 +127,12 @@ class SpelParserTests {
|
||||
|
||||
@Test
|
||||
void arithmeticPlus2() {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
SpelExpression expr = parser.parseRaw("37+41");
|
||||
assertThat(expr.getValue()).isEqualTo(78);
|
||||
}
|
||||
|
||||
@Test
|
||||
void arithmeticMultiply1() {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
SpelExpression expr = parser.parseRaw("2*3");
|
||||
assertThat(expr).isNotNull();
|
||||
assertThat(expr.getAST()).isNotNull();
|
||||
@@ -105,162 +141,120 @@ class SpelParserTests {
|
||||
|
||||
@Test
|
||||
void arithmeticPrecedence1() {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
SpelExpression expr = parser.parseRaw("2*3+5");
|
||||
assertThat(expr.getValue()).isEqualTo(11);
|
||||
}
|
||||
|
||||
@Test
|
||||
void generalExpressions() {
|
||||
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
parser.parseRaw("new String");
|
||||
})
|
||||
.satisfies(parseExceptionRequirements(SpelMessage.MISSING_CONSTRUCTOR_ARGS, 10));
|
||||
|
||||
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
parser.parseRaw("new String(3,");
|
||||
})
|
||||
.satisfies(parseExceptionRequirements(SpelMessage.RUN_OUT_OF_ARGUMENTS, 10));
|
||||
|
||||
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
parser.parseRaw("new String(3");
|
||||
})
|
||||
.satisfies(parseExceptionRequirements(SpelMessage.RUN_OUT_OF_ARGUMENTS, 10));
|
||||
|
||||
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
parser.parseRaw("new String(");
|
||||
})
|
||||
.satisfies(parseExceptionRequirements(SpelMessage.RUN_OUT_OF_ARGUMENTS, 10));
|
||||
|
||||
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
parser.parseRaw("\"abc");
|
||||
})
|
||||
.satisfies(parseExceptionRequirements(SpelMessage.NON_TERMINATING_DOUBLE_QUOTED_STRING, 0));
|
||||
|
||||
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
parser.parseRaw("'abc");
|
||||
})
|
||||
.satisfies(parseExceptionRequirements(SpelMessage.NON_TERMINATING_QUOTED_STRING, 0));
|
||||
|
||||
}
|
||||
|
||||
private <E extends SpelParseException> Consumer<E> parseExceptionRequirements(
|
||||
SpelMessage expectedMessage, int expectedPosition) {
|
||||
return ex -> {
|
||||
assertThat(ex.getMessageCode()).isEqualTo(expectedMessage);
|
||||
assertThat(ex.getPosition()).isEqualTo(expectedPosition);
|
||||
assertThat(ex.getMessage()).contains(ex.getExpressionString());
|
||||
};
|
||||
void parseExceptions() {
|
||||
assertParseException(() -> parser.parseRaw("new String"), MISSING_CONSTRUCTOR_ARGS, 10);
|
||||
assertParseException(() -> parser.parseRaw("new String(3,"), RUN_OUT_OF_ARGUMENTS, 10);
|
||||
assertParseException(() -> parser.parseRaw("new String(3"), RUN_OUT_OF_ARGUMENTS, 10);
|
||||
assertParseException(() -> parser.parseRaw("new String("), RUN_OUT_OF_ARGUMENTS, 10);
|
||||
assertParseException(() -> parser.parseRaw("\"abc"), NON_TERMINATING_DOUBLE_QUOTED_STRING, 0);
|
||||
assertParseException(() -> parser.parseRaw("'abc"), NON_TERMINATING_QUOTED_STRING, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void arithmeticPrecedence2() {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
SpelExpression expr = parser.parseRaw("2+3*5");
|
||||
assertThat(expr.getValue()).isEqualTo(17);
|
||||
}
|
||||
|
||||
@Test
|
||||
void arithmeticPrecedence3() {
|
||||
SpelExpression expr = new SpelExpressionParser().parseRaw("3+10/2");
|
||||
SpelExpression expr = parser.parseRaw("3+10/2");
|
||||
assertThat(expr.getValue()).isEqualTo(8);
|
||||
}
|
||||
|
||||
@Test
|
||||
void arithmeticPrecedence4() {
|
||||
SpelExpression expr = new SpelExpressionParser().parseRaw("10/2+3");
|
||||
SpelExpression expr = parser.parseRaw("10/2+3");
|
||||
assertThat(expr.getValue()).isEqualTo(8);
|
||||
}
|
||||
|
||||
@Test
|
||||
void arithmeticPrecedence5() {
|
||||
SpelExpression expr = new SpelExpressionParser().parseRaw("(4+10)/2");
|
||||
SpelExpression expr = parser.parseRaw("(4+10)/2");
|
||||
assertThat(expr.getValue()).isEqualTo(7);
|
||||
}
|
||||
|
||||
@Test
|
||||
void arithmeticPrecedence6() {
|
||||
SpelExpression expr = new SpelExpressionParser().parseRaw("(3+2)*2");
|
||||
SpelExpression expr = parser.parseRaw("(3+2)*2");
|
||||
assertThat(expr.getValue()).isEqualTo(10);
|
||||
}
|
||||
|
||||
@Test
|
||||
void booleanOperators() {
|
||||
SpelExpression expr = new SpelExpressionParser().parseRaw("true");
|
||||
assertThat(expr.getValue(Boolean.class)).isEqualTo(Boolean.TRUE);
|
||||
expr = new SpelExpressionParser().parseRaw("false");
|
||||
assertThat(expr.getValue(Boolean.class)).isEqualTo(Boolean.FALSE);
|
||||
expr = new SpelExpressionParser().parseRaw("false and false");
|
||||
assertThat(expr.getValue(Boolean.class)).isEqualTo(Boolean.FALSE);
|
||||
expr = new SpelExpressionParser().parseRaw("true and (true or false)");
|
||||
assertThat(expr.getValue(Boolean.class)).isEqualTo(Boolean.TRUE);
|
||||
expr = new SpelExpressionParser().parseRaw("true and true or false");
|
||||
assertThat(expr.getValue(Boolean.class)).isEqualTo(Boolean.TRUE);
|
||||
expr = new SpelExpressionParser().parseRaw("!true");
|
||||
assertThat(expr.getValue(Boolean.class)).isEqualTo(Boolean.FALSE);
|
||||
expr = new SpelExpressionParser().parseRaw("!(false or true)");
|
||||
assertThat(expr.getValue(Boolean.class)).isEqualTo(Boolean.FALSE);
|
||||
SpelExpression expr = parser.parseRaw("true");
|
||||
assertThat(expr.getValue(Boolean.class)).isTrue();
|
||||
expr = parser.parseRaw("false");
|
||||
assertThat(expr.getValue(Boolean.class)).isFalse();
|
||||
expr = parser.parseRaw("false and false");
|
||||
assertThat(expr.getValue(Boolean.class)).isFalse();
|
||||
expr = parser.parseRaw("true and (true or false)");
|
||||
assertThat(expr.getValue(Boolean.class)).isTrue();
|
||||
expr = parser.parseRaw("true and true or false");
|
||||
assertThat(expr.getValue(Boolean.class)).isTrue();
|
||||
expr = parser.parseRaw("!true");
|
||||
assertThat(expr.getValue(Boolean.class)).isFalse();
|
||||
expr = parser.parseRaw("!(false or true)");
|
||||
assertThat(expr.getValue(Boolean.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void booleanOperators_symbolic_spr9614() {
|
||||
SpelExpression expr = new SpelExpressionParser().parseRaw("true");
|
||||
assertThat(expr.getValue(Boolean.class)).isEqualTo(Boolean.TRUE);
|
||||
expr = new SpelExpressionParser().parseRaw("false");
|
||||
assertThat(expr.getValue(Boolean.class)).isEqualTo(Boolean.FALSE);
|
||||
expr = new SpelExpressionParser().parseRaw("false && false");
|
||||
assertThat(expr.getValue(Boolean.class)).isEqualTo(Boolean.FALSE);
|
||||
expr = new SpelExpressionParser().parseRaw("true && (true || false)");
|
||||
assertThat(expr.getValue(Boolean.class)).isEqualTo(Boolean.TRUE);
|
||||
expr = new SpelExpressionParser().parseRaw("true && true || false");
|
||||
assertThat(expr.getValue(Boolean.class)).isEqualTo(Boolean.TRUE);
|
||||
expr = new SpelExpressionParser().parseRaw("!true");
|
||||
assertThat(expr.getValue(Boolean.class)).isEqualTo(Boolean.FALSE);
|
||||
expr = new SpelExpressionParser().parseRaw("!(false || true)");
|
||||
assertThat(expr.getValue(Boolean.class)).isEqualTo(Boolean.FALSE);
|
||||
SpelExpression expr = parser.parseRaw("true");
|
||||
assertThat(expr.getValue(Boolean.class)).isTrue();
|
||||
expr = parser.parseRaw("false");
|
||||
assertThat(expr.getValue(Boolean.class)).isFalse();
|
||||
expr = parser.parseRaw("false && false");
|
||||
assertThat(expr.getValue(Boolean.class)).isFalse();
|
||||
expr = parser.parseRaw("true && (true || false)");
|
||||
assertThat(expr.getValue(Boolean.class)).isTrue();
|
||||
expr = parser.parseRaw("true && true || false");
|
||||
assertThat(expr.getValue(Boolean.class)).isTrue();
|
||||
expr = parser.parseRaw("!true");
|
||||
assertThat(expr.getValue(Boolean.class)).isFalse();
|
||||
expr = parser.parseRaw("!(false || true)");
|
||||
assertThat(expr.getValue(Boolean.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void stringLiterals() {
|
||||
SpelExpression expr = new SpelExpressionParser().parseRaw("'howdy'");
|
||||
SpelExpression expr = parser.parseRaw("'howdy'");
|
||||
assertThat(expr.getValue()).isEqualTo("howdy");
|
||||
expr = new SpelExpressionParser().parseRaw("'hello '' world'");
|
||||
expr = parser.parseRaw("'hello '' world'");
|
||||
assertThat(expr.getValue()).isEqualTo("hello ' world");
|
||||
}
|
||||
|
||||
@Test
|
||||
void stringLiterals2() {
|
||||
SpelExpression expr = new SpelExpressionParser().parseRaw("'howdy'.substring(0,2)");
|
||||
SpelExpression expr = parser.parseRaw("'howdy'.substring(0,2)");
|
||||
assertThat(expr.getValue()).isEqualTo("ho");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStringLiterals_DoubleQuotes_spr9620() {
|
||||
SpelExpression expr = new SpelExpressionParser().parseRaw("\"double quote: \"\".\"");
|
||||
SpelExpression expr = parser.parseRaw("\"double quote: \"\".\"");
|
||||
assertThat(expr.getValue()).isEqualTo("double quote: \".");
|
||||
expr = new SpelExpressionParser().parseRaw("\"hello \"\" world\"");
|
||||
expr = parser.parseRaw("\"hello \"\" world\"");
|
||||
assertThat(expr.getValue()).isEqualTo("hello \" world");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStringLiterals_DoubleQuotes_spr9620_2() {
|
||||
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() ->
|
||||
new SpelExpressionParser().parseRaw("\"double quote: \\\"\\\".\""))
|
||||
assertParseExceptionThrownBy(() -> parser.parseRaw("\"double quote: \\\"\\\".\""))
|
||||
.satisfies(ex -> {
|
||||
assertThat(ex.getPosition()).isEqualTo(17);
|
||||
assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.UNEXPECTED_ESCAPE_CHAR);
|
||||
assertThat(ex.getMessageCode()).isEqualTo(UNEXPECTED_ESCAPE_CHAR);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void positionalInformation() {
|
||||
SpelExpression expr = new SpelExpressionParser().parseRaw("true and true or false");
|
||||
SpelExpression expr = parser.parseRaw("true and true or false");
|
||||
SpelNode rootAst = expr.getAST();
|
||||
OpOr operatorOr = (OpOr) rootAst;
|
||||
OpAnd operatorAnd = (OpAnd) operatorOr.getLeftOperand();
|
||||
@@ -355,10 +349,10 @@ class SpelParserTests {
|
||||
checkNumber("0xa", 10, Integer.class);
|
||||
checkNumber("0xAL", 10L, Long.class);
|
||||
|
||||
checkNumberError("0x", SpelMessage.NOT_AN_INTEGER);
|
||||
checkNumberError("0xL", SpelMessage.NOT_A_LONG);
|
||||
checkNumberError(".324", SpelMessage.UNEXPECTED_DATA_AFTER_DOT);
|
||||
checkNumberError("3.4L", SpelMessage.REAL_CANNOT_BE_LONG);
|
||||
checkNumberError("0x", NOT_AN_INTEGER);
|
||||
checkNumberError("0xL", NOT_A_LONG);
|
||||
checkNumberError(".324", UNEXPECTED_DATA_AFTER_DOT);
|
||||
checkNumberError("3.4L", REAL_CANNOT_BE_LONG);
|
||||
|
||||
checkNumber("3.5f", 3.5f, Float.class);
|
||||
checkNumber("1.2e3", 1.2e3d, Double.class);
|
||||
@@ -371,7 +365,6 @@ class SpelParserTests {
|
||||
|
||||
private void checkNumber(String expression, Object value, Class<?> type) {
|
||||
try {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
SpelExpression expr = parser.parseRaw(expression);
|
||||
Object exprVal = expr.getValue();
|
||||
assertThat(exprVal).isEqualTo(value);
|
||||
@@ -383,9 +376,26 @@ class SpelParserTests {
|
||||
}
|
||||
|
||||
private void checkNumberError(String expression, SpelMessage expectedMessage) {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
assertThatExceptionOfType(SpelParseException.class).isThrownBy(() -> parser.parseRaw(expression))
|
||||
assertParseExceptionThrownBy(() -> parser.parseRaw(expression))
|
||||
.satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(expectedMessage));
|
||||
}
|
||||
|
||||
private static ThrowableAssertAlternative<SpelParseException> assertParseExceptionThrownBy(ThrowingCallable throwingCallable) {
|
||||
return assertThatExceptionOfType(SpelParseException.class).isThrownBy(throwingCallable);
|
||||
}
|
||||
|
||||
private static void assertParseException(ThrowingCallable throwingCallable, SpelMessage expectedMessage, int expectedPosition) {
|
||||
assertParseExceptionThrownBy(throwingCallable)
|
||||
.satisfies(parseExceptionRequirements(expectedMessage, expectedPosition));
|
||||
}
|
||||
|
||||
private static <E extends SpelParseException> Consumer<E> parseExceptionRequirements(
|
||||
SpelMessage expectedMessage, int expectedPosition) {
|
||||
return ex -> {
|
||||
assertThat(ex.getMessageCode()).isEqualTo(expectedMessage);
|
||||
assertThat(ex.getPosition()).isEqualTo(expectedPosition);
|
||||
assertThat(ex.getMessage()).contains(ex.getExpressionString());
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+57
-53
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,49 +35,52 @@ import org.springframework.transaction.support.TransactionSynchronizationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.transaction.PlatformTransactionManager}
|
||||
* implementation for a single JDBC {@link javax.sql.DataSource}. This class is
|
||||
* capable of working in any environment with any JDBC driver, as long as the setup
|
||||
* uses a {@code javax.sql.DataSource} as its {@code Connection} factory mechanism.
|
||||
* Binds a JDBC Connection from the specified DataSource to the current thread,
|
||||
* potentially allowing for one thread-bound Connection per DataSource.
|
||||
* {@link org.springframework.transaction.PlatformTransactionManager} implementation
|
||||
* for a single JDBC {@link javax.sql.DataSource}. This class is capable of working
|
||||
* in any environment with any JDBC driver, as long as the setup uses a
|
||||
* {@code javax.sql.DataSource} as its {@code Connection} factory mechanism.
|
||||
* Binds a JDBC {@code Connection} from the specified {@code DataSource} to the
|
||||
* current thread, potentially allowing for one thread-bound {@code Connection}
|
||||
* per {@code DataSource}.
|
||||
*
|
||||
* <p><b>Note: The DataSource that this transaction manager operates on needs
|
||||
* to return independent Connections.</b> The Connections may come from a pool
|
||||
* (the typical case), but the DataSource must not return thread-scoped /
|
||||
* request-scoped Connections or the like. This transaction manager will
|
||||
* associate Connections with thread-bound transactions itself, according
|
||||
* to the specified propagation behavior. It assumes that a separate,
|
||||
* independent Connection can be obtained even during an ongoing transaction.
|
||||
* <p><b>Note: The {@code DataSource} that this transaction manager operates on
|
||||
* needs to return independent {@code Connection}s.</b> The {@code Connection}s
|
||||
* typically come from a connection pool but the {@code DataSource} must not return
|
||||
* specifically scoped or constrained {@code Connection}s. This transaction manager
|
||||
* will associate {@code Connection}s with thread-bound transactions, according
|
||||
* to the specified propagation behavior. It assumes that a separate, independent
|
||||
* {@code Connection} can be obtained even during an ongoing transaction.
|
||||
*
|
||||
* <p>Application code is required to retrieve the JDBC Connection via
|
||||
* <p>Application code is required to retrieve the JDBC {@code Connection} via
|
||||
* {@link DataSourceUtils#getConnection(DataSource)} instead of a standard
|
||||
* Java EE-style {@link DataSource#getConnection()} call. Spring classes such as
|
||||
* EE-style {@link DataSource#getConnection()} call. Spring classes such as
|
||||
* {@link org.springframework.jdbc.core.JdbcTemplate} use this strategy implicitly.
|
||||
* If not used in combination with this transaction manager, the
|
||||
* {@link DataSourceUtils} lookup strategy behaves exactly like the native
|
||||
* DataSource lookup; it can thus be used in a portable fashion.
|
||||
* {@code DataSource} lookup; it can thus be used in a portable fashion.
|
||||
*
|
||||
* <p>Alternatively, you can allow application code to work with the standard
|
||||
* Java EE-style lookup pattern {@link DataSource#getConnection()}, for example for
|
||||
* legacy code that is not aware of Spring at all. In that case, define a
|
||||
* {@link TransactionAwareDataSourceProxy} for your target DataSource, and pass
|
||||
* that proxy DataSource to your DAOs, which will automatically participate in
|
||||
* Spring-managed transactions when accessing it.
|
||||
* EE-style lookup pattern {@link DataSource#getConnection()}, for example
|
||||
* for legacy code that is not aware of Spring at all. In that case, define a
|
||||
* {@link TransactionAwareDataSourceProxy} for your target {@code DataSource},
|
||||
* and pass that proxy {@code DataSource} to your DAOs which will automatically
|
||||
* participate in Spring-managed transactions when accessing it.
|
||||
*
|
||||
* <p>Supports custom isolation levels, and timeouts which get applied as
|
||||
* appropriate JDBC statement timeouts. To support the latter, application code
|
||||
* must either use {@link org.springframework.jdbc.core.JdbcTemplate}, call
|
||||
* {@link DataSourceUtils#applyTransactionTimeout} for each created JDBC Statement,
|
||||
* or go through a {@link TransactionAwareDataSourceProxy} which will create
|
||||
* timeout-aware JDBC Connections and Statements automatically.
|
||||
* {@link DataSourceUtils#applyTransactionTimeout} for each created JDBC
|
||||
* {@code Statement}, or go through a {@link TransactionAwareDataSourceProxy}
|
||||
* which will create timeout-aware JDBC {@code Connection}s and {@code Statement}s
|
||||
* automatically.
|
||||
*
|
||||
* <p>Consider defining a {@link LazyConnectionDataSourceProxy} for your target
|
||||
* DataSource, pointing both this transaction manager and your DAOs to it.
|
||||
* {@code DataSource}, pointing both this transaction manager and your DAOs to it.
|
||||
* This will lead to optimized handling of "empty" transactions, i.e. of transactions
|
||||
* without any JDBC statements executed. A LazyConnectionDataSourceProxy will not fetch
|
||||
* an actual JDBC Connection from the target DataSource until a Statement gets executed,
|
||||
* lazily applying the specified transaction settings to the target Connection.
|
||||
* without any JDBC statements executed. A {@code LazyConnectionDataSourceProxy} will
|
||||
* not fetch an actual JDBC {@code Connection} from the target {@code DataSource}
|
||||
* until a {@code Statement} gets executed, lazily applying the specified transaction
|
||||
* settings to the target {@code Connection}.
|
||||
*
|
||||
* <p>This transaction manager supports nested transactions via the JDBC 3.0
|
||||
* {@link java.sql.Savepoint} mechanism. The
|
||||
@@ -88,9 +91,9 @@ import org.springframework.util.Assert;
|
||||
* <p>This transaction manager can be used as a replacement for the
|
||||
* {@link org.springframework.transaction.jta.JtaTransactionManager} in the single
|
||||
* resource case, as it does not require a container that supports JTA, typically
|
||||
* in combination with a locally defined JDBC DataSource (e.g. an Apache Commons
|
||||
* DBCP connection pool). Switching between this local strategy and a JTA
|
||||
* environment is just a matter of configuration!
|
||||
* in combination with a locally defined JDBC {@code DataSource} (e.g. a Hikari
|
||||
* connection pool). Switching between this local strategy and a JTA environment
|
||||
* is just a matter of configuration!
|
||||
*
|
||||
* <p>As of 4.3.4, this transaction manager triggers flush callbacks on registered
|
||||
* transaction synchronizations (if synchronization is generally active), assuming
|
||||
@@ -112,6 +115,7 @@ import org.springframework.util.Assert;
|
||||
* @see TransactionAwareDataSourceProxy
|
||||
* @see LazyConnectionDataSourceProxy
|
||||
* @see org.springframework.jdbc.core.JdbcTemplate
|
||||
* @see org.springframework.jdbc.support.JdbcTransactionManager
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager
|
||||
@@ -124,8 +128,8 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
|
||||
|
||||
|
||||
/**
|
||||
* Create a new DataSourceTransactionManager instance.
|
||||
* A DataSource has to be set to be able to use it.
|
||||
* Create a new {@code DataSourceTransactionManager} instance.
|
||||
* A {@code DataSource} has to be set to be able to use it.
|
||||
* @see #setDataSource
|
||||
*/
|
||||
public DataSourceTransactionManager() {
|
||||
@@ -133,7 +137,7 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new DataSourceTransactionManager instance.
|
||||
* Create a new {@code DataSourceTransactionManager} instance.
|
||||
* @param dataSource the JDBC DataSource to manage transactions for
|
||||
*/
|
||||
public DataSourceTransactionManager(DataSource dataSource) {
|
||||
@@ -144,22 +148,22 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
|
||||
|
||||
|
||||
/**
|
||||
* Set the JDBC DataSource that this instance should manage transactions for.
|
||||
* <p>This will typically be a locally defined DataSource, for example an
|
||||
* Apache Commons DBCP connection pool. Alternatively, you can also drive
|
||||
* transactions for a non-XA J2EE DataSource fetched from JNDI. For an XA
|
||||
* DataSource, use JtaTransactionManager.
|
||||
* <p>The DataSource specified here should be the target DataSource to manage
|
||||
* transactions for, not a TransactionAwareDataSourceProxy. Only data access
|
||||
* code may work with TransactionAwareDataSourceProxy, while the transaction
|
||||
* manager needs to work on the underlying target DataSource. If there's
|
||||
* nevertheless a TransactionAwareDataSourceProxy passed in, it will be
|
||||
* unwrapped to extract its target DataSource.
|
||||
* <p><b>The DataSource passed in here needs to return independent Connections.</b>
|
||||
* The Connections may come from a pool (the typical case), but the DataSource
|
||||
* must not return thread-scoped / request-scoped Connections or the like.
|
||||
* @see TransactionAwareDataSourceProxy
|
||||
* @see org.springframework.transaction.jta.JtaTransactionManager
|
||||
* Set the JDBC {@code DataSource} that this instance should manage transactions for.
|
||||
* <p>This will typically be a locally defined {@code DataSource}, for example a
|
||||
* Hikari connection pool. Alternatively, you can also manage transactions for a
|
||||
* non-XA {@code DataSource} fetched from JNDI. For an XA {@code DataSource},
|
||||
* use {@link org.springframework.transaction.jta.JtaTransactionManager} instead.
|
||||
* <p>The {@code DataSource} specified here should be the target {@code DataSource}
|
||||
* to manage transactions for, not a {@link TransactionAwareDataSourceProxy}.
|
||||
* Only data access code may work with {@code TransactionAwareDataSourceProxy} while
|
||||
* the transaction manager needs to work on the underlying target {@code DataSource}.
|
||||
* If there is nevertheless a {@code TransactionAwareDataSourceProxy} passed in,
|
||||
* it will be unwrapped to extract its target {@code DataSource}.
|
||||
* <p><b>The {@code DataSource} passed in here needs to return independent
|
||||
* {@code Connection}s.</b> The {@code Connection}s typically come from a
|
||||
* connection pool but the {@code DataSource} must not return specifically
|
||||
* scoped or constrained {@code Connection}s, just possibly lazily fetched.
|
||||
* @see LazyConnectionDataSourceProxy
|
||||
*/
|
||||
public void setDataSource(@Nullable DataSource dataSource) {
|
||||
if (dataSource instanceof TransactionAwareDataSourceProxy) {
|
||||
@@ -174,7 +178,7 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JDBC DataSource that this instance manages transactions for.
|
||||
* Return the JDBC {@code DataSource} that this instance manages transactions for.
|
||||
*/
|
||||
@Nullable
|
||||
public DataSource getDataSource() {
|
||||
@@ -182,7 +186,7 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the DataSource for actual use.
|
||||
* Obtain the {@code DataSource} for actual use.
|
||||
* @return the DataSource (never {@code null})
|
||||
* @throws IllegalStateException in case of no DataSource set
|
||||
* @since 5.0
|
||||
|
||||
@@ -33,9 +33,9 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Helper class that provides static methods for obtaining JDBC Connections from
|
||||
* a {@link javax.sql.DataSource}. Includes special support for Spring-managed
|
||||
* transactional Connections, e.g. managed by {@link DataSourceTransactionManager}
|
||||
* Helper class that provides static methods for obtaining JDBC {@code Connection}s
|
||||
* from a {@link javax.sql.DataSource}. Includes special support for Spring-managed
|
||||
* transactional {@code Connection}s, e.g. managed by {@link DataSourceTransactionManager}
|
||||
* or {@link org.springframework.transaction.jta.JtaTransactionManager}.
|
||||
*
|
||||
* <p>Used internally by Spring's {@link org.springframework.jdbc.core.JdbcTemplate},
|
||||
@@ -46,7 +46,8 @@ import org.springframework.util.Assert;
|
||||
* @author Juergen Hoeller
|
||||
* @see #getConnection
|
||||
* @see #releaseConnection
|
||||
* @see DataSourceTransactionManager
|
||||
* @see org.springframework.jdbc.core.JdbcTemplate
|
||||
* @see org.springframework.jdbc.support.JdbcTransactionManager
|
||||
* @see org.springframework.transaction.jta.JtaTransactionManager
|
||||
* @see org.springframework.transaction.support.TransactionSynchronizationManager
|
||||
*/
|
||||
@@ -474,7 +475,7 @@ public abstract class DataSourceUtils {
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
private int order;
|
||||
private final int order;
|
||||
|
||||
private boolean holderActive = true;
|
||||
|
||||
|
||||
+12
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,8 +27,8 @@ import org.springframework.util.backoff.BackOff;
|
||||
* A {@link JmsListenerContainerFactory} implementation to build a regular
|
||||
* {@link DefaultMessageListenerContainer}.
|
||||
*
|
||||
* <p>This should be the default for most users and a good transition paths
|
||||
* for those that are used to build such container definition manually.
|
||||
* <p>This should be the default for most users and a good transition path
|
||||
* for those who are used to building such a container definition manually.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.1
|
||||
@@ -67,63 +67,63 @@ public class DefaultJmsListenerContainerFactory
|
||||
/**
|
||||
* @see DefaultMessageListenerContainer#setTaskExecutor
|
||||
*/
|
||||
public void setTaskExecutor(Executor taskExecutor) {
|
||||
public void setTaskExecutor(@Nullable Executor taskExecutor) {
|
||||
this.taskExecutor = taskExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DefaultMessageListenerContainer#setTransactionManager
|
||||
*/
|
||||
public void setTransactionManager(PlatformTransactionManager transactionManager) {
|
||||
public void setTransactionManager(@Nullable PlatformTransactionManager transactionManager) {
|
||||
this.transactionManager = transactionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DefaultMessageListenerContainer#setCacheLevel
|
||||
*/
|
||||
public void setCacheLevel(Integer cacheLevel) {
|
||||
public void setCacheLevel(@Nullable Integer cacheLevel) {
|
||||
this.cacheLevel = cacheLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DefaultMessageListenerContainer#setCacheLevelName
|
||||
*/
|
||||
public void setCacheLevelName(String cacheLevelName) {
|
||||
public void setCacheLevelName(@Nullable String cacheLevelName) {
|
||||
this.cacheLevelName = cacheLevelName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DefaultMessageListenerContainer#setConcurrency
|
||||
*/
|
||||
public void setConcurrency(String concurrency) {
|
||||
public void setConcurrency(@Nullable String concurrency) {
|
||||
this.concurrency = concurrency;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DefaultMessageListenerContainer#setMaxMessagesPerTask
|
||||
*/
|
||||
public void setMaxMessagesPerTask(Integer maxMessagesPerTask) {
|
||||
public void setMaxMessagesPerTask(@Nullable Integer maxMessagesPerTask) {
|
||||
this.maxMessagesPerTask = maxMessagesPerTask;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DefaultMessageListenerContainer#setReceiveTimeout
|
||||
*/
|
||||
public void setReceiveTimeout(Long receiveTimeout) {
|
||||
public void setReceiveTimeout(@Nullable Long receiveTimeout) {
|
||||
this.receiveTimeout = receiveTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DefaultMessageListenerContainer#setRecoveryInterval
|
||||
*/
|
||||
public void setRecoveryInterval(Long recoveryInterval) {
|
||||
public void setRecoveryInterval(@Nullable Long recoveryInterval) {
|
||||
this.recoveryInterval = recoveryInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DefaultMessageListenerContainer#setBackOff
|
||||
*/
|
||||
public void setBackOff(BackOff backOff) {
|
||||
public void setBackOff(@Nullable BackOff backOff) {
|
||||
this.backOff = backOff;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -105,7 +105,7 @@ import org.springframework.util.Assert;
|
||||
* @see DataSourceUtils#getConnection
|
||||
* @see DataSourceUtils#releaseConnection
|
||||
* @see org.springframework.jdbc.core.JdbcTemplate
|
||||
* @see org.springframework.jdbc.datasource.DataSourceTransactionManager
|
||||
* @see org.springframework.jdbc.support.JdbcTransactionManager
|
||||
* @see org.springframework.transaction.jta.JtaTransactionManager
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -109,7 +109,7 @@ import org.springframework.util.CollectionUtils;
|
||||
* @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
|
||||
* @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
|
||||
* @see org.springframework.jdbc.core.JdbcTemplate
|
||||
* @see org.springframework.jdbc.datasource.DataSourceTransactionManager
|
||||
* @see org.springframework.jdbc.support.JdbcTransactionManager
|
||||
* @see org.springframework.transaction.jta.JtaTransactionManager
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
|
||||
+1
-1
@@ -436,7 +436,7 @@ public abstract class SharedEntityManagerCreator {
|
||||
entry.setValue(storedProc.getOutputParameterValue(key.toString()));
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
catch (RuntimeException ex) {
|
||||
entry.setValue(ex);
|
||||
}
|
||||
}
|
||||
|
||||
+40
-14
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,7 @@ import java.sql.SQLException;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import org.eclipse.persistence.sessions.DatabaseLogin;
|
||||
import org.eclipse.persistence.sessions.UnitOfWork;
|
||||
|
||||
import org.springframework.jdbc.datasource.ConnectionHandle;
|
||||
@@ -35,14 +36,20 @@ import org.springframework.transaction.TransactionException;
|
||||
* Persistence Services (EclipseLink). Developed and tested against EclipseLink 2.7;
|
||||
* backwards-compatible with EclipseLink 2.5 and 2.6 at runtime.
|
||||
*
|
||||
* <p>By default, this class acquires an early EclipseLink transaction with an early
|
||||
* JDBC Connection for non-read-only transactions. This allows for mixing JDBC and
|
||||
* JPA/EclipseLink operations in the same transaction, with cross visibility of
|
||||
* <p>By default, this dialect acquires an early EclipseLink transaction with an
|
||||
* early JDBC Connection for non-read-only transactions. This allows for mixing
|
||||
* JDBC and JPA operations in the same transaction, with cross visibility of
|
||||
* their impact. If this is not needed, set the "lazyDatabaseTransaction" flag to
|
||||
* {@code true} or consistently declare all affected transactions as read-only.
|
||||
* As of Spring 4.1.2, this will reliably avoid early JDBC Connection retrieval
|
||||
* and therefore keep EclipseLink in shared cache mode.
|
||||
*
|
||||
* <p><b>NOTE: This dialect supports custom isolation levels with limitations.</b>
|
||||
* Consistent isolation level handling is only guaranteed when all Spring transaction
|
||||
* definitions specify a concrete isolation level, without any default transactions
|
||||
* running concurrently. Otherwise, you may see non-default isolation levels exposed
|
||||
* to default transactions when custom isolation is used in concurrent transactions.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5.2
|
||||
* @see #setLazyDatabaseTransaction
|
||||
@@ -78,19 +85,38 @@ public class EclipseLinkJpaDialect extends DefaultJpaDialect {
|
||||
public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
|
||||
throws PersistenceException, SQLException, TransactionException {
|
||||
|
||||
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
|
||||
int currentIsolationLevel = definition.getIsolationLevel();
|
||||
if (currentIsolationLevel != TransactionDefinition.ISOLATION_DEFAULT) {
|
||||
// Pass custom isolation level on to EclipseLink's DatabaseLogin configuration
|
||||
// (since Spring 4.1.2)
|
||||
// (since Spring 4.1.2 / revised in 5.3.28)
|
||||
UnitOfWork uow = entityManager.unwrap(UnitOfWork.class);
|
||||
uow.getLogin().setTransactionIsolation(definition.getIsolationLevel());
|
||||
DatabaseLogin databaseLogin = uow.getLogin();
|
||||
// Synchronize on shared DatabaseLogin instance for consistent isolation level
|
||||
// set and reset in case of concurrent transactions with different isolation.
|
||||
synchronized (databaseLogin) {
|
||||
int originalIsolationLevel = databaseLogin.getTransactionIsolation();
|
||||
// Apply current isolation level value, if necessary.
|
||||
if (currentIsolationLevel != originalIsolationLevel) {
|
||||
databaseLogin.setTransactionIsolation(currentIsolationLevel);
|
||||
}
|
||||
// Transaction begin including enforced JDBC Connection access
|
||||
// (picking up current isolation level from DatabaseLogin)
|
||||
entityManager.getTransaction().begin();
|
||||
uow.beginEarlyTransaction();
|
||||
entityManager.unwrap(Connection.class);
|
||||
// Restore original isolation level value, if necessary.
|
||||
if (currentIsolationLevel != originalIsolationLevel) {
|
||||
databaseLogin.setTransactionIsolation(originalIsolationLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entityManager.getTransaction().begin();
|
||||
|
||||
if (!definition.isReadOnly() && !this.lazyDatabaseTransaction) {
|
||||
// Begin an early transaction to force EclipseLink to get a JDBC Connection
|
||||
// so that Spring can manage transactions with JDBC as well as EclipseLink.
|
||||
entityManager.unwrap(UnitOfWork.class).beginEarlyTransaction();
|
||||
else {
|
||||
entityManager.getTransaction().begin();
|
||||
if (!definition.isReadOnly() && !this.lazyDatabaseTransaction) {
|
||||
// Begin an early transaction to force EclipseLink to get a JDBC Connection
|
||||
// so that Spring can manage transactions with JDBC as well as EclipseLink.
|
||||
entityManager.unwrap(UnitOfWork.class).beginEarlyTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
+15
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,8 +27,10 @@ import javax.persistence.Query;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.testfixture.io.SerializationTestUtils;
|
||||
import org.springframework.jdbc.datasource.DataSourceUtils;
|
||||
import org.springframework.orm.jpa.domain.DriversLicense;
|
||||
import org.springframework.orm.jpa.domain.Person;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatException;
|
||||
@@ -114,24 +116,34 @@ public abstract class AbstractContainerEntityManagerFactoryIntegrationTests
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLazyLoading() {
|
||||
public void testLazyLoading() throws Exception {
|
||||
try {
|
||||
Person tony = new Person();
|
||||
tony.setFirstName("Tony");
|
||||
tony.setLastName("Blair");
|
||||
tony.setDriversLicense(new DriversLicense("8439DK"));
|
||||
sharedEntityManager.persist(tony);
|
||||
assertThat(DataSourceUtils.getConnection(jdbcTemplate.getDataSource()).getTransactionIsolation())
|
||||
.isEqualTo(TransactionDefinition.ISOLATION_READ_COMMITTED);
|
||||
setComplete();
|
||||
endTransaction();
|
||||
|
||||
transactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
|
||||
startNewTransaction();
|
||||
assertThat(DataSourceUtils.getConnection(jdbcTemplate.getDataSource()).getTransactionIsolation())
|
||||
.isEqualTo(TransactionDefinition.ISOLATION_SERIALIZABLE);
|
||||
sharedEntityManager.clear();
|
||||
Person newTony = entityManagerFactory.createEntityManager().getReference(Person.class, tony.getId());
|
||||
assertThat(tony).isNotSameAs(newTony);
|
||||
endTransaction();
|
||||
|
||||
assertThat(newTony.getDriversLicense()).isNotNull();
|
||||
transactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
|
||||
startNewTransaction();
|
||||
assertThat(DataSourceUtils.getConnection(jdbcTemplate.getDataSource()).getTransactionIsolation())
|
||||
.isEqualTo(TransactionDefinition.ISOLATION_READ_COMMITTED);
|
||||
endTransaction();
|
||||
|
||||
assertThat(newTony.getDriversLicense()).isNotNull();
|
||||
newTony.getDriversLicense().getSerialNumber();
|
||||
}
|
||||
finally {
|
||||
|
||||
+32
-36
@@ -36,41 +36,36 @@ import org.springframework.transaction.reactive.TransactionSynchronizationManage
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.transaction.ReactiveTransactionManager}
|
||||
* implementation for a single R2DBC {@link ConnectionFactory}. This class is
|
||||
* capable of working in any environment with any R2DBC driver, as long as the
|
||||
* setup uses a {@code ConnectionFactory} as its {@link Connection} factory
|
||||
* mechanism. Binds a R2DBC {@code Connection} from the specified
|
||||
* {@code ConnectionFactory} to the current subscriber context, potentially
|
||||
* allowing for one context-bound {@code Connection} per {@code ConnectionFactory}.
|
||||
* {@link org.springframework.transaction.ReactiveTransactionManager} implementation
|
||||
* for a single R2DBC {@link ConnectionFactory}. This class is capable of working
|
||||
* in any environment with any R2DBC driver, as long as the setup uses a
|
||||
* {@code ConnectionFactory} as its {@link Connection} factory mechanism.
|
||||
* Binds a R2DBC {@code Connection} from the specified {@code ConnectionFactory}
|
||||
* to the current subscriber context, potentially allowing for one context-bound
|
||||
* {@code Connection} per {@code ConnectionFactory}.
|
||||
*
|
||||
* <p><b>Note: The {@code ConnectionFactory} that this transaction manager
|
||||
* operates on needs to return independent {@code Connection}s.</b>
|
||||
* The {@code Connection}s may come from a pool (the typical case), but the
|
||||
* {@code ConnectionFactory} must not return scoped {@code Connection}s
|
||||
* or the like. This transaction manager will associate {@code Connection}
|
||||
* with context-bound transactions itself, according to the specified propagation
|
||||
* behavior. It assumes that a separate, independent {@code Connection} can
|
||||
* be obtained even during an ongoing transaction.
|
||||
* <p><b>Note: The {@code ConnectionFactory} that this transaction manager operates
|
||||
* on needs to return independent {@code Connection}s.</b> The {@code Connection}s
|
||||
* typically come from a connection pool but the {@code ConnectionFactory} must not
|
||||
* return specifically scoped or constrained {@code Connection}s. This transaction
|
||||
* manager will associate {@code Connection} with context-bound transactions,
|
||||
* according to the specified propagation behavior. It assumes that a separate,
|
||||
* independent {@code Connection} can be obtained even during an ongoing transaction.
|
||||
*
|
||||
* <p>Application code is required to retrieve the R2DBC Connection via
|
||||
* {@link ConnectionFactoryUtils#getConnection(ConnectionFactory)}
|
||||
* instead of a standard R2DBC-style {@link ConnectionFactory#create()} call.
|
||||
* Spring classes such as {@code DatabaseClient} use this strategy implicitly.
|
||||
* If not used in combination with this transaction manager, the
|
||||
* {@link ConnectionFactoryUtils} lookup strategy behaves exactly like the
|
||||
* native {@code ConnectionFactory} lookup; it can thus be used in a portable fashion.
|
||||
* {@link ConnectionFactoryUtils} lookup strategy behaves exactly like the native
|
||||
* {@code ConnectionFactory} lookup; it can thus be used in a portable fashion.
|
||||
*
|
||||
* <p>Alternatively, you can allow application code to work with the standard
|
||||
* R2DBC lookup pattern {@link ConnectionFactory#create()}, for example for code
|
||||
* that is not aware of Spring at all. In that case, define a
|
||||
* {@link TransactionAwareConnectionFactoryProxy} for your target {@code ConnectionFactory},
|
||||
* and pass that proxy {@code ConnectionFactory} to your DAOs, which will automatically
|
||||
* participate in Spring-managed transactions when accessing it.
|
||||
*
|
||||
* <p>This transaction manager triggers flush callbacks on registered transaction
|
||||
* synchronizations (if synchronization is generally active), assuming resources
|
||||
* operating on the underlying R2DBC {@code Connection}.
|
||||
* <p>Alternatively, you can allow application code to work with the lookup pattern
|
||||
* {@link ConnectionFactory#create()}, for example for code not aware of Spring.
|
||||
* In that case, define a {@link TransactionAwareConnectionFactoryProxy} for your
|
||||
* target {@code ConnectionFactory}, and pass that proxy {@code ConnectionFactory}
|
||||
* to your DAOs which will automatically participate in Spring-managed transactions
|
||||
* when accessing it.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 5.3
|
||||
@@ -89,7 +84,7 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
|
||||
|
||||
/**
|
||||
* Create a new {@code R2dbcTransactionManager} instance.
|
||||
* A ConnectionFactory has to be set to be able to use it.
|
||||
* A {@code ConnectionFactory} has to be set to be able to use it.
|
||||
* @see #setConnectionFactory
|
||||
*/
|
||||
public R2dbcTransactionManager() {}
|
||||
@@ -106,12 +101,13 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
|
||||
|
||||
|
||||
/**
|
||||
* Set the R2DBC {@link ConnectionFactory} that this instance should manage transactions for.
|
||||
* <p>This will typically be a locally defined {@code ConnectionFactory}, for example a connection pool.
|
||||
* <p><b>The {@code ConnectionFactory} passed in here needs to return independent {@link Connection}s.</b>
|
||||
* The {@code Connection}s may come from a pool (the typical case), but the {@code ConnectionFactory}
|
||||
* must not return scoped {@code Connection}s or the like.
|
||||
* @see TransactionAwareConnectionFactoryProxy
|
||||
* Set the R2DBC {@link ConnectionFactory} that this instance should manage transactions
|
||||
* for. This will typically be a locally defined {@code ConnectionFactory}, for example
|
||||
* an R2DBC connection pool.
|
||||
* <p><b>The {@code ConnectionFactory} passed in here needs to return independent
|
||||
* {@link Connection}s.</b> The {@code Connection}s typically come from a connection
|
||||
* pool but the {@code ConnectionFactory} must not return specifically scoped or
|
||||
* constrained {@code Connection}s.
|
||||
*/
|
||||
public void setConnectionFactory(@Nullable ConnectionFactory connectionFactory) {
|
||||
this.connectionFactory = connectionFactory;
|
||||
@@ -142,8 +138,8 @@ public class R2dbcTransactionManager extends AbstractReactiveTransactionManager
|
||||
* transactional connection: "SET TRANSACTION READ ONLY" as understood by Oracle,
|
||||
* MySQL and Postgres.
|
||||
* <p>The exact treatment, including any SQL statement executed on the connection,
|
||||
* can be customized through {@link #prepareTransactionalConnection(Connection, TransactionDefinition)}.
|
||||
* @see #prepareTransactionalConnection(Connection, TransactionDefinition)
|
||||
* can be customized through {@link #prepareTransactionalConnection}.
|
||||
* @see #prepareTransactionalConnection
|
||||
*/
|
||||
public void setEnforceReadOnly(boolean enforceReadOnly) {
|
||||
this.enforceReadOnly = enforceReadOnly;
|
||||
|
||||
+12
-12
@@ -125,11 +125,11 @@ public abstract class MockRestRequestMatchers {
|
||||
* @param name the name of the query parameter whose value(s) will be asserted
|
||||
* @param matcher the Hamcrest matcher to apply to the entire list of values
|
||||
* for the given query parameter
|
||||
* @since 5.3.26
|
||||
* @since 5.3.27
|
||||
* @see #queryParam(String, Matcher...)
|
||||
* @see #queryParam(String, String...)
|
||||
*/
|
||||
public static RequestMatcher queryParam(String name, Matcher<? super List<String>> matcher) {
|
||||
public static RequestMatcher queryParamList(String name, Matcher<? super List<String>> matcher) {
|
||||
return request -> {
|
||||
MultiValueMap<String, String> params = getQueryParams(request);
|
||||
List<String> paramValues = params.get(name);
|
||||
@@ -147,14 +147,14 @@ public abstract class MockRestRequestMatchers {
|
||||
* values, effectively ignoring the additional parameter values. If the number
|
||||
* of provided {@code matchers} exceeds the number of query parameter values,
|
||||
* an {@link AssertionError} will be thrown to signal the mismatch.
|
||||
* <p>See {@link #queryParam(String, Matcher)} for a variant which accepts a
|
||||
* <p>See {@link #queryParamList(String, Matcher)} for a variant which accepts a
|
||||
* {@code Matcher} that applies to the entire list of values as opposed to
|
||||
* applying only to individual values.
|
||||
* @param name the name of the query parameter whose value(s) will be asserted
|
||||
* @param matchers the Hamcrest matchers to apply to individual query parameter
|
||||
* values; the n<sup>th</sup> matcher is applied to the n<sup>th</sup> query
|
||||
* parameter value
|
||||
* @see #queryParam(String, Matcher)
|
||||
* @see #queryParamList(String, Matcher)
|
||||
* @see #queryParam(String, String...)
|
||||
*/
|
||||
@SafeVarargs
|
||||
@@ -175,14 +175,14 @@ public abstract class MockRestRequestMatchers {
|
||||
* parameter values, effectively ignoring the additional parameter values. If
|
||||
* the number of {@code expectedValues} exceeds the number of query parameter
|
||||
* values, an {@link AssertionError} will be thrown to signal the mismatch.
|
||||
* <p>See {@link #queryParam(String, Matcher)} for a variant which accepts a
|
||||
* <p>See {@link #queryParamList(String, Matcher)} for a variant which accepts a
|
||||
* Hamcrest {@code Matcher} that applies to the entire list of values as opposed
|
||||
* to asserting only individual values.
|
||||
* @param name the name of the query parameter whose value(s) will be asserted
|
||||
* @param expectedValues the expected values of individual query parameter values;
|
||||
* the n<sup>th</sup> expected value is compared to the n<sup>th</sup> query
|
||||
* parameter value
|
||||
* @see #queryParam(String, Matcher)
|
||||
* @see #queryParamList(String, Matcher)
|
||||
* @see #queryParam(String, Matcher...)
|
||||
*/
|
||||
public static RequestMatcher queryParam(String name, String... expectedValues) {
|
||||
@@ -211,11 +211,11 @@ public abstract class MockRestRequestMatchers {
|
||||
* @param name the name of the header whose value(s) will be asserted
|
||||
* @param matcher the Hamcrest matcher to apply to the entire list of values
|
||||
* for the given header
|
||||
* @since 5.3.26
|
||||
* @since 5.3.27
|
||||
* @see #header(String, Matcher...)
|
||||
* @see #header(String, String...)
|
||||
*/
|
||||
public static RequestMatcher header(String name, Matcher<? super List<String>> matcher) {
|
||||
public static RequestMatcher headerList(String name, Matcher<? super List<String>> matcher) {
|
||||
return request -> {
|
||||
List<String> headerValues = request.getHeaders().get(name);
|
||||
if (headerValues == null) {
|
||||
@@ -232,13 +232,13 @@ public abstract class MockRestRequestMatchers {
|
||||
* effectively ignoring the additional header values. If the number of
|
||||
* provided {@code matchers} exceeds the number of header values, an
|
||||
* {@link AssertionError} will be thrown to signal the mismatch.
|
||||
* <p>See {@link #header(String, Matcher)} for a variant which accepts a
|
||||
* <p>See {@link #headerList(String, Matcher)} for a variant which accepts a
|
||||
* Hamcrest {@code Matcher} that applies to the entire list of values as
|
||||
* opposed to applying only to individual values.
|
||||
* @param name the name of the header whose value(s) will be asserted
|
||||
* @param matchers the Hamcrest matchers to apply to individual header values;
|
||||
* the n<sup>th</sup> matcher is applied to the n<sup>th</sup> header value
|
||||
* @see #header(String, Matcher)
|
||||
* @see #headerList(String, Matcher)
|
||||
* @see #header(String, String...)
|
||||
*/
|
||||
@SafeVarargs
|
||||
@@ -260,13 +260,13 @@ public abstract class MockRestRequestMatchers {
|
||||
* additional header values. If the number of {@code expectedValues} exceeds the
|
||||
* number of header values, an {@link AssertionError} will be thrown to signal the
|
||||
* mismatch.
|
||||
* <p>See {@link #header(String, Matcher)} for a variant which accepts a
|
||||
* <p>See {@link #headerList(String, Matcher)} for a variant which accepts a
|
||||
* Hamcrest {@code Matcher} that applies to the entire list of values as
|
||||
* opposed to applying only to individual values.
|
||||
* @param name the name of the header whose value(s) will be asserted
|
||||
* @param expectedValues the expected values of individual header values; the
|
||||
* n<sup>th</sup> expected value is compared to the n<sup>th</sup> header value
|
||||
* @see #header(String, Matcher)
|
||||
* @see #headerList(String, Matcher)
|
||||
* @see #header(String, Matcher...)
|
||||
*/
|
||||
public static RequestMatcher header(String name, String... expectedValues) {
|
||||
|
||||
+57
-53
@@ -29,19 +29,17 @@ import org.springframework.mock.http.client.MockClientHttpRequest;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.any;
|
||||
import static org.hamcrest.Matchers.anything;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.either;
|
||||
import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.everyItem;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
|
||||
/**
|
||||
@@ -164,7 +162,7 @@ class MockRestRequestMatchersTests {
|
||||
@Test
|
||||
void headerListMissing() {
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.header("foo", hasSize(2)).match(this.request))
|
||||
.isThrownBy(() -> MockRestRequestMatchers.headerList("foo", hasSize(2)).match(this.request))
|
||||
.withMessage("Expected header <foo> to exist but was null");
|
||||
}
|
||||
|
||||
@@ -172,29 +170,18 @@ class MockRestRequestMatchersTests {
|
||||
void headerListMatchers() throws IOException {
|
||||
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
|
||||
|
||||
MockRestRequestMatchers.header("foo", containsInAnyOrder(endsWith("baz"), endsWith("bar"))).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", contains(is("bar"), is("baz"))).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", contains(is("bar"), anything())).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", hasItem(endsWith("baz"))).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", everyItem(startsWith("ba"))).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", hasSize(2)).match(this.request);
|
||||
MockRestRequestMatchers.headerList("foo", containsInAnyOrder(endsWith("baz"), endsWith("bar"))).match(this.request);
|
||||
MockRestRequestMatchers.headerList("foo", contains(is("bar"), is("baz"))).match(this.request);
|
||||
MockRestRequestMatchers.headerList("foo", contains(is("bar"), anything())).match(this.request);
|
||||
MockRestRequestMatchers.headerList("foo", hasItem(endsWith("baz"))).match(this.request);
|
||||
MockRestRequestMatchers.headerList("foo", everyItem(startsWith("ba"))).match(this.request);
|
||||
MockRestRequestMatchers.headerList("foo", hasSize(2)).match(this.request);
|
||||
|
||||
// These can be a bit ambiguous when reading the test (the compiler selects the list matcher):
|
||||
MockRestRequestMatchers.header("foo", notNullValue()).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", is(anything())).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", allOf(notNullValue(), notNullValue())).match(this.request);
|
||||
MockRestRequestMatchers.headerList("foo", notNullValue()).match(this.request);
|
||||
MockRestRequestMatchers.headerList("foo", is(anything())).match(this.request);
|
||||
MockRestRequestMatchers.headerList("foo", allOf(notNullValue(), notNullValue())).match(this.request);
|
||||
|
||||
// These are not as ambiguous thanks to an inner matcher that is either obviously list-oriented,
|
||||
// string-oriented, or obviously a vararg of matchers
|
||||
|
||||
// list matcher version
|
||||
MockRestRequestMatchers.header("foo", allOf(notNullValue(), hasSize(2))).match(this.request);
|
||||
|
||||
// vararg version
|
||||
MockRestRequestMatchers.header("foo", allOf(notNullValue(), endsWith("ar"))).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", is((any(String.class)))).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", either(is("bar")).or(is(nullValue()))).match(this.request);
|
||||
MockRestRequestMatchers.header("foo", is(notNullValue()), is(notNullValue())).match(this.request);
|
||||
MockRestRequestMatchers.headerList("foo", allOf(notNullValue(), hasSize(2))).match(this.request);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -202,27 +189,41 @@ class MockRestRequestMatchersTests {
|
||||
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
|
||||
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.header("foo", contains(containsString("ba"))).match(this.request))
|
||||
.isThrownBy(() -> MockRestRequestMatchers.headerList("foo", contains(containsString("ba"))).match(this.request))
|
||||
.withMessageContainingAll(
|
||||
"Request header [foo] values",
|
||||
"Expected: iterable containing [a string containing \"ba\"]",
|
||||
"but: not matched: \"baz\"");
|
||||
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.header("foo", hasItem(endsWith("ba"))).match(this.request))
|
||||
.isThrownBy(() -> MockRestRequestMatchers.headerList("foo", hasItem(endsWith("ba"))).match(this.request))
|
||||
.withMessageContainingAll(
|
||||
"Request header [foo] values",
|
||||
"Expected: a collection containing a string ending with \"ba\"",
|
||||
"but: mismatches were: [was \"bar\", was \"baz\"]");
|
||||
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.header("foo", everyItem(endsWith("ar"))).match(this.request))
|
||||
.isThrownBy(() -> MockRestRequestMatchers.headerList("foo", everyItem(endsWith("ar"))).match(this.request))
|
||||
.withMessageContainingAll(
|
||||
"Request header [foo] values",
|
||||
"Expected: every item is a string ending with \"ar\"",
|
||||
"but: an item was \"baz\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void headerListDoesntHideHeaderWithSingleMatcher() throws IOException {
|
||||
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
|
||||
|
||||
MockRestRequestMatchers.header("foo", equalTo("bar")).match(this.request);
|
||||
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.headerList("foo", equalTo("bar")).match(this.request))
|
||||
.withMessageContainingAll(
|
||||
"Request header [foo] values",
|
||||
"Expected: \"bar\"",
|
||||
"but: was <[bar, baz]>");
|
||||
}
|
||||
|
||||
@Test
|
||||
void headers() throws Exception {
|
||||
this.request.getHeaders().put("foo", Arrays.asList("bar", "baz"));
|
||||
@@ -290,7 +291,7 @@ class MockRestRequestMatchersTests {
|
||||
@Test
|
||||
void queryParamListMissing() {
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.queryParam("foo", hasSize(2)).match(this.request))
|
||||
.isThrownBy(() -> MockRestRequestMatchers.queryParamList("foo", hasSize(2)).match(this.request))
|
||||
.withMessage("Expected query param <foo> to exist but was null");
|
||||
}
|
||||
|
||||
@@ -298,29 +299,18 @@ class MockRestRequestMatchersTests {
|
||||
void queryParamListMatchers() throws IOException {
|
||||
this.request.setURI(URI.create("http://www.foo.example/a?foo=bar&foo=baz"));
|
||||
|
||||
MockRestRequestMatchers.queryParam("foo", containsInAnyOrder(endsWith("baz"), endsWith("bar"))).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", contains(is("bar"), is("baz"))).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", contains(is("bar"), anything())).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", hasItem(endsWith("baz"))).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", everyItem(startsWith("ba"))).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", hasSize(2)).match(this.request);
|
||||
MockRestRequestMatchers.queryParamList("foo", containsInAnyOrder(endsWith("baz"), endsWith("bar"))).match(this.request);
|
||||
MockRestRequestMatchers.queryParamList("foo", contains(is("bar"), is("baz"))).match(this.request);
|
||||
MockRestRequestMatchers.queryParamList("foo", contains(is("bar"), anything())).match(this.request);
|
||||
MockRestRequestMatchers.queryParamList("foo", hasItem(endsWith("baz"))).match(this.request);
|
||||
MockRestRequestMatchers.queryParamList("foo", everyItem(startsWith("ba"))).match(this.request);
|
||||
MockRestRequestMatchers.queryParamList("foo", hasSize(2)).match(this.request);
|
||||
|
||||
// These can be a bit ambiguous when reading the test (the compiler selects the list matcher):
|
||||
MockRestRequestMatchers.queryParam("foo", notNullValue()).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", is(anything())).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", allOf(notNullValue(), notNullValue())).match(this.request);
|
||||
MockRestRequestMatchers.queryParamList("foo", notNullValue()).match(this.request);
|
||||
MockRestRequestMatchers.queryParamList("foo", is(anything())).match(this.request);
|
||||
MockRestRequestMatchers.queryParamList("foo", allOf(notNullValue(), notNullValue())).match(this.request);
|
||||
|
||||
// These are not as ambiguous thanks to an inner matcher that is either obviously list-oriented,
|
||||
// string-oriented, or obviously a vararg of matchers
|
||||
|
||||
// list matcher version
|
||||
MockRestRequestMatchers.queryParam("foo", allOf(notNullValue(), hasSize(2))).match(this.request);
|
||||
|
||||
// vararg version
|
||||
MockRestRequestMatchers.queryParam("foo", allOf(notNullValue(), endsWith("ar"))).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", is((any(String.class)))).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", either(is("bar")).or(is(nullValue()))).match(this.request);
|
||||
MockRestRequestMatchers.queryParam("foo", is(notNullValue()), is(notNullValue())).match(this.request);
|
||||
MockRestRequestMatchers.queryParamList("foo", allOf(notNullValue(), hasSize(2))).match(this.request);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -328,27 +318,41 @@ class MockRestRequestMatchersTests {
|
||||
this.request.setURI(URI.create("http://www.foo.example/a?foo=bar&foo=baz"));
|
||||
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.queryParam("foo", contains(containsString("ba"))).match(this.request))
|
||||
.isThrownBy(() -> MockRestRequestMatchers.queryParamList("foo", contains(containsString("ba"))).match(this.request))
|
||||
.withMessageContainingAll(
|
||||
"Query param [foo] values",
|
||||
"Expected: iterable containing [a string containing \"ba\"]",
|
||||
"but: not matched: \"baz\"");
|
||||
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.queryParam("foo", hasItem(endsWith("ba"))).match(this.request))
|
||||
.isThrownBy(() -> MockRestRequestMatchers.queryParamList("foo", hasItem(endsWith("ba"))).match(this.request))
|
||||
.withMessageContainingAll(
|
||||
"Query param [foo] values",
|
||||
"Expected: a collection containing a string ending with \"ba\"",
|
||||
"but: mismatches were: [was \"bar\", was \"baz\"]");
|
||||
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.queryParam("foo", everyItem(endsWith("ar"))).match(this.request))
|
||||
.isThrownBy(() -> MockRestRequestMatchers.queryParamList("foo", everyItem(endsWith("ar"))).match(this.request))
|
||||
.withMessageContainingAll(
|
||||
"Query param [foo] values",
|
||||
"Expected: every item is a string ending with \"ar\"",
|
||||
"but: an item was \"baz\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryParamListDoesntHideQueryParamWithSingleMatcher() throws IOException {
|
||||
this.request.setURI(URI.create("http://www.foo.example/a?foo=bar&foo=baz"));
|
||||
|
||||
MockRestRequestMatchers.queryParam("foo", equalTo("bar")).match(this.request);
|
||||
|
||||
assertThatAssertionError()
|
||||
.isThrownBy(() -> MockRestRequestMatchers.queryParamList("foo", equalTo("bar")).match(this.request))
|
||||
.withMessageContainingAll(
|
||||
"Query param [foo] values",
|
||||
"Expected: \"bar\"",
|
||||
"but: was <[bar, baz]>");
|
||||
}
|
||||
|
||||
private static ThrowableTypeAssert<AssertionError> assertThatAssertionError() {
|
||||
return assertThatExceptionOfType(AssertionError.class);
|
||||
}
|
||||
|
||||
+7
-15
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -106,14 +106,12 @@ public class HttpComponentsClientHttpConnector implements ClientHttpConnector, C
|
||||
Function<? super ClientHttpRequest, Mono<Void>> requestCallback) {
|
||||
|
||||
HttpClientContext context = this.contextProvider.apply(method, uri);
|
||||
|
||||
if (context.getCookieStore() == null) {
|
||||
context.setCookieStore(new BasicCookieStore());
|
||||
}
|
||||
|
||||
HttpComponentsClientHttpRequest request = new HttpComponentsClientHttpRequest(method, uri,
|
||||
context, this.dataBufferFactory);
|
||||
|
||||
HttpComponentsClientHttpRequest request =
|
||||
new HttpComponentsClientHttpRequest(method, uri, context, this.dataBufferFactory);
|
||||
return requestCallback.apply(request).then(Mono.defer(() -> execute(request, context)));
|
||||
}
|
||||
|
||||
@@ -123,7 +121,6 @@ public class HttpComponentsClientHttpConnector implements ClientHttpConnector, C
|
||||
return Mono.create(sink -> {
|
||||
ReactiveResponseConsumer reactiveResponseConsumer =
|
||||
new ReactiveResponseConsumer(new MonoFutureCallbackAdapter(sink, this.dataBufferFactory, context));
|
||||
|
||||
this.client.execute(requestProducer, reactiveResponseConsumer, context, null);
|
||||
});
|
||||
}
|
||||
@@ -133,6 +130,7 @@ public class HttpComponentsClientHttpConnector implements ClientHttpConnector, C
|
||||
this.client.close();
|
||||
}
|
||||
|
||||
|
||||
private static class MonoFutureCallbackAdapter
|
||||
implements FutureCallback<Message<HttpResponse, Publisher<ByteBuffer>>> {
|
||||
|
||||
@@ -144,6 +142,7 @@ public class HttpComponentsClientHttpConnector implements ClientHttpConnector, C
|
||||
|
||||
public MonoFutureCallbackAdapter(MonoSink<ClientHttpResponse> sink,
|
||||
DataBufferFactory dataBufferFactory, HttpClientContext context) {
|
||||
|
||||
this.sink = sink;
|
||||
this.dataBufferFactory = dataBufferFactory;
|
||||
this.context = context;
|
||||
@@ -151,19 +150,12 @@ public class HttpComponentsClientHttpConnector implements ClientHttpConnector, C
|
||||
|
||||
@Override
|
||||
public void completed(Message<HttpResponse, Publisher<ByteBuffer>> result) {
|
||||
HttpComponentsClientHttpResponse response =
|
||||
new HttpComponentsClientHttpResponse(this.dataBufferFactory, result, this.context);
|
||||
this.sink.success(response);
|
||||
this.sink.success(new HttpComponentsClientHttpResponse(this.dataBufferFactory, result, this.context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Exception ex) {
|
||||
Throwable t = ex;
|
||||
if (t instanceof HttpStreamResetException) {
|
||||
HttpStreamResetException httpStreamResetException = (HttpStreamResetException) ex;
|
||||
t = httpStreamResetException.getCause();
|
||||
}
|
||||
this.sink.error(t);
|
||||
this.sink.error(ex instanceof HttpStreamResetException && ex.getCause() != null ? ex.getCause() : ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -53,6 +53,7 @@ class HttpComponentsHeadersAdapter implements MultiValueMap<String, String> {
|
||||
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getFirst(String key) {
|
||||
Header header = this.message.getFirstHeader(key);
|
||||
return (header != null ? header.getValue() : null);
|
||||
|
||||
+4
-2
@@ -79,10 +79,12 @@ class ReactorServerHttpRequest extends AbstractServerHttpRequest {
|
||||
return new URI(resolveBaseUrl(request) + resolveRequestUri(request));
|
||||
}
|
||||
|
||||
private static String resolveBaseUrl(HttpServerRequest request) {
|
||||
private static URI resolveBaseUrl(HttpServerRequest request) throws URISyntaxException {
|
||||
String scheme = request.scheme();
|
||||
int port = request.hostPort();
|
||||
return scheme + "://" + request.hostName() + (usePort(scheme, port) ? ":" + port : "");
|
||||
return (usePort(scheme, port) ?
|
||||
new URI(scheme, null, request.hostName(), port, null, null, null) :
|
||||
new URI(scheme, request.hostName(), null, null));
|
||||
}
|
||||
|
||||
private static boolean usePort(String scheme, int port) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
description = "Spring WebFlux"
|
||||
|
||||
apply plugin: "kotlin"
|
||||
apply plugin: "kotlinx-serialization"
|
||||
|
||||
dependencies {
|
||||
api(project(":spring-beans"))
|
||||
@@ -45,6 +46,7 @@ dependencies {
|
||||
testImplementation('org.apache.httpcomponents.core5:httpcore5-reactive')
|
||||
testImplementation("com.squareup.okhttp3:mockwebserver")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-script-runtime")
|
||||
testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json")
|
||||
testRuntimeOnly("org.jetbrains.kotlin:kotlin-scripting-jsr223")
|
||||
testRuntimeOnly("org.jruby:jruby")
|
||||
testRuntimeOnly("org.python:jython-standalone")
|
||||
|
||||
+10
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -139,8 +139,15 @@ public abstract class AbstractMessageWriterResultHandler extends HandlerResultHa
|
||||
}
|
||||
else {
|
||||
publisher = Mono.justOrEmpty(body);
|
||||
actualElementType = body != null ? ResolvableType.forInstance(body) : bodyType;
|
||||
elementType = (bodyType.toClass() == Object.class && body != null ? actualElementType : bodyType);
|
||||
ResolvableType bodyInstanceType = ResolvableType.forInstance(body);
|
||||
if (bodyType.toClass() == Object.class && body != null) {
|
||||
actualElementType = bodyInstanceType;
|
||||
elementType = bodyInstanceType;
|
||||
}
|
||||
else {
|
||||
actualElementType = (body == null || bodyInstanceType.hasUnresolvableGenerics()) ? bodyType : bodyInstanceType;
|
||||
elementType = bodyType;
|
||||
}
|
||||
}
|
||||
|
||||
if (elementType.resolve() == void.class || elementType.resolve() == Void.class) {
|
||||
|
||||
+1
-1
@@ -112,7 +112,7 @@ public class DelegatingWebFluxConfigurationTests {
|
||||
boolean condition = initializer.getValidator() instanceof LocalValidatorFactoryBean;
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(initializer.getConversionService()).isSameAs(formatterRegistry.getValue());
|
||||
assertThat(codecsConfigurer.getValue().getReaders().size()).isEqualTo(14);
|
||||
assertThat(codecsConfigurer.getValue().getReaders()).hasSize(15);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+3
-3
@@ -152,7 +152,7 @@ public class WebFluxConfigurationSupportTests {
|
||||
assertThat(adapter).isNotNull();
|
||||
|
||||
List<HttpMessageReader<?>> readers = adapter.getMessageReaders();
|
||||
assertThat(readers.size()).isEqualTo(14);
|
||||
assertThat(readers).hasSize(15);
|
||||
|
||||
ResolvableType multiValueMapType = forClassWithGenerics(MultiValueMap.class, String.class, String.class);
|
||||
|
||||
@@ -207,7 +207,7 @@ public class WebFluxConfigurationSupportTests {
|
||||
assertThat(handler.getOrder()).isEqualTo(0);
|
||||
|
||||
List<HttpMessageWriter<?>> writers = handler.getMessageWriters();
|
||||
assertThat(writers.size()).isEqualTo(13);
|
||||
assertThat(writers).hasSize(14);
|
||||
|
||||
assertHasMessageWriter(writers, forClass(byte[].class), APPLICATION_OCTET_STREAM);
|
||||
assertHasMessageWriter(writers, forClass(ByteBuffer.class), APPLICATION_OCTET_STREAM);
|
||||
@@ -235,7 +235,7 @@ public class WebFluxConfigurationSupportTests {
|
||||
assertThat(handler.getOrder()).isEqualTo(100);
|
||||
|
||||
List<HttpMessageWriter<?>> writers = handler.getMessageWriters();
|
||||
assertThat(writers.size()).isEqualTo(13);
|
||||
assertThat(writers).hasSize(14);
|
||||
|
||||
assertHasMessageWriter(writers, forClass(byte[].class), APPLICATION_OCTET_STREAM);
|
||||
assertHasMessageWriter(writers, forClass(ByteBuffer.class), APPLICATION_OCTET_STREAM);
|
||||
|
||||
+15
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -62,6 +62,7 @@ import static org.springframework.web.testfixture.method.ResolvableMethod.on;
|
||||
* Unit tests for {@link AbstractMessageWriterResultHandler}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class MessageWriterResultHandlerTests {
|
||||
|
||||
@@ -180,6 +181,17 @@ public class MessageWriterResultHandlerTests {
|
||||
assertResponseBody("[{\"id\":123,\"name\":\"foo\"},{\"id\":456,\"name\":\"bar\"}]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jacksonTypeWithSubTypeAndObjectReturnValue() {
|
||||
MethodParameter returnType = on(TestController.class).resolveReturnType(Object.class);
|
||||
|
||||
SimpleBean body = new SimpleBean(123L, "foo");
|
||||
this.resultHandler.writeBody(body, returnType, this.exchange).block(Duration.ofSeconds(5));
|
||||
|
||||
assertThat(this.exchange.getResponse().getHeaders().getContentType()).isEqualTo(APPLICATION_JSON);
|
||||
assertResponseBody("{\"id\":123,\"name\":\"foo\"}");
|
||||
}
|
||||
|
||||
|
||||
private void assertResponseBody(String responseBody) {
|
||||
StepVerifier.create(this.exchange.getResponse().getBody())
|
||||
@@ -287,6 +299,8 @@ public class MessageWriterResultHandlerTests {
|
||||
Identifiable identifiable() { return null; }
|
||||
|
||||
List<Identifiable> listIdentifiable() { return null; }
|
||||
|
||||
Object object() { return null; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-2
@@ -47,6 +47,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Stephane Maldini
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.0
|
||||
*/
|
||||
class RequestMappingIntegrationTests extends AbstractRequestMappingIntegrationTests {
|
||||
@@ -91,8 +92,8 @@ class RequestMappingIntegrationTests extends AbstractRequestMappingIntegrationTe
|
||||
void stream(HttpServer httpServer) throws Exception {
|
||||
startServer(httpServer);
|
||||
|
||||
String[] expected = {"0", "1", "2", "3", "4"};
|
||||
assertThat(performGet("/stream", new HttpHeaders(), String[].class).getBody()).isEqualTo(expected);
|
||||
Integer[] expected = {0, 1, 2, 3, 4};
|
||||
assertThat(performGet("/stream", new HttpHeaders(), Integer[].class).getBody()).isEqualTo(expected);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.reactive.result.method.annotation
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.core.ResolvableType
|
||||
import org.springframework.core.io.buffer.DataBuffer
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.http.codec.EncoderHttpMessageWriter
|
||||
import org.springframework.http.codec.HttpMessageWriter
|
||||
import org.springframework.http.codec.json.Jackson2JsonEncoder
|
||||
import org.springframework.http.codec.json.KotlinSerializationJsonEncoder
|
||||
import org.springframework.util.ObjectUtils
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder
|
||||
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest
|
||||
import org.springframework.web.testfixture.method.ResolvableMethod
|
||||
import org.springframework.web.testfixture.server.MockServerWebExchange
|
||||
import reactor.test.StepVerifier
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Kotlin unit tests for {@link AbstractMessageWriterResultHandler}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class KotlinMessageWriterResultHandlerTests {
|
||||
|
||||
private val resultHandler = initResultHandler()
|
||||
|
||||
private val exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/path"))
|
||||
|
||||
|
||||
private fun initResultHandler(vararg writers: HttpMessageWriter<*>): AbstractMessageWriterResultHandler {
|
||||
val writerList = if (ObjectUtils.isEmpty(writers)) {
|
||||
listOf(
|
||||
EncoderHttpMessageWriter(KotlinSerializationJsonEncoder()),
|
||||
EncoderHttpMessageWriter(Jackson2JsonEncoder())
|
||||
)
|
||||
} else {
|
||||
listOf(*writers)
|
||||
}
|
||||
val resolver = RequestedContentTypeResolverBuilder().build()
|
||||
return object : AbstractMessageWriterResultHandler(writerList, resolver) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nonSuspendWithoutResponseEntity() {
|
||||
val returnType = ResolvableMethod.on(SampleController::class.java)
|
||||
.resolveReturnType(List::class.java, Person::class.java)
|
||||
val body = listOf(Person(UserId(1), "John"))
|
||||
resultHandler.writeBody(body, returnType, exchange).block(Duration.ofSeconds(5))
|
||||
|
||||
Assertions.assertThat(exchange.response.headers.contentType).isEqualTo(MediaType.APPLICATION_JSON)
|
||||
assertResponseBody("[{\"userId\":1,\"name\":\"John\"}]")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nonSuspendWithResponseEntity() {
|
||||
val returnType = ResolvableMethod.on(SampleController::class.java)
|
||||
.returning(ResolvableType.forClassWithGenerics(ResponseEntity::class.java,
|
||||
ResolvableType.forClassWithGenerics(List::class.java, Person::class.java)))
|
||||
.build().returnType()
|
||||
val body = ResponseEntity.ok(listOf(Person(UserId(1), "John")))
|
||||
resultHandler.writeBody(body.body, returnType.nested(), returnType, exchange).block(Duration.ofSeconds(5))
|
||||
|
||||
Assertions.assertThat(exchange.response.headers.contentType).isEqualTo(MediaType.APPLICATION_JSON)
|
||||
assertResponseBody("[{\"userId\":1,\"name\":\"John\"}]")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun suspendWithoutResponseEntity() {
|
||||
val returnType = ResolvableMethod.on(CoroutinesSampleController::class.java)
|
||||
.resolveReturnType(List::class.java, Person::class.java)
|
||||
val body = listOf(Person(UserId(1), "John"))
|
||||
resultHandler.writeBody(body, returnType, exchange).block(Duration.ofSeconds(5))
|
||||
|
||||
Assertions.assertThat(exchange.response.headers.contentType).isEqualTo(MediaType.APPLICATION_JSON)
|
||||
assertResponseBody("[{\"userId\":1,\"name\":\"John\"}]")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun suspendWithResponseEntity() {
|
||||
val returnType = ResolvableMethod.on(CoroutinesSampleController::class.java)
|
||||
.returning(ResolvableType.forClassWithGenerics(ResponseEntity::class.java,
|
||||
ResolvableType.forClassWithGenerics(List::class.java, Person::class.java)))
|
||||
.build().returnType()
|
||||
val body = ResponseEntity.ok(listOf(Person(UserId(1), "John")))
|
||||
resultHandler.writeBody(body.body, returnType.nested(), returnType, exchange).block(Duration.ofSeconds(5))
|
||||
|
||||
Assertions.assertThat(exchange.response.headers.contentType).isEqualTo(MediaType.APPLICATION_JSON)
|
||||
assertResponseBody("[{\"userId\":1,\"name\":\"John\"}]")
|
||||
}
|
||||
|
||||
private fun assertResponseBody(responseBody: String) {
|
||||
StepVerifier.create(exchange.response.body)
|
||||
.consumeNextWith { buf: DataBuffer ->
|
||||
Assertions.assertThat(
|
||||
buf.toString(StandardCharsets.UTF_8)
|
||||
).isEqualTo(responseBody)
|
||||
}
|
||||
.expectComplete()
|
||||
.verify()
|
||||
}
|
||||
|
||||
@RestController
|
||||
class SampleController {
|
||||
|
||||
@GetMapping("/non-suspend-with-response-entity")
|
||||
fun withResponseEntity(): ResponseEntity<List<Person>> =
|
||||
TODO()
|
||||
|
||||
@GetMapping("/non-suspend-without-response-entity")
|
||||
fun withoutResponseEntity(): List<Person> =
|
||||
TODO()
|
||||
}
|
||||
|
||||
@RestController
|
||||
class CoroutinesSampleController {
|
||||
|
||||
@GetMapping("/suspend-with-response-entity")
|
||||
suspend fun suspendAndResponseEntity(): ResponseEntity<List<Person>> =
|
||||
TODO()
|
||||
|
||||
@GetMapping("/suspend-without-response-entity")
|
||||
suspend fun suspendWithoutResponseEntity(): List<Person> =
|
||||
TODO()
|
||||
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Person(
|
||||
val userId: UserId,
|
||||
val name: String,
|
||||
)
|
||||
|
||||
@JvmInline
|
||||
@Serializable
|
||||
value class UserId(val id: Int)
|
||||
|
||||
}
|
||||
+10
-4
@@ -48,6 +48,7 @@ import org.springframework.web.servlet.ModelAndView;
|
||||
* <a href="https://www.w3.org/TR/eventsource/">Server-Sent Events</a>.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.3.2
|
||||
*/
|
||||
final class SseServerResponse extends AbstractServerResponse {
|
||||
@@ -90,7 +91,7 @@ final class SseServerResponse extends AbstractServerResponse {
|
||||
}
|
||||
|
||||
DefaultAsyncServerResponse.writeAsync(request, response, result);
|
||||
this.sseConsumer.accept(new DefaultSseBuilder(response, context, result));
|
||||
this.sseConsumer.accept(new DefaultSseBuilder(response, context, result, this.headers()));
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -113,15 +114,19 @@ final class SseServerResponse extends AbstractServerResponse {
|
||||
|
||||
private final List<HttpMessageConverter<?>> messageConverters;
|
||||
|
||||
private final HttpHeaders httpHeaders;
|
||||
|
||||
private final StringBuilder builder = new StringBuilder();
|
||||
|
||||
private boolean sendFailed;
|
||||
|
||||
|
||||
public DefaultSseBuilder(HttpServletResponse response, Context context, DeferredResult<?> deferredResult) {
|
||||
public DefaultSseBuilder(HttpServletResponse response, Context context, DeferredResult<?> deferredResult,
|
||||
HttpHeaders httpHeaders) {
|
||||
this.outputMessage = new ServletServerHttpResponse(response);
|
||||
this.deferredResult = deferredResult;
|
||||
this.messageConverters = context.messageConverters();
|
||||
this.httpHeaders = httpHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -206,7 +211,7 @@ final class SseServerResponse extends AbstractServerResponse {
|
||||
for (HttpMessageConverter<?> converter : this.messageConverters) {
|
||||
if (converter.canWrite(dataClass, MediaType.APPLICATION_JSON)) {
|
||||
HttpMessageConverter<Object> objectConverter = (HttpMessageConverter<Object>) converter;
|
||||
ServerHttpResponse response = new MutableHeadersServerHttpResponse(this.outputMessage);
|
||||
ServerHttpResponse response = new MutableHeadersServerHttpResponse(this.outputMessage, this.httpHeaders);
|
||||
objectConverter.write(data, MediaType.APPLICATION_JSON, response);
|
||||
this.outputMessage.getBody().write(NL_NL);
|
||||
this.outputMessage.flush();
|
||||
@@ -276,9 +281,10 @@ final class SseServerResponse extends AbstractServerResponse {
|
||||
|
||||
private final HttpHeaders mutableHeaders = new HttpHeaders();
|
||||
|
||||
public MutableHeadersServerHttpResponse(ServerHttpResponse delegate) {
|
||||
public MutableHeadersServerHttpResponse(ServerHttpResponse delegate, HttpHeaders headers) {
|
||||
super(delegate);
|
||||
this.mutableHeaders.putAll(delegate.getHeaders());
|
||||
this.mutableHeaders.putAll(headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+21
-3
@@ -776,9 +776,27 @@ public class MvcUriComponentsBuilder {
|
||||
}
|
||||
|
||||
else if (controllerType.isInterface()) {
|
||||
return (T) Proxy.newProxyInstance(controllerType.getClassLoader(),
|
||||
new Class<?>[] {controllerType, MethodInvocationInfo.class},
|
||||
interceptor);
|
||||
ClassLoader classLoader = controllerType.getClassLoader();
|
||||
if (classLoader == null) {
|
||||
// JDK bootstrap loader -> use MethodInvocationInfo ClassLoader instead.
|
||||
classLoader = MethodInvocationInfo.class.getClassLoader();
|
||||
}
|
||||
else if (classLoader.getParent() == null) {
|
||||
// Potentially the JDK platform loader on JDK 9+
|
||||
ClassLoader miiClassLoader = MethodInvocationInfo.class.getClassLoader();
|
||||
ClassLoader miiParent = miiClassLoader.getParent();
|
||||
while (miiParent != null) {
|
||||
if (classLoader == miiParent) {
|
||||
// Suggested ClassLoader is ancestor of MethodInvocationInfo ClassLoader
|
||||
// -> use MethodInvocationInfo ClassLoader itself instead.
|
||||
classLoader = miiClassLoader;
|
||||
break;
|
||||
}
|
||||
miiParent = miiParent.getParent();
|
||||
}
|
||||
}
|
||||
Class<?>[] ifcs = new Class<?>[] {controllerType, MethodInvocationInfo.class};
|
||||
return (T) Proxy.newProxyInstance(classLoader, ifcs, interceptor);
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
+28
@@ -33,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
class SseServerResponseTests {
|
||||
|
||||
@@ -89,6 +90,33 @@ class SseServerResponseTests {
|
||||
assertThat(this.mockResponse.getContentAsString()).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sendObjectWithPrettyPrint() throws Exception {
|
||||
Person person = new Person("John Doe", 42);
|
||||
ServerResponse response = ServerResponse.sse(sse -> {
|
||||
try {
|
||||
sse.send(person);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
});
|
||||
|
||||
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
|
||||
converter.setPrettyPrint(true);
|
||||
ServerResponse.Context context = () -> Collections.singletonList(converter);
|
||||
|
||||
ModelAndView mav = response.writeTo(this.mockRequest, this.mockResponse, context);
|
||||
assertThat(mav).isNull();
|
||||
|
||||
String expected = "data:{\n" +
|
||||
"data: \"name\" : \"John Doe\",\n" +
|
||||
"data: \"age\" : 42\n" +
|
||||
"data:}\n" +
|
||||
"\n";
|
||||
assertThat(this.mockResponse.getContentAsString()).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void builder() throws Exception {
|
||||
ServerResponse response = ServerResponse.sse(sse -> {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user