Skip to content

Commit e246b47

Browse files
sbrannenbclozel
authored andcommitted
Disable variable assignment in SimpleEvaluationContext
This commit introduces infrastructure to differentiate between programmatic setting of a variable in an EvaluationContext versus the assignment of a variable within a SpEL expression using the assignment operator (=). In addition, this commit disables variable assignment within expressions when using the SimpleEvaluationContext. Closes gh-30328
1 parent 965a639 commit e246b47

File tree

11 files changed

+181
-41
lines changed

11 files changed

+181
-41
lines changed

spring-expression/src/main/java/org/springframework/expression/EvaluationContext.java

+39-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,19 +17,29 @@
1717
package org.springframework.expression;
1818

1919
import java.util.List;
20+
import java.util.function.Supplier;
2021

2122
import org.springframework.lang.Nullable;
2223

2324
/**
2425
* Expressions are executed in an evaluation context. It is in this context that
2526
* references are resolved when encountered during expression evaluation.
2627
*
27-
* <p>There is a default implementation of this EvaluationContext interface:
28-
* {@link org.springframework.expression.spel.support.StandardEvaluationContext}
29-
* which can be extended, rather than having to implement everything manually.
28+
* <p>There are two default implementations of this interface.
29+
* <ul>
30+
* <li>{@link org.springframework.expression.spel.support.SimpleEvaluationContext
31+
* SimpleEvaluationContext}: a simpler builder-style {@code EvaluationContext}
32+
* variant for data-binding purposes, which allows for opting into several SpEL
33+
* features as needed.</li>
34+
* <li>{@link org.springframework.expression.spel.support.StandardEvaluationContext
35+
* StandardEvaluationContext}: a powerful and highly configurable {@code EvaluationContext}
36+
* implementation, which can be extended, rather than having to implement everything
37+
* manually.</li>
38+
* </ul>
3039
*
3140
* @author Andy Clement
3241
* @author Juergen Hoeller
42+
* @author Sam Brannen
3343
* @since 3.0
3444
*/
3545
public interface EvaluationContext {
@@ -85,15 +95,38 @@ public interface EvaluationContext {
8595
OperatorOverloader getOperatorOverloader();
8696

8797
/**
88-
* Set a named variable within this evaluation context to a specified value.
98+
* Assign the value created by the specified {@link Supplier} to a named variable
99+
* within this evaluation context.
100+
* <p>In contrast to {@link #setVariable(String, Object)}, this method should only
101+
* be invoked to support the assignment operator ({@code =}) within an expression.
102+
* <p>By default, this method delegates to {@code setVariable(String, Object)},
103+
* providing the value created by the {@code valueSupplier}. Concrete implementations
104+
* may override this <em>default</em> method to provide different semantics.
105+
* @param name the name of the variable to assign
106+
* @param valueSupplier the supplier of the value to be assigned to the variable
107+
* @return a {@link TypedValue} wrapping the assigned value
108+
* @since 5.2.24
109+
*/
110+
default TypedValue assignVariable(String name, Supplier<TypedValue> valueSupplier) {
111+
TypedValue typedValue = valueSupplier.get();
112+
setVariable(name, typedValue.getValue());
113+
return typedValue;
114+
}
115+
116+
/**
117+
* Set a named variable in this evaluation context to a specified value.
118+
* <p>In contrast to {@link #assignVariable(String, Supplier)}, this method
119+
* should only be invoked programmatically when interacting directly with the
120+
* {@code EvaluationContext} &mdash; for example, to provide initial
121+
* configuration for the context.
89122
* @param name the name of the variable to set
90123
* @param value the value to be placed in the variable
91124
*/
92125
void setVariable(String name, @Nullable Object value);
93126

94127
/**
95128
* Look up a named variable within this evaluation context.
96-
* @param name variable to lookup
129+
* @param name the name of the variable to look up
97130
* @return the value of the variable, or {@code null} if not found
98131
*/
99132
@Nullable

spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java

+29-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
import java.util.List;
2424
import java.util.Map;
2525
import java.util.NoSuchElementException;
26+
import java.util.function.Supplier;
2627

2728
import org.springframework.core.convert.TypeDescriptor;
2829
import org.springframework.expression.EvaluationContext;
@@ -38,18 +39,19 @@
3839
import org.springframework.util.CollectionUtils;
3940

4041
/**
41-
* An ExpressionState is for maintaining per-expression-evaluation state, any changes to
42-
* it are not seen by other expressions but it gives a place to hold local variables and
42+
* ExpressionState is for maintaining per-expression-evaluation state: any changes to
43+
* it are not seen by other expressions, but it gives a place to hold local variables and
4344
* for component expressions in a compound expression to communicate state. This is in
4445
* contrast to the EvaluationContext, which is shared amongst expression evaluations, and
4546
* any changes to it will be seen by other expressions or any code that chooses to ask
4647
* questions of the context.
4748
*
48-
* <p>It also acts as a place for to define common utility routines that the various AST
49+
* <p>It also acts as a place to define common utility routines that the various AST
4950
* nodes might need.
5051
*
5152
* @author Andy Clement
5253
* @author Juergen Hoeller
54+
* @author Sam Brannen
5355
* @since 3.0
5456
*/
5557
public class ExpressionState {
@@ -138,6 +140,29 @@ public TypedValue getScopeRootContextObject() {
138140
return this.scopeRootObjects.element();
139141
}
140142

143+
/**
144+
* Assign the value created by the specified {@link Supplier} to a named variable
145+
* within the evaluation context.
146+
* <p>In contrast to {@link #setVariable(String, Object)}, this method should
147+
* only be invoked to support assignment within an expression.
148+
* @param name the name of the variable to assign
149+
* @param valueSupplier the supplier of the value to be assigned to the variable
150+
* @return a {@link TypedValue} wrapping the assigned value
151+
* @since 5.2.24
152+
* @see EvaluationContext#assignVariable(String, Supplier)
153+
*/
154+
public TypedValue assignVariable(String name, Supplier<TypedValue> valueSupplier) {
155+
return this.relatedContext.assignVariable(name, valueSupplier);
156+
}
157+
158+
/**
159+
* Set a named variable in the evaluation context to a specified value.
160+
* <p>In contrast to {@link #assignVariable(String, Supplier)}, this method
161+
* should only be invoked programmatically.
162+
* @param name the name of the variable to set
163+
* @param value the value to be placed in the variable
164+
* @see EvaluationContext#setVariable(String, Object)
165+
*/
141166
public void setVariable(String name, @Nullable Object value) {
142167
this.relatedContext.setVariable(name, value);
143168
}

spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,11 @@ public enum SpelMessage {
276276

277277
/** @since 5.2.24 */
278278
MAX_EXPRESSION_LENGTH_EXCEEDED(Kind.ERROR, 1079,
279-
"SpEL expression is too long, exceeding the threshold of ''{0}'' characters");
279+
"SpEL expression is too long, exceeding the threshold of ''{0}'' characters"),
280+
281+
/** @since 5.2.24 */
282+
VARIABLE_ASSIGNMENT_NOT_SUPPORTED(Kind.ERROR, 1080,
283+
"Assignment to variable ''{0}'' is not supported");
280284

281285

282286
private final Kind kind;

spring-expression/src/main/java/org/springframework/expression/spel/ast/Assign.java

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
2727
* <p>Example: 'someNumberProperty=42'
2828
*
2929
* @author Andy Clement
30+
* @author Sam Brannen
3031
* @since 3.0
3132
*/
3233
public class Assign extends SpelNodeImpl {
@@ -38,9 +39,7 @@ public Assign(int startPos, int endPos, SpelNodeImpl... operands) {
3839

3940
@Override
4041
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
41-
TypedValue newValue = this.children[1].getValueInternal(state);
42-
getChild(0).setValue(state, newValue.getValue());
43-
return newValue;
42+
return this.children[0].setValueInternal(state, () -> this.children[1].getValueInternal(state));
4443
}
4544

4645
@Override

spring-expression/src/main/java/org/springframework/expression/spel/ast/CompoundExpression.java

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,20 +17,21 @@
1717
package org.springframework.expression.spel.ast;
1818

1919
import java.util.StringJoiner;
20+
import java.util.function.Supplier;
2021

2122
import org.springframework.asm.MethodVisitor;
2223
import org.springframework.expression.EvaluationException;
2324
import org.springframework.expression.TypedValue;
2425
import org.springframework.expression.spel.CodeFlow;
2526
import org.springframework.expression.spel.ExpressionState;
2627
import org.springframework.expression.spel.SpelEvaluationException;
27-
import org.springframework.lang.Nullable;
2828

2929
/**
3030
* Represents a DOT separated expression sequence, such as
3131
* {@code 'property1.property2.methodOne()'}.
3232
*
3333
* @author Andy Clement
34+
* @author Sam Brannen
3435
* @since 3.0
3536
*/
3637
public class CompoundExpression extends SpelNodeImpl {
@@ -95,8 +96,12 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
9596
}
9697

9798
@Override
98-
public void setValue(ExpressionState state, @Nullable Object value) throws EvaluationException {
99-
getValueRef(state).setValue(value);
99+
public TypedValue setValueInternal(ExpressionState state, Supplier<TypedValue> valueSupplier)
100+
throws EvaluationException {
101+
102+
TypedValue typedValue = valueSupplier.get();
103+
getValueRef(state).setValue(typedValue.getValue());
104+
return typedValue;
100105
}
101106

102107
@Override

spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java

+10-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
2525
import java.util.List;
2626
import java.util.Map;
2727
import java.util.StringJoiner;
28+
import java.util.function.Supplier;
2829

2930
import org.springframework.asm.MethodVisitor;
3031
import org.springframework.core.convert.TypeDescriptor;
@@ -45,11 +46,12 @@
4546

4647
/**
4748
* An Indexer can index into some proceeding structure to access a particular piece of it.
48-
* Supported structures are: strings / collections (lists/sets) / arrays.
49+
* <p>Supported structures are: strings / collections (lists/sets) / arrays.
4950
*
5051
* @author Andy Clement
5152
* @author Phillip Webb
5253
* @author Stephane Nicoll
54+
* @author Sam Brannen
5355
* @since 3.0
5456
*/
5557
// TODO support multidimensional arrays
@@ -102,8 +104,12 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
102104
}
103105

104106
@Override
105-
public void setValue(ExpressionState state, @Nullable Object newValue) throws EvaluationException {
106-
getValueRef(state).setValue(newValue);
107+
public TypedValue setValueInternal(ExpressionState state, Supplier<TypedValue> valueSupplier)
108+
throws EvaluationException {
109+
110+
TypedValue typedValue = valueSupplier.get();
111+
getValueRef(state).setValue(typedValue.getValue());
112+
return typedValue;
107113
}
108114

109115
@Override

spring-expression/src/main/java/org/springframework/expression/spel/ast/PropertyOrFieldReference.java

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
2121
import java.util.HashMap;
2222
import java.util.List;
2323
import java.util.Map;
24+
import java.util.function.Supplier;
2425

2526
import org.springframework.asm.Label;
2627
import org.springframework.asm.MethodVisitor;
@@ -46,6 +47,7 @@
4647
* @author Andy Clement
4748
* @author Juergen Hoeller
4849
* @author Clark Duplichien
50+
* @author Sam Brannen
4951
* @since 3.0
5052
*/
5153
public class PropertyOrFieldReference extends SpelNodeImpl {
@@ -147,8 +149,12 @@ else if (Map.class == resultDescriptor.getType()) {
147149
}
148150

149151
@Override
150-
public void setValue(ExpressionState state, @Nullable Object newValue) throws EvaluationException {
151-
writeProperty(state.getActiveContextObject(), state.getEvaluationContext(), this.name, newValue);
152+
public TypedValue setValueInternal(ExpressionState state, Supplier<TypedValue> valueSupplier)
153+
throws EvaluationException {
154+
155+
TypedValue typedValue = valueSupplier.get();
156+
writeProperty(state.getActiveContextObject(), state.getEvaluationContext(), this.name, typedValue.getValue());
157+
return typedValue;
152158
}
153159

154160
@Override

spring-expression/src/main/java/org/springframework/expression/spel/ast/SpelNodeImpl.java

+28-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.lang.reflect.Constructor;
2020
import java.lang.reflect.Member;
2121
import java.lang.reflect.Method;
22+
import java.util.function.Supplier;
2223

2324
import org.springframework.asm.MethodVisitor;
2425
import org.springframework.asm.Opcodes;
@@ -40,6 +41,7 @@
4041
*
4142
* @author Andy Clement
4243
* @author Juergen Hoeller
44+
* @author Sam Brannen
4345
* @since 3.0
4446
*/
4547
public abstract class SpelNodeImpl implements SpelNode, Opcodes {
@@ -64,7 +66,7 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
6466
* <p>The descriptor is like the bytecode form but is slightly easier to work with.
6567
* It does not include the trailing semicolon (for non array reference types).
6668
* Some examples: Ljava/lang/String, I, [I
67-
*/
69+
*/
6870
@Nullable
6971
protected volatile String exitTypeDescriptor;
7072

@@ -83,8 +85,8 @@ public SpelNodeImpl(int startPos, int endPos, SpelNodeImpl... operands) {
8385

8486

8587
/**
86-
* Return {@code true} if the next child is one of the specified classes.
87-
*/
88+
* Return {@code true} if the next child is one of the specified classes.
89+
*/
8890
protected boolean nextChildIs(Class<?>... classes) {
8991
if (this.parent != null) {
9092
SpelNodeImpl[] peers = this.parent.children;
@@ -125,6 +127,28 @@ public boolean isWritable(ExpressionState expressionState) throws EvaluationExce
125127

126128
@Override
127129
public void setValue(ExpressionState expressionState, @Nullable Object newValue) throws EvaluationException {
130+
setValueInternal(expressionState, () -> new TypedValue(newValue));
131+
}
132+
133+
/**
134+
* Evaluate the expression to a node and then set the new value created by the
135+
* specified {@link Supplier} on that node.
136+
* <p>For example, if the expression evaluates to a property reference, then the
137+
* property will be set to the new value.
138+
* <p>Favor this method over {@link #setValue(ExpressionState, Object)} when
139+
* the value should be lazily computed.
140+
* <p>By default, this method throws a {@link SpelEvaluationException},
141+
* effectively disabling this feature. Subclasses may override this method to
142+
* provide an actual implementation.
143+
* @param expressionState the current expression state (includes the context)
144+
* @param valueSupplier a supplier of the new value
145+
* @throws EvaluationException if any problem occurs evaluating the expression or
146+
* setting the new value
147+
* @since 5.2.24
148+
*/
149+
public TypedValue setValueInternal(ExpressionState expressionState, Supplier<TypedValue> valueSupplier)
150+
throws EvaluationException {
151+
128152
throw new SpelEvaluationException(getStartPosition(), SpelMessage.SETVALUE_NOT_SUPPORTED, getClass());
129153
}
130154

0 commit comments

Comments
 (0)