Skip to content

DATAMONGO-2545 - Fix regression in String query SpEL parameter binding. #864

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.1.0-SNAPSHOT</version>
<version>3.1.0-DATAMONGO-2545-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.1.0-SNAPSHOT</version>
<version>3.1.0-DATAMONGO-2545-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.1.0-SNAPSHOT</version>
<version>3.1.0-DATAMONGO-2545-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.1.0-SNAPSHOT</version>
<version>3.1.0-DATAMONGO-2545-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.bson.Transformer;
import org.bson.codecs.*;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.json.JsonParseException;
import org.springframework.data.spel.EvaluationContextProvider;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
Expand Down Expand Up @@ -190,9 +191,26 @@ public Document decode(final BsonReader reader, final DecoderContext decoderCont

Document document = new Document();
reader.readStartDocument();
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
String fieldName = reader.readName();
document.put(fieldName, readValue(reader, decoderContext));

try {

while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
String fieldName = reader.readName();
Object value = readValue(reader, decoderContext);
document.put(fieldName, value);
}
} catch (JsonParseException e) {
try {

Object value = readValue(reader, decoderContext);
if (value instanceof Map) {
if (!((Map) value).isEmpty()) {
return new Document((Map) value);
}
}
} catch (Exception ex) {
throw e;
}
}

reader.readEndDocument();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.function.Supplier;
import java.util.regex.Matcher;
Expand Down Expand Up @@ -370,26 +371,33 @@ private BindableValue bindableValueFor(JsonToken token) {

if (token.getType().equals(JsonTokenType.UNQUOTED_STRING)) {

if (matcher.find()) {

int index = computeParameterIndex(matcher.group());
bindableValue.setValue(getBindableValueForIndex(index));
bindableValue.setType(bsonTypeForValue(getBindableValueForIndex(index)));
return bindableValue;
}

Matcher regexMatcher = EXPRESSION_BINDING_PATTERN.matcher(tokenValue);
if (regexMatcher.find()) {

String binding = regexMatcher.group();
String expression = binding.substring(3, binding.length() - 1);

Matcher inSpelMatcher = PARAMETER_BINDING_PATTERN.matcher(expression);
while (inSpelMatcher.find()) {

int index = computeParameterIndex(inSpelMatcher.group());
expression = expression.replace(inSpelMatcher.group(), getBindableValueForIndex(index).toString());
}

Object value = evaluateExpression(expression);
bindableValue.setValue(value);
bindableValue.setType(bsonTypeForValue(value));
return bindableValue;
}

if (matcher.find()) {

int index = computeParameterIndex(matcher.group());
bindableValue.setValue(getBindableValueForIndex(index));
bindableValue.setType(bsonTypeForValue(getBindableValueForIndex(index)));
return bindableValue;
}

bindableValue.setValue(tokenValue);
bindableValue.setType(BsonType.STRING);
return bindableValue;
Expand All @@ -398,26 +406,35 @@ private BindableValue bindableValueFor(JsonToken token) {

String computedValue = tokenValue;

boolean matched = false;
while (matcher.find()) {

matched = true;
String group = matcher.group();
int index = computeParameterIndex(group);
computedValue = computedValue.replace(group, nullSafeToString(getBindableValueForIndex(index)));
}

if (!matched) {
Matcher regexMatcher = EXPRESSION_BINDING_PATTERN.matcher(computedValue);

Matcher regexMatcher = EXPRESSION_BINDING_PATTERN.matcher(tokenValue);
while (regexMatcher.find()) {

while (regexMatcher.find()) {
String binding = regexMatcher.group();
String expression = binding.substring(3, binding.length() - 1);

String binding = regexMatcher.group();
String expression = binding.substring(3, binding.length() - 1);
Matcher inSpelMatcher = PARAMETER_BINDING_PATTERN.matcher(expression);
while (inSpelMatcher.find()) {

computedValue = computedValue.replace(binding, nullSafeToString(evaluateExpression(expression)));
int index = computeParameterIndex(inSpelMatcher.group());
expression = expression.replace(inSpelMatcher.group(), getBindableValueForIndex(index).toString());
}

computedValue = computedValue.replace(binding, nullSafeToString(evaluateExpression(expression)));

bindableValue.setValue(computedValue);
bindableValue.setType(BsonType.STRING);

return bindableValue;
}

while (matcher.find()) {

String group = matcher.group();
int index = computeParameterIndex(group);
computedValue = computedValue.replace(group, nullSafeToString(getBindableValueForIndex(index)));
}

bindableValue.setValue(computedValue);
Expand Down Expand Up @@ -479,6 +496,9 @@ private BsonType bsonTypeForValue(Object value) {
if (ClassUtils.isAssignable(Iterable.class, type)) {
return BsonType.ARRAY;
}
if (ClassUtils.isAssignable(Map.class, type)) {
return BsonType.DOCUMENT;
}

return BsonType.UNDEFINED;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

import static org.assertj.core.api.Assertions.*;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
Expand All @@ -26,6 +29,7 @@
import org.bson.Document;
import org.bson.codecs.DecoderContext;
import org.junit.jupiter.api.Test;
import org.springframework.data.spel.EvaluationContextProvider;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.SpelExpressionParser;
Expand Down Expand Up @@ -264,10 +268,92 @@ void bindQuotedMulitParameterInArray() {
assertThat(target).isEqualTo(Document.parse("{\"$and\": [{\"v1\": {\"$in\": [1]}}]}"));
}

@Test // DATAMONGO-2545
void shouldABindArgumentsViaIndexInSpelExpressions() {

Object[] args = new Object[] { "yess", "nooo" };
StandardEvaluationContext evaluationContext = (StandardEvaluationContext) EvaluationContextProvider.DEFAULT
.getEvaluationContext(args);

ParameterBindingJsonReader reader = new ParameterBindingJsonReader(
"{ 'isBatman' : ?#{ T(" + this.getClass().getName() + ").isBatman() ? [0] : [1] }}",
new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext));
Document target = new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build());

assertThat(target).isEqualTo(new Document("isBatman", "nooo"));
}

@Test // DATAMONGO-2545
void shouldAllowMethodArgumentPlaceholdersInSpelExpressions/*becuase this worked before*/() {

Object[] args = new Object[] { "yess", "nooo" };
StandardEvaluationContext evaluationContext = (StandardEvaluationContext) EvaluationContextProvider.DEFAULT
.getEvaluationContext(args);

ParameterBindingJsonReader reader = new ParameterBindingJsonReader(
"{ 'isBatman' : ?#{ T(" + this.getClass().getName() + ").isBatman() ? '?0' : '?1' }}",
new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext));
Document target = new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build());

assertThat(target).isEqualTo(new Document("isBatman", "nooo"));
}

@Test // DATAMONGO-2545
void shouldAllowMethodArgumentPlaceholdersInQuotedSpelExpressions/*becuase this worked before*/() {

Object[] args = new Object[] { "yess", "nooo" };
StandardEvaluationContext evaluationContext = (StandardEvaluationContext) EvaluationContextProvider.DEFAULT
.getEvaluationContext(args);

ParameterBindingJsonReader reader = new ParameterBindingJsonReader(
"{ 'isBatman' : \"?#{ T(" + this.getClass().getName() + ").isBatman() ? '?0' : '?1' }\" }",
new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext));
Document target = new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build());

assertThat(target).isEqualTo(new Document("isBatman", "nooo"));
}

@Test // DATAMONGO-2545
void evaluatesSpelExpressionDefiningEntireQuery() {

Object[] args = new Object[] {};
StandardEvaluationContext evaluationContext = (StandardEvaluationContext) EvaluationContextProvider.DEFAULT
.getEvaluationContext(args);
evaluationContext.setRootObject(new DummySecurityObject(new DummyWithId("wonderwoman")));

String json = "?#{ T(" + this.getClass().getName()
+ ").isBatman() ? {'_class': { '$eq' : 'region' }} : { '$and' : { {'_class': { '$eq' : 'region' } }, {'user.superviser': principal.id } } } }";

ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json,
new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext));
Document target = new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build());

assertThat(target)
.isEqualTo(new Document("$and", Arrays.asList(new Document("_class", new Document("$eq", "region")),
new Document("user.superviser", "wonderwoman"))));
}

private static Document parse(String json, Object... args) {

ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json, args);
return new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build());
}

// DATAMONGO-2545
public static boolean isBatman() {
return false;
}

@Data
@AllArgsConstructor
public static class DummySecurityObject {
DummyWithId principal;
}

@Data
@AllArgsConstructor
public static class DummyWithId {
String id;
}

}