Browse Source

Introduce withAssignmentDisabled() option for SimpleEvaluationContext

To support additional use cases, this commit introduces a
withAssignmentDisabled() method in the Builder for
SimpleEvaluationContext.

See gh-33319
Closes gh-33321

(cherry picked from commit e74406afd0)
6.0.x
Sam Brannen 1 year ago
parent
commit
79c7bfdbad
  1. 52
      spring-expression/src/main/java/org/springframework/expression/spel/support/SimpleEvaluationContext.java
  2. 53
      spring-expression/src/test/java/org/springframework/expression/spel/support/SimpleEvaluationContextTests.java

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

@ -64,8 +64,9 @@ import org.springframework.lang.Nullable; @@ -64,8 +64,9 @@ import org.springframework.lang.Nullable;
* read-only access to properties via {@link DataBindingPropertyAccessor}. Similarly,
* {@link SimpleEvaluationContext#forReadWriteDataBinding()} enables read and write access
* to properties. Alternatively, configure custom accessors via
* {@link SimpleEvaluationContext#forPropertyAccessors} and potentially activate method
* resolution and/or a type converter through the builder.
* {@link SimpleEvaluationContext#forPropertyAccessors}, potentially
* {@linkplain Builder#withAssignmentDisabled() disable assignment}, and optionally
* activate method resolution and/or a type converter through the builder.
*
* <p>Note that {@code SimpleEvaluationContext} is typically not configured
* with a default root object. Instead it is meant to be created once and
@ -234,9 +235,8 @@ public final class SimpleEvaluationContext implements EvaluationContext { @@ -234,9 +235,8 @@ public final class SimpleEvaluationContext implements EvaluationContext {
* ({@code ++}), and decrement ({@code --}) operators are disabled.
* @return {@code true} if assignment is enabled; {@code false} otherwise
* @since 5.3.38
* @see #forPropertyAccessors(PropertyAccessor...)
* @see #forReadOnlyDataBinding()
* @see #forReadWriteDataBinding()
* @see Builder#withAssignmentDisabled()
*/
@Override
public boolean isAssignmentEnabled() {
@ -245,15 +245,18 @@ public final class SimpleEvaluationContext implements EvaluationContext { @@ -245,15 +245,18 @@ public final class SimpleEvaluationContext implements EvaluationContext {
/**
* Create a {@code SimpleEvaluationContext} for the specified {@link PropertyAccessor}
* delegates: typically a custom {@code PropertyAccessor} specific to a use case
* (e.g. attribute resolution in a custom data structure), potentially combined with
* a {@link DataBindingPropertyAccessor} if property dereferences are needed as well.
* <p>Assignment is enabled within expressions evaluated by the context created via
* this factory method.
* delegates: typically a custom {@code PropertyAccessor} specific to a use case &mdash;
* for example, for attribute resolution in a custom data structure &mdash; potentially
* combined with a {@link DataBindingPropertyAccessor} if property dereferences are
* needed as well.
* <p>By default, assignment is enabled within expressions evaluated by the context
* created via this factory method; however, assignment can be disabled via
* {@link Builder#withAssignmentDisabled()}.
* @param accessors the accessor delegates to use
* @see DataBindingPropertyAccessor#forReadOnlyAccess()
* @see DataBindingPropertyAccessor#forReadWriteAccess()
* @see #isAssignmentEnabled()
* @see Builder#withAssignmentDisabled()
*/
public static Builder forPropertyAccessors(PropertyAccessor... accessors) {
for (PropertyAccessor accessor : accessors) {
@ -262,7 +265,7 @@ public final class SimpleEvaluationContext implements EvaluationContext { @@ -262,7 +265,7 @@ public final class SimpleEvaluationContext implements EvaluationContext {
"ReflectivePropertyAccessor. Consider using DataBindingPropertyAccessor or a custom subclass.");
}
}
return new Builder(true, accessors);
return new Builder(accessors);
}
/**
@ -273,22 +276,26 @@ public final class SimpleEvaluationContext implements EvaluationContext { @@ -273,22 +276,26 @@ public final class SimpleEvaluationContext implements EvaluationContext {
* @see DataBindingPropertyAccessor#forReadOnlyAccess()
* @see #forPropertyAccessors
* @see #isAssignmentEnabled()
* @see Builder#withAssignmentDisabled()
*/
public static Builder forReadOnlyDataBinding() {
return new Builder(false, DataBindingPropertyAccessor.forReadOnlyAccess());
return new Builder(DataBindingPropertyAccessor.forReadOnlyAccess()).withAssignmentDisabled();
}
/**
* Create a {@code SimpleEvaluationContext} for read-write access to
* public properties via {@link DataBindingPropertyAccessor}.
* <p>Assignment is enabled within expressions evaluated by the context created via
* this factory method.
* <p>By default, assignment is enabled within expressions evaluated by the context
* created via this factory method. Assignment can be disabled via
* {@link Builder#withAssignmentDisabled()}; however, it is preferable to use
* {@link #forReadOnlyDataBinding()} if you desire read-only access.
* @see DataBindingPropertyAccessor#forReadWriteAccess()
* @see #forPropertyAccessors
* @see #isAssignmentEnabled()
* @see Builder#withAssignmentDisabled()
*/
public static Builder forReadWriteDataBinding() {
return new Builder(true, DataBindingPropertyAccessor.forReadWriteAccess());
return new Builder(DataBindingPropertyAccessor.forReadWriteAccess());
}
@ -307,15 +314,24 @@ public final class SimpleEvaluationContext implements EvaluationContext { @@ -307,15 +314,24 @@ public final class SimpleEvaluationContext implements EvaluationContext {
@Nullable
private TypedValue rootObject;
private final boolean assignmentEnabled;
private boolean assignmentEnabled = true;
private Builder(boolean assignmentEnabled, PropertyAccessor... accessors) {
this.assignmentEnabled = assignmentEnabled;
private Builder(PropertyAccessor... accessors) {
this.accessors = Arrays.asList(accessors);
}
/**
* Disable assignment within expressions evaluated by this evaluation context.
* @since 5.3.38
* @see SimpleEvaluationContext#isAssignmentEnabled()
*/
public Builder withAssignmentDisabled() {
this.assignmentEnabled = false;
return this;
}
/**
* Register the specified {@link MethodResolver} delegates for
* a combination of property access and method resolution.
@ -347,7 +363,6 @@ public final class SimpleEvaluationContext implements EvaluationContext { @@ -347,7 +363,6 @@ public final class SimpleEvaluationContext implements EvaluationContext {
return this;
}
/**
* Register a custom {@link ConversionService}.
* <p>By default a {@link StandardTypeConverter} backed by a
@ -359,6 +374,7 @@ public final class SimpleEvaluationContext implements EvaluationContext { @@ -359,6 +374,7 @@ public final class SimpleEvaluationContext implements EvaluationContext {
this.typeConverter = new StandardTypeConverter(conversionService);
return this;
}
/**
* Register a custom {@link TypeConverter}.
* <p>By default a {@link StandardTypeConverter} backed by a

53
spring-expression/src/test/java/org/springframework/expression/spel/support/SimpleEvaluationContextTests.java

@ -187,6 +187,59 @@ class SimpleEvaluationContextTests { @@ -187,6 +187,59 @@ class SimpleEvaluationContextTests {
assertIncrementAndDecrementWritesForIndexedStructures(context);
}
@Test
void forPropertyAccessorsWithAssignmentDisabled() {
SimpleEvaluationContext context = SimpleEvaluationContext
.forPropertyAccessors(new CompilableMapAccessor(), DataBindingPropertyAccessor.forReadOnlyAccess())
.withAssignmentDisabled()
.build();
assertCommonReadOnlyModeBehavior(context);
// Map -- with key as property name supported by CompilableMapAccessor
Expression expression;
expression = parser.parseExpression("map.yellow");
// setValue() is supported even though assignment is not.
expression.setValue(context, model, "pineapple");
assertThat(expression.getValue(context, model, String.class)).isEqualTo("pineapple");
// WRITE -- via assignment operator
// Variable
assertAssignmentDisabled(context, "#myVar = 'rejected'");
// Property
assertAssignmentDisabled(context, "name = 'rejected'");
assertAssignmentDisabled(context, "map.yellow = 'rejected'");
assertIncrementDisabled(context, "count++");
assertIncrementDisabled(context, "++count");
assertDecrementDisabled(context, "count--");
assertDecrementDisabled(context, "--count");
// Array Index
assertAssignmentDisabled(context, "array[0] = 'rejected'");
assertIncrementDisabled(context, "numbers[0]++");
assertIncrementDisabled(context, "++numbers[0]");
assertDecrementDisabled(context, "numbers[0]--");
assertDecrementDisabled(context, "--numbers[0]");
// List Index
assertAssignmentDisabled(context, "list[0] = 'rejected'");
// Map Index -- key as String
assertAssignmentDisabled(context, "map['red'] = 'rejected'");
// Map Index -- key as pseudo property name
assertAssignmentDisabled(context, "map[yellow] = 'rejected'");
// String Index
assertAssignmentDisabled(context, "name[0] = 'rejected'");
// Object Index
assertAssignmentDisabled(context, "['name'] = 'rejected'");
}
private void assertReadWriteMode(SimpleEvaluationContext context) {
// Variables can always be set programmatically within an EvaluationContext.

Loading…
Cancel
Save