DATAMONGO-2418 - Obtain EvaluationContextExtensions lazily when parsing Bson queries.

An eager evaluation of the context extension can lead to errors when e.g. the security context was not present.

Original pull request: #814.
This commit is contained in:
Christoph Strobl
2019-11-19 13:40:04 +01:00
committed by Mark Paluch
parent 277b7a1c7c
commit 60112b4d14
8 changed files with 83 additions and 12 deletions

View File

@@ -113,7 +113,7 @@ class AggregationUtils {
SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
ParameterBindingContext bindingContext = new ParameterBindingContext((accessor::getBindableValue), expressionParser,
evaluationContextProvider.getEvaluationContext(method.getParameters(), accessor.getValues()));
() -> evaluationContextProvider.getEvaluationContext(method.getParameters(), accessor.getValues()));
List<AggregationOperation> target = new ArrayList<>(method.getAnnotatedAggregation().length);
for (String source : method.getAnnotatedAggregation()) {

View File

@@ -73,7 +73,7 @@ class CollationUtils {
if (StringUtils.trimLeadingWhitespace(collationExpression).startsWith("{")) {
ParameterBindingContext bindingContext = new ParameterBindingContext((accessor::getBindableValue),
expressionParser, evaluationContextProvider.getEvaluationContext(parameters, accessor.getValues()));
expressionParser, () -> evaluationContextProvider.getEvaluationContext(parameters, accessor.getValues()));
return Collation.from(CODEC.decode(collationExpression, bindingContext));
}

View File

@@ -117,7 +117,7 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
protected Query createQuery(ConvertingParameterAccessor accessor) {
ParameterBindingContext bindingContext = new ParameterBindingContext((accessor::getBindableValue), expressionParser,
evaluationContextProvider.getEvaluationContext(getQueryMethod().getParameters(), accessor.getValues()));
() -> evaluationContextProvider.getEvaluationContext(getQueryMethod().getParameters(), accessor.getValues()));
Document queryObject = CODEC.decode(this.query, bindingContext);
Document fieldsObject = CODEC.decode(this.fieldSpec, bindingContext);

View File

@@ -116,7 +116,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
protected Query createQuery(ConvertingParameterAccessor accessor) {
ParameterBindingContext bindingContext = new ParameterBindingContext((accessor::getBindableValue), expressionParser,
evaluationContextProvider.getEvaluationContext(getQueryMethod().getParameters(), accessor.getValues()));
() -> evaluationContextProvider.getEvaluationContext(getQueryMethod().getParameters(), accessor.getValues()));
Document queryObject = CODEC.decode(this.query, bindingContext);
Document fieldsObject = CODEC.decode(this.fieldSpec, bindingContext);

View File

@@ -15,28 +15,54 @@
*/
package org.springframework.data.mongodb.util.json;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.function.Supplier;
import org.springframework.data.util.Lazy;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
/**
* Reusable context for binding parameters to an placeholder or a SpEL expression within a JSON structure. <br />
* Reusable context for binding parameters to a placeholder or a SpEL expression within a JSON structure. <br />
* To be used along with {@link ParameterBindingDocumentCodec#decode(String, ParameterBindingContext)}.
*
* @author Christoph Strobl
* @since 2.2
*/
@RequiredArgsConstructor
@Getter
public class ParameterBindingContext {
private final ValueProvider valueProvider;
private final SpelExpressionParser expressionParser;
private final EvaluationContext evaluationContext;
private final Lazy<EvaluationContext> evaluationContext;
/**
* @param valueProvider
* @param expressionParser
* @param evaluationContext
* @deprecated since 2.2.3 - Please use
* {@link #ParameterBindingContext(ValueProvider, SpelExpressionParser, Supplier)} instead.
*/
@Deprecated
public ParameterBindingContext(ValueProvider valueProvider, SpelExpressionParser expressionParser,
EvaluationContext evaluationContext) {
this(valueProvider, expressionParser, () -> evaluationContext);
}
/**
* @param valueProvider
* @param expressionParser
* @param evaluationContext a {@link Supplier} for {@link Lazy} context retrieval.
* @since 2.2.3
*/
public ParameterBindingContext(ValueProvider valueProvider, SpelExpressionParser expressionParser,
Supplier<EvaluationContext> evaluationContext) {
this.valueProvider = valueProvider;
this.expressionParser = expressionParser;
this.evaluationContext = evaluationContext instanceof Lazy ? (Lazy) evaluationContext : Lazy.of(evaluationContext);
}
@Nullable
public Object bindableValueForIndex(int index) {
@@ -47,6 +73,18 @@ public class ParameterBindingContext {
public Object evaluateExpression(String expressionString) {
Expression expression = expressionParser.parseExpression(expressionString);
return expression.getValue(this.evaluationContext, Object.class);
return expression.getValue(getEvaluationContext(), Object.class);
}
public EvaluationContext getEvaluationContext() {
return this.evaluationContext.get();
}
public SpelExpressionParser getExpressionParser() {
return expressionParser;
}
public ValueProvider getValueProvider() {
return valueProvider;
}
}

View File

@@ -162,7 +162,7 @@ public class ParameterBindingDocumentCodec implements CollectibleCodec<Document>
public Document decode(@Nullable String json, Object[] values) {
return decode(json, new ParameterBindingContext((index) -> values[index], new SpelExpressionParser(),
EvaluationContextProvider.DEFAULT.getEvaluationContext(values)));
() -> EvaluationContextProvider.DEFAULT.getEvaluationContext(values)));
}
public Document decode(@Nullable String json, ParameterBindingContext bindingContext) {

View File

@@ -26,6 +26,7 @@ import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -96,6 +97,15 @@ public class ParameterBindingJsonReader extends AbstractBsonReader {
public ParameterBindingJsonReader(String json, ValueProvider accessor, SpelExpressionParser spelExpressionParser,
EvaluationContext evaluationContext) {
this(json, accessor, spelExpressionParser, () -> evaluationContext);
}
/**
* @since 2.2.3
*/
public ParameterBindingJsonReader(String json, ValueProvider accessor, SpelExpressionParser spelExpressionParser,
Supplier<EvaluationContext> evaluationContext) {
this.scanner = new JsonScanner(json);
setContext(new Context(null, BsonContextType.TOP_LEVEL));

View File

@@ -26,6 +26,10 @@ import java.util.List;
import org.bson.Document;
import org.bson.codecs.DecoderContext;
import org.junit.Test;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
/**
* Unit tests for {@link ParameterBindingJsonReader}.
@@ -197,6 +201,25 @@ public class ParameterBindingJsonReaderUnitTests {
assertThat(target).isEqualTo(Document.parse("{ 'end_date' : { $gte : { $date : " + time + " } } } "));
}
@Test // DATAMONGO-2418
public void shouldNotAccessSpElEveluationContextWhenNoSpelPresentInBindableTarget() {
Object[] args = new Object[] { "value" };
EvaluationContext evaluationContext = new StandardEvaluationContext() {
@Override
public TypedValue getRootObject() {
throw new RuntimeException("o_O");
}
};
ParameterBindingJsonReader reader = new ParameterBindingJsonReader("{ 'name':'?0' }",
new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext));
Document target = new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build());
assertThat(target).isEqualTo(new Document("name", "value"));
}
private static Document parse(String json, Object... args) {
ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json, args);