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:
committed by
Gary Russell
parent
ee7be04c15
commit
516ecbcf8b
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -276,6 +276,7 @@ public abstract class HttpRequestHandlingEndpointSupport extends BaseHttpInbound
|
||||
headers.putAll(
|
||||
ExpressionEvalMap.from(getHeaderExpressions())
|
||||
.usingEvaluationContext(evaluationContext)
|
||||
.withRoot(httpEntity)
|
||||
.build());
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -268,6 +268,7 @@ public class WebFluxInboundEndpoint extends BaseHttpInboundEndpoint implements W
|
||||
headers.putAll(
|
||||
ExpressionEvalMap.from(getHeaderExpressions())
|
||||
.usingEvaluationContext(evaluationContext)
|
||||
.withRoot(httpEntity)
|
||||
.build());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user