Skip to content

Commit a698f66

Browse files
committed
Merge branch '6.1.x'
# Conflicts: # spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java
2 parents 28668d7 + 0127de5 commit a698f66

File tree

8 files changed

+562
-26
lines changed

8 files changed

+562
-26
lines changed

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

+14
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,18 @@ default TypedValue assignVariable(String name, Supplier<TypedValue> valueSupplie
153153
@Nullable
154154
Object lookupVariable(String name);
155155

156+
/**
157+
* Determine if assignment is enabled within expressions evaluated by this evaluation
158+
* context.
159+
* <p>If this method returns {@code false}, the assignment ({@code =}), increment
160+
* ({@code ++}), and decrement ({@code --}) operators are disabled.
161+
* <p>By default, this method returns {@code true}. Concrete implementations may override
162+
* this <em>default</em> method to disable assignment.
163+
* @return {@code true} if assignment is enabled; {@code false} otherwise
164+
* @since 5.3.38
165+
*/
166+
default boolean isAssignmentEnabled() {
167+
return true;
168+
}
169+
156170
}

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 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,8 @@
1919
import org.springframework.expression.EvaluationException;
2020
import org.springframework.expression.TypedValue;
2121
import org.springframework.expression.spel.ExpressionState;
22+
import org.springframework.expression.spel.SpelEvaluationException;
23+
import org.springframework.expression.spel.SpelMessage;
2224

2325
/**
2426
* Represents assignment. An alternative to calling {@code setValue}
@@ -39,6 +41,9 @@ public Assign(int startPos, int endPos, SpelNodeImpl... operands) {
3941

4042
@Override
4143
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
44+
if (!state.getEvaluationContext().isAssignmentEnabled()) {
45+
throw new SpelEvaluationException(getStartPosition(), SpelMessage.NOT_ASSIGNABLE, toStringAST());
46+
}
4247
return this.children[0].setValueInternal(state, () -> this.children[1].getValueInternal(state));
4348
}
4449

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

+4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ public OpDec(int startPos, int endPos, boolean postfix, SpelNodeImpl... operands
5353

5454
@Override
5555
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
56+
if (!state.getEvaluationContext().isAssignmentEnabled()) {
57+
throw new SpelEvaluationException(getStartPosition(), SpelMessage.OPERAND_NOT_DECREMENTABLE, toStringAST());
58+
}
59+
5660
SpelNodeImpl operand = getLeftOperand();
5761

5862
// The operand is going to be read and then assigned to, we don't want to evaluate it twice.

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ public OpInc(int startPos, int endPos, boolean postfix, SpelNodeImpl... operands
5353

5454
@Override
5555
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
56+
if (!state.getEvaluationContext().isAssignmentEnabled()) {
57+
throw new SpelEvaluationException(getStartPosition(), SpelMessage.OPERAND_NOT_INCREMENTABLE, toStringAST());
58+
}
59+
5660
SpelNodeImpl operand = getLeftOperand();
5761
ValueRef valueRef = operand.getValueRef(state);
5862

@@ -106,7 +110,7 @@ else if (op1 instanceof Byte) {
106110
}
107111
}
108112

109-
// set the name value
113+
// set the new value
110114
try {
111115
valueRef.setValue(newValue.getValue());
112116
}

spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java

+52-20
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,25 @@
5151
* SpEL language syntax, e.g. excluding references to Java types, constructors,
5252
* and bean references.
5353
*
54-
* <p>When creating a {@code SimpleEvaluationContext} you need to choose the
55-
* level of support that you need for property access in SpEL expressions:
54+
* <p>When creating a {@code SimpleEvaluationContext} you need to choose the level of
55+
* support that you need for data binding in SpEL expressions:
5656
* <ul>
57-
* <li>A custom {@code PropertyAccessor} (typically not reflection-based),
58-
* potentially combined with a {@link DataBindingPropertyAccessor}</li>
59-
* <li>Data binding properties for read-only access</li>
60-
* <li>Data binding properties for read and write</li>
57+
* <li>Data binding for read-only access</li>
58+
* <li>Data binding for read and write access</li>
59+
* <li>A custom {@code PropertyAccessor} (typically not reflection-based), potentially
60+
* combined with a {@link DataBindingPropertyAccessor}</li>
6161
* </ul>
6262
*
63-
* <p>Conveniently, {@link SimpleEvaluationContext#forReadOnlyDataBinding()}
64-
* enables read access to properties via {@link DataBindingPropertyAccessor};
65-
* same for {@link SimpleEvaluationContext#forReadWriteDataBinding()} when
66-
* write access is needed as well. Alternatively, configure custom accessors
67-
* via {@link SimpleEvaluationContext#forPropertyAccessors}, and potentially
68-
* activate method resolution and/or a type converter through the builder.
63+
* <p>Conveniently, {@link SimpleEvaluationContext#forReadOnlyDataBinding()} enables
64+
* read-only access to properties via {@link DataBindingPropertyAccessor}. Similarly,
65+
* {@link SimpleEvaluationContext#forReadWriteDataBinding()} enables read and write access
66+
* to properties. Alternatively, configure custom accessors via
67+
* {@link SimpleEvaluationContext#forPropertyAccessors} and potentially activate method
68+
* resolution and/or a type converter through the builder.
6969
*
7070
* <p>Note that {@code SimpleEvaluationContext} is typically not configured
7171
* with a default root object. Instead it is meant to be created once and
72-
* used repeatedly through {@code getValue} calls on a pre-compiled
72+
* used repeatedly through {@code getValue} calls on a predefined
7373
* {@link org.springframework.expression.Expression} with both an
7474
* {@code EvaluationContext} and a root object as arguments:
7575
* {@link org.springframework.expression.Expression#getValue(EvaluationContext, Object)}.
@@ -89,9 +89,9 @@
8989
* @author Juergen Hoeller
9090
* @author Sam Brannen
9191
* @since 4.3.15
92-
* @see #forPropertyAccessors
9392
* @see #forReadOnlyDataBinding()
9493
* @see #forReadWriteDataBinding()
94+
* @see #forPropertyAccessors
9595
* @see StandardEvaluationContext
9696
* @see StandardTypeConverter
9797
* @see DataBindingPropertyAccessor
@@ -120,15 +120,19 @@ public final class SimpleEvaluationContext implements EvaluationContext {
120120

121121
private final Map<String, Object> variables = new HashMap<>();
122122

123+
private final boolean assignmentEnabled;
124+
123125

124126
private SimpleEvaluationContext(List<PropertyAccessor> propertyAccessors, List<IndexAccessor> indexAccessors,
125-
List<MethodResolver> resolvers, @Nullable TypeConverter converter, @Nullable TypedValue rootObject) {
127+
List<MethodResolver> resolvers, @Nullable TypeConverter converter, @Nullable TypedValue rootObject,
128+
boolean assignmentEnabled) {
126129

127130
this.propertyAccessors = propertyAccessors;
128131
this.indexAccessors = indexAccessors;
129132
this.methodResolvers = resolvers;
130133
this.typeConverter = (converter != null ? converter : new StandardTypeConverter());
131134
this.rootObject = (rootObject != null ? rootObject : TypedValue.NULL);
135+
this.assignmentEnabled = assignmentEnabled;
132136
}
133137

134138

@@ -257,15 +261,33 @@ public Object lookupVariable(String name) {
257261
return this.variables.get(name);
258262
}
259263

264+
/**
265+
* Determine if assignment is enabled within expressions evaluated by this evaluation
266+
* context.
267+
* <p>If this method returns {@code false}, the assignment ({@code =}), increment
268+
* ({@code ++}), and decrement ({@code --}) operators are disabled.
269+
* @return {@code true} if assignment is enabled; {@code false} otherwise
270+
* @since 5.3.38
271+
* @see #forPropertyAccessors(PropertyAccessor...)
272+
* @see #forReadOnlyDataBinding()
273+
* @see #forReadWriteDataBinding()
274+
*/
275+
@Override
276+
public boolean isAssignmentEnabled() {
277+
return this.assignmentEnabled;
278+
}
260279

261280
/**
262281
* Create a {@code SimpleEvaluationContext} for the specified {@link PropertyAccessor}
263282
* delegates: typically a custom {@code PropertyAccessor} specific to a use case
264283
* (e.g. attribute resolution in a custom data structure), potentially combined with
265284
* a {@link DataBindingPropertyAccessor} if property dereferences are needed as well.
285+
* <p>Assignment is enabled within expressions evaluated by the context created via
286+
* this factory method.
266287
* @param accessors the accessor delegates to use
267288
* @see DataBindingPropertyAccessor#forReadOnlyAccess()
268289
* @see DataBindingPropertyAccessor#forReadWriteAccess()
290+
* @see #isAssignmentEnabled()
269291
*/
270292
public static Builder forPropertyAccessors(PropertyAccessor... accessors) {
271293
for (PropertyAccessor accessor : accessors) {
@@ -274,27 +296,33 @@ public static Builder forPropertyAccessors(PropertyAccessor... accessors) {
274296
"ReflectivePropertyAccessor. Consider using DataBindingPropertyAccessor or a custom subclass.");
275297
}
276298
}
277-
return new Builder(accessors);
299+
return new Builder(true, accessors);
278300
}
279301

280302
/**
281303
* Create a {@code SimpleEvaluationContext} for read-only access to
282304
* public properties via {@link DataBindingPropertyAccessor}.
305+
* <p>Assignment is disabled within expressions evaluated by the context created via
306+
* this factory method.
283307
* @see DataBindingPropertyAccessor#forReadOnlyAccess()
284308
* @see #forPropertyAccessors
309+
* @see #isAssignmentEnabled()
285310
*/
286311
public static Builder forReadOnlyDataBinding() {
287-
return new Builder(DataBindingPropertyAccessor.forReadOnlyAccess());
312+
return new Builder(false, DataBindingPropertyAccessor.forReadOnlyAccess());
288313
}
289314

290315
/**
291316
* Create a {@code SimpleEvaluationContext} for read-write access to
292317
* public properties via {@link DataBindingPropertyAccessor}.
318+
* <p>Assignment is enabled within expressions evaluated by the context created via
319+
* this factory method.
293320
* @see DataBindingPropertyAccessor#forReadWriteAccess()
294321
* @see #forPropertyAccessors
322+
* @see #isAssignmentEnabled()
295323
*/
296324
public static Builder forReadWriteDataBinding() {
297-
return new Builder(DataBindingPropertyAccessor.forReadWriteAccess());
325+
return new Builder(true, DataBindingPropertyAccessor.forReadWriteAccess());
298326
}
299327

300328

@@ -315,8 +343,11 @@ public static final class Builder {
315343
@Nullable
316344
private TypedValue rootObject;
317345

346+
private final boolean assignmentEnabled;
318347

319-
private Builder(PropertyAccessor... accessors) {
348+
349+
private Builder(boolean assignmentEnabled, PropertyAccessor... accessors) {
350+
this.assignmentEnabled = assignmentEnabled;
320351
this.propertyAccessors = Arrays.asList(accessors);
321352
}
322353

@@ -410,8 +441,9 @@ public Builder withTypedRootObject(Object rootObject, TypeDescriptor typeDescrip
410441

411442
public SimpleEvaluationContext build() {
412443
return new SimpleEvaluationContext(this.propertyAccessors, this.indexAccessors,
413-
this.resolvers, this.typeConverter, this.rootObject);
444+
this.resolvers, this.typeConverter, this.rootObject, this.assignmentEnabled);
414445
}
446+
415447
}
416448

417449
}

spring-expression/src/test/java/org/springframework/expression/spel/CompilableMapAccessor.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
* @author Andy Clement
3333
* @since 4.1
3434
*/
35-
class CompilableMapAccessor implements CompilablePropertyAccessor {
35+
public class CompilableMapAccessor implements CompilablePropertyAccessor {
3636

3737
private final boolean allowWrite;
3838

spring-expression/src/test/java/org/springframework/expression/spel/PropertyAccessTests.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,11 @@ void propertyReadOnly() {
188188

189189
assertThatSpelEvaluationException()
190190
.isThrownBy(() -> parser.parseExpression("name='p3'").getValue(context, target))
191-
.extracting(SpelEvaluationException::getMessageCode).isEqualTo(SpelMessage.PROPERTY_OR_FIELD_NOT_WRITABLE);
191+
.extracting(SpelEvaluationException::getMessageCode).isEqualTo(SpelMessage.NOT_ASSIGNABLE);
192192

193193
assertThatSpelEvaluationException()
194194
.isThrownBy(() -> parser.parseExpression("['name']='p4'").getValue(context, target))
195-
.extracting(SpelEvaluationException::getMessageCode).isEqualTo(SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE);
195+
.extracting(SpelEvaluationException::getMessageCode).isEqualTo(SpelMessage.NOT_ASSIGNABLE);
196196
}
197197

198198
@Test
@@ -207,7 +207,7 @@ void propertyReadOnlyWithRecordStyle() {
207207

208208
assertThatSpelEvaluationException()
209209
.isThrownBy(() -> parser.parseExpression("name='p3'").getValue(context, target2))
210-
.extracting(SpelEvaluationException::getMessageCode).isEqualTo(SpelMessage.PROPERTY_OR_FIELD_NOT_WRITABLE);
210+
.extracting(SpelEvaluationException::getMessageCode).isEqualTo(SpelMessage.NOT_ASSIGNABLE);
211211
}
212212

213213
@Test

0 commit comments

Comments
 (0)