GH-3114: Honor SpEL contract in ExpressionEvalMap

Fixes https://github.com/spring-projects/spring-integration/issues/3114

The contract of SpEL with its
`getValue(EvaluationContext context, @Nullable Object rootObject)` is
that we need to deal with provided `rootObject` even if it is `null`
and don't consult with `context.getRootObject()`

* Fix `ExpressionEvalMap` to have an internal `rootExplicitlySet`
to indicate that `root` explicitly provided by consumer, even if it is null.
According this flag call respective `Expression.getValue()`
* Add `@Nullable` to methods and their arguments into `ExpressionEvalMap`
& `FunctionExpression` to honor `Expression` contracts
* Populate an `HttpEntity` explicitly into `ExpressionEvalMap` from the
`HttpRequestHandlingEndpointSupport` and `WebFluxInboundEndpoint` for
full picture

* Fix JavaDocs indentations in the `ExpressionEvalMap`
This commit is contained in:
Artem Bilan
2019-11-22 16:29:23 -05:00
committed by Gary Russell
parent ee7be04c15
commit 516ecbcf8b
5 changed files with 76 additions and 35 deletions

View File

@@ -47,13 +47,13 @@ import org.springframework.util.Assert;
* <pre class="code">
* {@code
*ExpressionEvalMap evalMap = ExpressionEvalMap
* .from(expressions)
* .usingCallback(new EvaluationCallback() {
* Object evaluate(Expression expression) {
* // return some expression evaluation
* }
* })
* .build();
* .from(expressions)
* .usingCallback(new EvaluationCallback() {
* Object evaluate(Expression expression) {
* // return some expression evaluation
* }
* })
* .build();
*}
* </pre>
* <p>
@@ -84,6 +84,7 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
* from {@link #original} and returns the result of evaluation using {@link #evaluationCallback}.
*/
@Override
@Nullable
public Object get(Object key) {
Object value = this.original.get(key);
if (value != null) {
@@ -106,9 +107,9 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
@Override
public Set<Map.Entry<String, Object>> entrySet() {
return this.original.entrySet()
return this.original.keySet()
.stream()
.map(e -> new SimpleImmutableEntry<>(e.getKey(), get(e.getKey())))
.map((key) -> new SimpleImmutableEntry<>(key, get(key)))
.collect(Collectors.toSet());
}
@@ -206,22 +207,35 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
*/
public static class ComponentsEvaluationCallback implements EvaluationCallback {
@Nullable
private final EvaluationContext context;
@Nullable
private final Object root;
private final boolean rootExplicitlySet;
@Nullable
private final Class<?> returnType;
public ComponentsEvaluationCallback(EvaluationContext context, Object root, Class<?> returnType) {
public ComponentsEvaluationCallback(@Nullable EvaluationContext context, @Nullable Object root,
boolean rootExplicitlySet, @Nullable Class<?> returnType) {
this.context = context;
this.root = root;
this.rootExplicitlySet = rootExplicitlySet;
this.returnType = returnType;
}
@Override
public Object evaluate(Expression expression) {
if (this.context != null) {
return expression.getValue(this.context, this.root, this.returnType);
if (this.rootExplicitlySet) {
return expression.getValue(this.context, this.root, this.returnType);
}
else {
return expression.getValue(this.context, this.returnType);
}
}
return expression.getValue(this.root, this.returnType);
}
@@ -238,10 +252,15 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
private EvaluationCallback evaluationCallback;
@Nullable
private EvaluationContext context;
@Nullable
private Object root;
private boolean rootExplicitlySet;
@Nullable
private Class<?> returnType;
private final ExpressionEvalMapComponentsBuilder evalMapComponentsBuilder =
@@ -267,8 +286,9 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
return this.evalMapComponentsBuilder;
}
public ExpressionEvalMapComponentsBuilder withRoot(Object root) {
public ExpressionEvalMapComponentsBuilder withRoot(@Nullable Object root) {
this.root = root;
this.rootExplicitlySet = true;
return this.evalMapComponentsBuilder;
}
@@ -295,7 +315,8 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
else {
return new ExpressionEvalMap(ExpressionEvalMapBuilder.this.expressions,
new ComponentsEvaluationCallback(ExpressionEvalMapBuilder.this.context,
ExpressionEvalMapBuilder.this.root, ExpressionEvalMapBuilder.this.returnType));
ExpressionEvalMapBuilder.this.root, ExpressionEvalMapBuilder.this.rootExplicitlySet,
ExpressionEvalMapBuilder.this.returnType));
}
}
@@ -315,7 +336,7 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
}
@Override
public ExpressionEvalMapComponentsBuilder withRoot(Object root) {
public ExpressionEvalMapComponentsBuilder withRoot(@Nullable Object root) {
return ExpressionEvalMapBuilder.this.withRoot(root);
}
@@ -340,7 +361,7 @@ public final class ExpressionEvalMap extends AbstractMap<String, Object> {
ExpressionEvalMapComponentsBuilder usingEvaluationContext(EvaluationContext context);
ExpressionEvalMapComponentsBuilder withRoot(Object root);
ExpressionEvalMapComponentsBuilder withRoot(@Nullable Object root);
ExpressionEvalMapComponentsBuilder withReturnType(Class<?> returnType);

View File

@@ -25,6 +25,7 @@ import org.springframework.expression.Expression;
import org.springframework.expression.TypedValue;
import org.springframework.expression.common.ExpressionUtils;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -47,6 +48,7 @@ import org.springframework.util.Assert;
*
* @author Artem Bilan
* @author Gary Russell
*
* @since 5.0
*/
public class FunctionExpression<S> implements Expression {
@@ -65,46 +67,58 @@ public class FunctionExpression<S> implements Expression {
}
@Override
@Nullable
public Object getValue() throws EvaluationException {
return this.function.apply(null);
}
@Override
@Nullable
@SuppressWarnings("unchecked")
public Object getValue(Object rootObject) throws EvaluationException {
public Object getValue(@Nullable Object rootObject) throws EvaluationException {
return this.function.apply((S) rootObject);
}
@Override
public <T> T getValue(Class<T> desiredResultType) throws EvaluationException {
@Nullable
public <T> T getValue(@Nullable Class<T> desiredResultType) throws EvaluationException {
return getValue(this.defaultContext, desiredResultType);
}
@Override
public <T> T getValue(Object rootObject, Class<T> desiredResultType) throws EvaluationException {
@Nullable
public <T> T getValue(@Nullable Object rootObject, @Nullable Class<T> desiredResultType)
throws EvaluationException {
return getValue(this.defaultContext, rootObject, desiredResultType);
}
@Override
@Nullable
public Object getValue(EvaluationContext context) throws EvaluationException {
Object root = context.getRootObject().getValue();
return root == null ? getValue() : getValue(root);
return getValue(context.getRootObject().getValue());
}
@Override
public Object getValue(EvaluationContext context, Object rootObject) throws EvaluationException {
@Nullable
public Object getValue(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException {
return getValue(rootObject);
}
@Override
public <T> T getValue(EvaluationContext context, Class<T> desiredResultType) throws EvaluationException {
@Nullable
public <T> T getValue(EvaluationContext context, @Nullable Class<T> desiredResultType) throws EvaluationException {
return ExpressionUtils.convertTypedValue(context, new TypedValue(getValue(context)), desiredResultType);
}
@Override
public <T> T getValue(EvaluationContext context, Object rootObject, Class<T> desiredResultType)
@Nullable
public <T> T getValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Class<T> desiredResultType)
throws EvaluationException {
return ExpressionUtils.convertTypedValue(context, new TypedValue(getValue(rootObject)), desiredResultType);
return ExpressionUtils.convertTypedValue(context,
new TypedValue(getValue(context, rootObject)),
desiredResultType);
}
@Override
@@ -113,7 +127,7 @@ public class FunctionExpression<S> implements Expression {
}
@Override
public Class<?> getValueType(Object rootObject) throws EvaluationException {
public Class<?> getValueType(@Nullable Object rootObject) throws EvaluationException {
throw this.readOnlyException;
}
@@ -123,7 +137,7 @@ public class FunctionExpression<S> implements Expression {
}
@Override
public Class<?> getValueType(EvaluationContext context, Object rootObject) throws EvaluationException {
public Class<?> getValueType(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException {
throw this.readOnlyException;
}
@@ -133,7 +147,7 @@ public class FunctionExpression<S> implements Expression {
}
@Override
public TypeDescriptor getValueTypeDescriptor(Object rootObject) throws EvaluationException {
public TypeDescriptor getValueTypeDescriptor(@Nullable Object rootObject) throws EvaluationException {
throw this.readOnlyException;
}
@@ -143,23 +157,25 @@ public class FunctionExpression<S> implements Expression {
}
@Override
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, Object rootObject)
public TypeDescriptor getValueTypeDescriptor(EvaluationContext context, @Nullable Object rootObject)
throws EvaluationException {
throw this.readOnlyException;
}
@Override
public void setValue(EvaluationContext context, Object value) throws EvaluationException {
public void setValue(EvaluationContext context, @Nullable Object value) throws EvaluationException {
throw this.readOnlyException;
}
@Override
public void setValue(Object rootObject, Object value) throws EvaluationException {
public void setValue(@Nullable Object rootObject, @Nullable Object value) throws EvaluationException {
throw this.readOnlyException;
}
@Override
public void setValue(EvaluationContext context, Object rootObject, Object value) throws EvaluationException {
public void setValue(EvaluationContext context, @Nullable Object rootObject, @Nullable Object value)
throws EvaluationException {
throw this.readOnlyException;
}
@@ -169,12 +185,12 @@ public class FunctionExpression<S> implements Expression {
}
@Override
public boolean isWritable(EvaluationContext context, Object rootObject) throws EvaluationException {
public boolean isWritable(EvaluationContext context, @Nullable Object rootObject) throws EvaluationException {
return false;
}
@Override
public boolean isWritable(Object rootObject) throws EvaluationException {
public boolean isWritable(@Nullable Object rootObject) throws EvaluationException {
return false;
}

View File

@@ -276,6 +276,7 @@ public abstract class HttpRequestHandlingEndpointSupport extends BaseHttpInbound
headers.putAll(
ExpressionEvalMap.from(getHeaderExpressions())
.usingEvaluationContext(evaluationContext)
.withRoot(httpEntity)
.build());
}

View File

@@ -181,8 +181,9 @@ public class HttpDslTests {
Message<?> result = this.multiPartFilesChannel.receive(10_000);
assertThat(result).isNotNull();
assertThat(result.getHeaders()).containsEntry("contentLength", -1L);
assertThat(result)
.isNotNull()
.extracting(Message::getPayload)
.satisfies((payload) ->
assertThat((Map<String, ?>) payload)
@@ -321,7 +322,8 @@ public class HttpDslTests {
@Bean
public IntegrationFlow multiPartFilesFlow() {
return IntegrationFlows
.from(Http.inboundChannelAdapter("/multiPartFiles"))
.from(Http.inboundChannelAdapter("/multiPartFiles")
.headerFunction("contentLength", (entity) -> entity.getHeaders().getContentLength()))
.channel((c) -> c.queue("multiPartFilesChannel"))
.get();
}

View File

@@ -268,6 +268,7 @@ public class WebFluxInboundEndpoint extends BaseHttpInboundEndpoint implements W
headers.putAll(
ExpressionEvalMap.from(getHeaderExpressions())
.usingEvaluationContext(evaluationContext)
.withRoot(httpEntity)
.build());
}