Browse Source

Retain parameter type when binding parameters in annotated Query/Aggregation.

This commit ensures the parameter type is preserved when binding parameters used within the value of the Query or Aggregation annotation

Closes: #4089
3.3.x
Christoph Strobl 4 years ago
parent
commit
7c5ac764b3
No known key found for this signature in database
GPG Key ID: 8CC1AB53391458C8
  1. 73
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/EvaluationContextExpressionEvaluator.java
  2. 41
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java
  3. 50
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java
  4. 51
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java

73
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/EvaluationContextExpressionEvaluator.java

@ -0,0 +1,73 @@ @@ -0,0 +1,73 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.util.json;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.Nullable;
/**
* @author Christoph Strobl
* @since 3.3.5
*/
class EvaluationContextExpressionEvaluator implements SpELExpressionEvaluator {
ValueProvider valueProvider;
ExpressionParser expressionParser;
Supplier<EvaluationContext> evaluationContext;
public EvaluationContextExpressionEvaluator(ValueProvider valueProvider, ExpressionParser expressionParser,
Supplier<EvaluationContext> evaluationContext) {
this.valueProvider = valueProvider;
this.expressionParser = expressionParser;
this.evaluationContext = evaluationContext;
}
@Nullable
@Override
public <T> T evaluate(String expression) {
return evaluateExpression(expression, Collections.emptyMap());
}
public EvaluationContext getEvaluationContext(String expressionString) {
return evaluationContext != null ? evaluationContext.get() : new StandardEvaluationContext();
}
public SpelExpression getParsedExpression(String expressionString) {
return (SpelExpression) (expressionParser != null ? expressionParser : new SpelExpressionParser())
.parseExpression(expressionString);
}
public <T> T evaluateExpression(String expressionString, Map<String, Object> variables) {
SpelExpression expression = getParsedExpression(expressionString);
EvaluationContext ctx = getEvaluationContext(expressionString);
variables.entrySet().forEach(entry -> ctx.setVariable(entry.getKey(), entry.getValue()));
Object result = expression.getValue(ctx, Object.class);
return (T) result;
}
}

41
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java

@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
*/
package org.springframework.data.mongodb.util.json;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
@ -58,13 +59,7 @@ public class ParameterBindingContext { @@ -58,13 +59,7 @@ public class ParameterBindingContext {
*/
public ParameterBindingContext(ValueProvider valueProvider, ExpressionParser expressionParser,
Supplier<EvaluationContext> evaluationContext) {
this(valueProvider, new SpELExpressionEvaluator() {
@Override
public <T> T evaluate(String expressionString) {
return (T) expressionParser.parseExpression(expressionString).getValue(evaluationContext.get(), Object.class);
}
});
this(valueProvider, new EvaluationContextExpressionEvaluator(valueProvider, expressionParser, evaluationContext));
}
/**
@ -87,20 +82,20 @@ public class ParameterBindingContext { @@ -87,20 +82,20 @@ public class ParameterBindingContext {
* @return
* @since 3.1
*/
public static ParameterBindingContext forExpressions(ValueProvider valueProvider,
ExpressionParser expressionParser, Function<ExpressionDependencies, EvaluationContext> contextFunction) {
public static ParameterBindingContext forExpressions(ValueProvider valueProvider, ExpressionParser expressionParser,
Function<ExpressionDependencies, EvaluationContext> contextFunction) {
return new ParameterBindingContext(valueProvider, new SpELExpressionEvaluator() {
@Override
public <T> T evaluate(String expressionString) {
return new ParameterBindingContext(valueProvider,
new EvaluationContextExpressionEvaluator(valueProvider, expressionParser, null) {
Expression expression = expressionParser.parseExpression(expressionString);
ExpressionDependencies dependencies = ExpressionDependencies.discover(expression);
EvaluationContext evaluationContext = contextFunction.apply(dependencies);
@Override
public EvaluationContext getEvaluationContext(String expressionString) {
return (T) expression.getValue(evaluationContext, Object.class);
}
});
Expression expression = getParsedExpression(expressionString);
ExpressionDependencies dependencies = ExpressionDependencies.discover(expression);
return contextFunction.apply(dependencies);
}
});
}
@Nullable
@ -113,6 +108,16 @@ public class ParameterBindingContext { @@ -113,6 +108,16 @@ public class ParameterBindingContext {
return expressionEvaluator.evaluate(expressionString);
}
@Nullable
public Object evaluateExpression(String expressionString, Map<String, Object> variables) {
if (expressionEvaluator instanceof EvaluationContextExpressionEvaluator) {
return ((EvaluationContextExpressionEvaluator) expressionEvaluator).evaluateExpression(expressionString,
variables);
}
return expressionEvaluator.evaluate(expressionString);
}
public ValueProvider getValueProvider() {
return valueProvider;
}

50
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java

@ -20,8 +20,12 @@ import static java.lang.String.*; @@ -20,8 +20,12 @@ import static java.lang.String.*;
import java.text.DateFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
@ -64,6 +68,7 @@ public class ParameterBindingJsonReader extends AbstractBsonReader { @@ -64,6 +68,7 @@ public class ParameterBindingJsonReader extends AbstractBsonReader {
private static final Pattern PARAMETER_ONLY_BINDING_PATTERN = Pattern.compile("^\\?(\\d+)$");
private static final Pattern PARAMETER_BINDING_PATTERN = Pattern.compile("\\?(\\d+)");
private static final Pattern EXPRESSION_BINDING_PATTERN = Pattern.compile("[\\?:]#\\{.*\\}");
private static final Pattern SPEL_PARAMETER_BINDING_PATTERN = Pattern.compile("('\\?(\\d+)'|\\?(\\d+))");
private final ParameterBindingContext bindingContext;
@ -379,14 +384,24 @@ public class ParameterBindingJsonReader extends AbstractBsonReader { @@ -379,14 +384,24 @@ public class ParameterBindingJsonReader extends AbstractBsonReader {
String binding = regexMatcher.group();
String expression = binding.substring(3, binding.length() - 1);
Matcher inSpelMatcher = PARAMETER_BINDING_PATTERN.matcher(expression);
Matcher inSpelMatcher = SPEL_PARAMETER_BINDING_PATTERN.matcher(expression); // ?0 '?0'
Map<String, Object> innerSpelVariables = new HashMap<>();
while (inSpelMatcher.find()) {
int index = computeParameterIndex(inSpelMatcher.group());
expression = expression.replace(inSpelMatcher.group(), getBindableValueForIndex(index).toString());
String group = inSpelMatcher.group();
int index = computeParameterIndex(group);
Object value = getBindableValueForIndex(index);
String varName = "__QVar" + innerSpelVariables.size();
expression = expression.replace(group, "#" + varName);
if(group.startsWith("'")) { // retain the string semantic
innerSpelVariables.put(varName, nullSafeToString(value));
} else {
innerSpelVariables.put(varName, value);
}
}
Object value = evaluateExpression(expression);
Object value = evaluateExpression(expression, innerSpelVariables);
bindableValue.setValue(value);
bindableValue.setType(bsonTypeForValue(value));
return bindableValue;
@ -415,14 +430,24 @@ public class ParameterBindingJsonReader extends AbstractBsonReader { @@ -415,14 +430,24 @@ public class ParameterBindingJsonReader extends AbstractBsonReader {
String binding = regexMatcher.group();
String expression = binding.substring(3, binding.length() - 1);
Matcher inSpelMatcher = PARAMETER_BINDING_PATTERN.matcher(expression);
Matcher inSpelMatcher = SPEL_PARAMETER_BINDING_PATTERN.matcher(expression);
Map<String, Object> innerSpelVariables = new HashMap<>();
while (inSpelMatcher.find()) {
int index = computeParameterIndex(inSpelMatcher.group());
expression = expression.replace(inSpelMatcher.group(), getBindableValueForIndex(index).toString());
String group = inSpelMatcher.group();
int index = computeParameterIndex(group);
Object value = getBindableValueForIndex(index);
String varName = "__QVar" + innerSpelVariables.size();
expression = expression.replace(group, "#" + varName);
if(group.startsWith("'")) { // retain the string semantic
innerSpelVariables.put(varName, nullSafeToString(value));
} else {
innerSpelVariables.put(varName, value);
}
}
computedValue = computedValue.replace(binding, nullSafeToString(evaluateExpression(expression)));
computedValue = computedValue.replace(binding, nullSafeToString(evaluateExpression(expression, innerSpelVariables)));
bindableValue.setValue(computedValue);
bindableValue.setType(BsonType.STRING);
@ -459,7 +484,7 @@ public class ParameterBindingJsonReader extends AbstractBsonReader { @@ -459,7 +484,7 @@ public class ParameterBindingJsonReader extends AbstractBsonReader {
}
private static int computeParameterIndex(String parameter) {
return NumberUtils.parseNumber(parameter.replace("?", ""), Integer.class);
return NumberUtils.parseNumber(parameter.replace("?", "").replace("'", ""), Integer.class);
}
private Object getBindableValueForIndex(int index) {
@ -511,7 +536,12 @@ public class ParameterBindingJsonReader extends AbstractBsonReader { @@ -511,7 +536,12 @@ public class ParameterBindingJsonReader extends AbstractBsonReader {
@Nullable
private Object evaluateExpression(String expressionString) {
return bindingContext.evaluateExpression(expressionString);
return bindingContext.evaluateExpression(expressionString, Collections.emptyMap());
}
@Nullable
private Object evaluateExpression(String expressionString, Map<String,Object> variables) {
return bindingContext.evaluateExpression(expressionString, variables);
}
// Spring Data Customization END

51
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java

@ -25,6 +25,7 @@ import java.util.Arrays; @@ -25,6 +25,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import org.bson.Document;
import org.bson.codecs.DecoderContext;
@ -32,6 +33,7 @@ import org.junit.jupiter.api.Test; @@ -32,6 +33,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.data.spel.EvaluationContextProvider;
import org.springframework.data.spel.ExpressionDependencies;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ParseException;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@ -390,6 +392,55 @@ class ParameterBindingJsonReaderUnitTests { @@ -390,6 +392,55 @@ class ParameterBindingJsonReaderUnitTests {
assertThat(target).isEqualTo(new Document("parent", null));
}
@Test // GH-4089
void retainsSpelArgumentTypeViaArgumentIndex() {
String source = "new java.lang.Object()";
Document target = parse("{ arg0 : ?#{[0]} }", source);
assertThat(target.get("arg0")).isEqualTo(source);
}
@Test // GH-4089
void retainsSpelArgumentTypeViaParameterPlaceholder() {
String source = "new java.lang.Object()";
Document target = parse("{ arg0 : :#{?0} }", source);
assertThat(target.get("arg0")).isEqualTo(source);
}
@Test // GH-4089
void enforcesStringSpelArgumentTypeViaParameterPlaceholderWhenQuoted() {
Integer source = 10;
Document target = parse("{ arg0 : :#{'?0'} }", source);
assertThat(target.get("arg0")).isEqualTo("10");
}
@Test // GH-4089
void enforcesSpelArgumentTypeViaParameterPlaceholderWhenQuoted() {
String source = "new java.lang.Object()";
Document target = parse("{ arg0 : :#{'?0'} }", source);
assertThat(target.get("arg0")).isEqualTo(source);
}
@Test // GH-4089
void retainsSpelArgumentTypeViaParameterPlaceholderWhenValueContainsSingleQuotes() {
String source = "' + new java.lang.Object() + '";
Document target = parse("{ arg0 : :#{?0} }", source);
assertThat(target.get("arg0")).isEqualTo(source);
}
@Test // GH-4089
void retainsSpelArgumentTypeViaParameterPlaceholderWhenValueContainsDoubleQuotes() {
String source = "\\\" + new java.lang.Object() + \\\"";
Document target = parse("{ arg0 : :#{?0} }", source);
assertThat(target.get("arg0")).isEqualTo(source);
}
private static Document parse(String json, Object... args) {
ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json, args);

Loading…
Cancel
Save