mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
16 Commits
v5.2.23.RELEASE
...
5.2.x
| Author | SHA1 | Date | |
|---|---|---|---|
| b40e8e047c | |||
| a789e29a52 | |||
| e6d5b385f6 | |||
| c206866fed | |||
| ca01ee05f7 | |||
| 2e72269eb0 | |||
| ac82b5cd36 | |||
| 921635b3bc | |||
| 7c8fed7f3b | |||
| 89a3d64ada | |||
| a8bc09925d | |||
| e246b47f4d | |||
| 965a639275 | |||
| cbbb2871b3 | |||
| 18403cddea | |||
| ad191277a6 |
@@ -23,6 +23,7 @@ buildSrc/build
|
||||
/spring-*/build
|
||||
/spring-core/kotlin-coroutines/build
|
||||
/framework-bom/build
|
||||
/framework-docs/build
|
||||
/integration-tests/build
|
||||
/src/asciidoc/build
|
||||
target/
|
||||
@@ -41,3 +42,5 @@ out
|
||||
test-output
|
||||
atlassian-ide-plugin.xml
|
||||
.gradletasknamecache
|
||||
|
||||
cached-antora-playbook.yml
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:focal-20220302
|
||||
FROM ubuntu:jammy-20230624
|
||||
|
||||
ADD setup.sh /setup.sh
|
||||
ADD get-jdk-url.sh /get-jdk-url.sh
|
||||
|
||||
@@ -3,10 +3,10 @@ set -e
|
||||
|
||||
case "$1" in
|
||||
java8)
|
||||
echo "https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u322-b06/OpenJDK8U-jdk_x64_linux_hotspot_8u322b06.tar.gz"
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/8u372+7/bellsoft-jdk8u372+7-linux-amd64.tar.gz"
|
||||
;;
|
||||
java11)
|
||||
echo "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.14.1%2B1/OpenJDK11U-jdk_x64_linux_hotspot_11.0.14.1_1.tar.gz"
|
||||
echo "https://github.com/bell-sw/Liberica/releases/download/11.0.19%2B7/bellsoft-jdk11.0.19+7-linux-amd64.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo $"Unknown java version"
|
||||
|
||||
@@ -277,7 +277,6 @@ jobs:
|
||||
download_artifacts: false
|
||||
save_build_info: true
|
||||
- task: promote
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: M
|
||||
@@ -322,7 +321,6 @@ jobs:
|
||||
download_artifacts: false
|
||||
save_build_info: true
|
||||
- task: promote
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RC
|
||||
@@ -367,7 +365,6 @@ jobs:
|
||||
download_artifacts: true
|
||||
save_build_info: true
|
||||
- task: promote
|
||||
image: ci-image
|
||||
file: git-repo/ci/tasks/promote-version.yml
|
||||
params:
|
||||
RELEASE_TYPE: RELEASE
|
||||
|
||||
@@ -6,11 +6,11 @@ CONFIG_DIR=git-repo/ci/config
|
||||
version=$( cat artifactory-repo/build-info.json | jq -r '.buildInfo.modules[0].id' | sed 's/.*:.*:\(.*\)/\1/' )
|
||||
export BUILD_INFO_LOCATION=$(pwd)/artifactory-repo/build-info.json
|
||||
|
||||
java -jar /opt/concourse-release-scripts.jar \
|
||||
java -jar /concourse-release-scripts.jar \
|
||||
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
|
||||
publishToCentral $RELEASE_TYPE $BUILD_INFO_LOCATION artifactory-repo || { exit 1; }
|
||||
|
||||
java -jar /opt/concourse-release-scripts.jar \
|
||||
java -jar /concourse-release-scripts.jar \
|
||||
--spring.config.location=${CONFIG_DIR}/release-scripts.yml \
|
||||
promote $RELEASE_TYPE $BUILD_INFO_LOCATION || { exit 1; }
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: springio/github-changelog-generator
|
||||
tag: '0.0.7'
|
||||
tag: '0.0.8'
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
- name: git-repo
|
||||
- name: artifactory-repo
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
---
|
||||
platform: linux
|
||||
image_resource:
|
||||
type: registry-image
|
||||
source:
|
||||
repository: springio/concourse-release-scripts
|
||||
tag: '0.3.4'
|
||||
username: ((docker-hub-username))
|
||||
password: ((docker-hub-password))
|
||||
inputs:
|
||||
- name: git-repo
|
||||
- name: artifactory-repo
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
version=5.2.23.BUILD-SNAPSHOT
|
||||
version=5.2.26.BUILD-SNAPSHOT
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
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.4.0.RELEASE")
|
||||
asciidoctorExt("io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.6.1")
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -107,9 +107,8 @@ dokka {
|
||||
}
|
||||
|
||||
task downloadResources(type: Download) {
|
||||
def version = "0.2.1.RELEASE"
|
||||
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"
|
||||
|
||||
+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
|
||||
|
||||
+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);
|
||||
}
|
||||
|
||||
+14
-2
@@ -264,11 +264,23 @@ public enum SpelMessage {
|
||||
|
||||
/** @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.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;
|
||||
|
||||
+38
-1
@@ -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.
|
||||
@@ -25,11 +25,19 @@ import org.springframework.lang.Nullable;
|
||||
* @author Juergen Hoeller
|
||||
* @author Phillip Webb
|
||||
* @author Andy Clement
|
||||
* @author Sam Brannen
|
||||
* @since 3.0
|
||||
* @see org.springframework.expression.spel.standard.SpelExpressionParser#SpelExpressionParser(SpelParserConfiguration)
|
||||
*/
|
||||
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;
|
||||
|
||||
|
||||
private static final SpelCompilerMode defaultCompilerMode;
|
||||
|
||||
static {
|
||||
@@ -50,6 +58,8 @@ public class SpelParserConfiguration {
|
||||
|
||||
private final int maximumAutoGrowSize;
|
||||
|
||||
private final int maximumExpressionLength;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code SpelParserConfiguration} instance with default settings.
|
||||
@@ -98,11 +108,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -142,4 +171,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-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,6 +25,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
|
||||
|
||||
+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
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ public class OperatorMatches extends Operator {
|
||||
* Maximum number of characters permitted in a regular expression.
|
||||
* @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;
|
||||
|
||||
|
||||
+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
|
||||
|
||||
+12
-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,15 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkExpressionLength(String string) {
|
||||
if (string != null) {
|
||||
int maxLength = this.configuration.getMaximumExpressionLength();
|
||||
if (string.length() > maxLength) {
|
||||
throw new SpelEvaluationException(SpelMessage.MAX_EXPRESSION_LENGTH_EXCEEDED, maxLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// expression
|
||||
// : logicalOrExpression
|
||||
// ( (ASSIGN^ logicalOrExpression)
|
||||
|
||||
+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);
|
||||
|
||||
+19
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -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();
|
||||
|
||||
+69
-8
@@ -24,6 +24,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.AccessException;
|
||||
@@ -38,6 +40,7 @@ import org.springframework.expression.MethodResolver;
|
||||
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;
|
||||
@@ -59,6 +62,40 @@ import static org.assertj.core.api.Assertions.within;
|
||||
*/
|
||||
public class EvaluationTests extends AbstractExpressionTests {
|
||||
|
||||
@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
|
||||
public void testCreateListsOnAttemptToIndexNull01() throws EvaluationException, ParseException {
|
||||
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true));
|
||||
@@ -201,15 +238,13 @@ public 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);
|
||||
}
|
||||
|
||||
@@ -355,9 +390,26 @@ public class EvaluationTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
// assignment
|
||||
|
||||
@Test
|
||||
public void testAssignmentToVariables01() {
|
||||
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
|
||||
@@ -1367,6 +1419,15 @@ public 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 {
|
||||
|
||||
|
||||
+60
-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 @@ public 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");
|
||||
@@ -404,11 +401,11 @@ public class OperatorTests extends AbstractExpressionTests {
|
||||
assertThat(expr.toStringAST()).isEqualTo("(2 + 3)");
|
||||
|
||||
// use as a unary operator
|
||||
evaluate("+5d",5d,Double.class);
|
||||
evaluate("+5L",5L,Long.class);
|
||||
evaluate("+5",5,Integer.class);
|
||||
evaluate("+new java.math.BigDecimal('5')", new BigDecimal("5"),BigDecimal.class);
|
||||
evaluateAndCheckError("+'abc'",SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
|
||||
evaluate("+5d", 5d, Double.class);
|
||||
evaluate("+5L", 5L, Long.class);
|
||||
evaluate("+5", 5, Integer.class);
|
||||
evaluate("+new java.math.BigDecimal('5')", new BigDecimal("5"), BigDecimal.class);
|
||||
evaluateAndCheckError("+'abc'", SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES);
|
||||
|
||||
// string concatenation
|
||||
evaluate("'abc'+'def'","abcdef",String.class);
|
||||
@@ -587,6 +584,59 @@ public 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
|
||||
public void testLongs() {
|
||||
evaluate("3L == 4L", false, Boolean.class);
|
||||
|
||||
+13
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,7 +21,9 @@ import java.util.function.Consumer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionException;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.SpelMessage;
|
||||
import org.springframework.expression.spel.SpelNode;
|
||||
import org.springframework.expression.spel.SpelParseException;
|
||||
@@ -35,9 +37,19 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
/**
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
public class SpelParserTests {
|
||||
|
||||
@Test // gh-30464
|
||||
public void nullExpression() {
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
String expression = null;
|
||||
Expression expr = parser.parseExpression(expression);
|
||||
Object result = expr.getValue();
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void theMostBasic() {
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
+13
-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.
|
||||
@@ -20,6 +20,7 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.http.server.PathContainer;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Parser for URI path patterns producing {@link PathPattern} instances that can
|
||||
@@ -103,6 +104,17 @@ public class PathPatternParser {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepare the given pattern for use in matching to full URL paths.
|
||||
* <p>By default, prepend a leading slash if needed for non-empty patterns.
|
||||
* @param pattern the pattern to initialize
|
||||
* @return the updated pattern
|
||||
* @since 5.2.25
|
||||
*/
|
||||
public String initFullPathPattern(String pattern) {
|
||||
return (StringUtils.hasLength(pattern) && !pattern.startsWith("/") ? "/" + pattern : pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the path pattern content, a character at a time, breaking it into
|
||||
* path elements around separator boundaries and verifying the structure at each
|
||||
|
||||
+4
-5
@@ -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,10 +109,9 @@ public abstract class RequestPredicates {
|
||||
*/
|
||||
public static RequestPredicate path(String pattern) {
|
||||
Assert.notNull(pattern, "'pattern' must not be null");
|
||||
if (!pattern.isEmpty() && !pattern.startsWith("/")) {
|
||||
pattern = "/" + pattern;
|
||||
}
|
||||
return pathPredicates(PathPatternParser.defaultInstance).apply(pattern);
|
||||
PathPatternParser parser = PathPatternParser.defaultInstance;
|
||||
pattern = parser.initFullPathPattern(pattern);
|
||||
return pathPredicates(parser).apply(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
-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.
|
||||
@@ -28,9 +28,9 @@ import org.springframework.beans.BeansException;
|
||||
import org.springframework.http.server.PathContainer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
|
||||
/**
|
||||
* Abstract base class for URL-mapped
|
||||
@@ -185,8 +185,9 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||
Object resolvedHandler = handler;
|
||||
|
||||
// Parse path pattern
|
||||
urlPath = prependLeadingSlash(urlPath);
|
||||
PathPattern pattern = getPathPatternParser().parse(urlPath);
|
||||
PathPatternParser parser = getPathPatternParser();
|
||||
urlPath = parser.initFullPathPattern(urlPath);
|
||||
PathPattern pattern = parser.parse(urlPath);
|
||||
if (this.handlerMap.containsKey(pattern)) {
|
||||
Object existingHandler = this.handlerMap.get(pattern);
|
||||
if (existingHandler != null && existingHandler != resolvedHandler) {
|
||||
@@ -215,14 +216,4 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
|
||||
return (handler instanceof String ? "'" + handler + "'" : handler.toString());
|
||||
}
|
||||
|
||||
|
||||
private static String prependLeadingSlash(String pattern) {
|
||||
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
|
||||
return "/" + pattern;
|
||||
}
|
||||
else {
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-14
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,7 +35,6 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.http.server.PathContainer;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
@@ -86,8 +85,9 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
||||
public void registerHandlers(Map<String, ResourceWebHandler> handlerMap) {
|
||||
this.handlerMap.clear();
|
||||
handlerMap.forEach((rawPattern, resourceWebHandler) -> {
|
||||
rawPattern = prependLeadingSlash(rawPattern);
|
||||
PathPattern pattern = PathPatternParser.defaultInstance.parse(rawPattern);
|
||||
PathPatternParser parser = PathPatternParser.defaultInstance;
|
||||
rawPattern = parser.initFullPathPattern(rawPattern);
|
||||
PathPattern pattern = parser.parse(rawPattern);
|
||||
this.handlerMap.put(pattern, resourceWebHandler);
|
||||
});
|
||||
}
|
||||
@@ -173,14 +173,4 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private static String prependLeadingSlash(String pattern) {
|
||||
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
|
||||
return "/" + pattern;
|
||||
}
|
||||
else {
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-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.
|
||||
@@ -564,11 +564,9 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<PathPattern> result = new ArrayList<>(patterns.length);
|
||||
for (String path : patterns) {
|
||||
if (StringUtils.hasText(path) && !path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
result.add(parser.parse(path));
|
||||
for (String pattern : patterns) {
|
||||
pattern = parser.initFullPathPattern(pattern);
|
||||
result.add(parser.parse(pattern));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
+9
-8
@@ -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.
|
||||
@@ -107,10 +107,9 @@ public abstract class RequestPredicates {
|
||||
*/
|
||||
public static RequestPredicate path(String pattern) {
|
||||
Assert.notNull(pattern, "'pattern' must not be null");
|
||||
if (!pattern.isEmpty() && !pattern.startsWith("/")) {
|
||||
pattern = "/" + pattern;
|
||||
}
|
||||
return pathPredicates(PathPatternParser.defaultInstance).apply(pattern);
|
||||
PathPatternParser parser = PathPatternParser.defaultInstance;
|
||||
pattern = parser.initFullPathPattern(pattern);
|
||||
return pathPredicates(parser).apply(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -333,14 +332,14 @@ public abstract class RequestPredicates {
|
||||
void method(Set<HttpMethod> methods);
|
||||
|
||||
/**
|
||||
* Receive notification of an path predicate.
|
||||
* Receive notification of a path predicate.
|
||||
* @param pattern the path pattern that makes up the predicate
|
||||
* @see RequestPredicates#path(String)
|
||||
*/
|
||||
void path(String pattern);
|
||||
|
||||
/**
|
||||
* Receive notification of an path extension predicate.
|
||||
* Receive notification of a path extension predicate.
|
||||
* @param extension the path extension that makes up the predicate
|
||||
* @see RequestPredicates#pathExtension(String)
|
||||
*/
|
||||
@@ -426,11 +425,11 @@ public abstract class RequestPredicates {
|
||||
void unknown(RequestPredicate predicate);
|
||||
}
|
||||
|
||||
|
||||
private static class HttpMethodPredicate implements RequestPredicate {
|
||||
|
||||
private final Set<HttpMethod> httpMethods;
|
||||
|
||||
|
||||
public HttpMethodPredicate(HttpMethod httpMethod) {
|
||||
Assert.notNull(httpMethod, "HttpMethod must not be null");
|
||||
this.httpMethods = EnumSet.of(httpMethod);
|
||||
@@ -641,12 +640,14 @@ public abstract class RequestPredicates {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class PathExtensionPredicate implements RequestPredicate {
|
||||
|
||||
private final Predicate<String> extensionPredicate;
|
||||
|
||||
@Nullable
|
||||
private final String extension;
|
||||
|
||||
public PathExtensionPredicate(Predicate<String> extensionPredicate) {
|
||||
Assert.notNull(extensionPredicate, "Predicate must not be null");
|
||||
this.extensionPredicate = extensionPredicate;
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+3
-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.
|
||||
@@ -34,6 +34,7 @@ import org.springframework.util.PathMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
|
||||
/**
|
||||
* A logical disjunction (' || ') request condition that matches a request
|
||||
@@ -142,9 +143,7 @@ public class PatternsRequestCondition extends AbstractRequestCondition<PatternsR
|
||||
}
|
||||
Set<String> result = new LinkedHashSet<>(patterns.length);
|
||||
for (String pattern : patterns) {
|
||||
if (StringUtils.hasLength(pattern) && !pattern.startsWith("/")) {
|
||||
pattern = "/" + pattern;
|
||||
}
|
||||
pattern = PathPatternParser.defaultInstance.initFullPathPattern(pattern);
|
||||
result.add(pattern);
|
||||
}
|
||||
return result;
|
||||
|
||||
+3
-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.
|
||||
@@ -65,6 +65,7 @@ import org.springframework.web.servlet.DispatcherServlet;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
|
||||
/**
|
||||
* Creates instances of {@link org.springframework.web.util.UriComponentsBuilder}
|
||||
@@ -545,9 +546,7 @@ public class MvcUriComponentsBuilder {
|
||||
String typePath = getClassMapping(controllerType);
|
||||
String methodPath = getMethodMapping(method);
|
||||
String path = pathMatcher.combine(typePath, methodPath);
|
||||
if (StringUtils.hasLength(path) && !path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
path = PathPatternParser.defaultInstance.initFullPathPattern(path);
|
||||
builder.path(path);
|
||||
|
||||
return applyContributors(builder, method, args);
|
||||
|
||||
Reference in New Issue
Block a user