From 748b33f05ccc9b0afbe65afe6c796855563a458c Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 13 May 2020 08:07:30 +0200 Subject: [PATCH 1/3] DATAMONGO-2545 - Prepare issue branch. --- pom.xml | 2 +- spring-data-mongodb-benchmarks/pom.xml | 2 +- spring-data-mongodb-distribution/pom.xml | 2 +- spring-data-mongodb/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index f59b2c8bf4..21679c574a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 3.1.0-SNAPSHOT + 3.1.0-DATAMONGO-2545-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml index a85b081e26..26e0446da9 100644 --- a/spring-data-mongodb-benchmarks/pom.xml +++ b/spring-data-mongodb-benchmarks/pom.xml @@ -7,7 +7,7 @@ org.springframework.data spring-data-mongodb-parent - 3.1.0-SNAPSHOT + 3.1.0-DATAMONGO-2545-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index 33014c7c4f..ce47969464 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-mongodb-parent - 3.1.0-SNAPSHOT + 3.1.0-DATAMONGO-2545-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index c85d971ad8..2a424138b6 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 3.1.0-SNAPSHOT + 3.1.0-DATAMONGO-2545-SNAPSHOT ../pom.xml From dc3d729a2d17718221d9677547845a844ccbe0c5 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 13 May 2020 09:48:02 +0200 Subject: [PATCH 2/3] DATAMONGO-2545 - Fix regression in String query SpEL parameter binding. We reenabled parameter binding within SpEL using query parameter placeholders ?0, ?1,... instead of their array index [0],[1],... --- .../util/json/ParameterBindingJsonReader.java | 58 ++++++++++++------- .../ParameterBindingJsonReaderUnitTests.java | 51 ++++++++++++++++ 2 files changed, 88 insertions(+), 21 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java index 1402081516..bdc98d532f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java @@ -370,26 +370,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; @@ -398,26 +405,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); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java index c3257b5f71..6e6e14dbda 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java @@ -26,6 +26,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; @@ -264,10 +265,60 @@ 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")); + } + 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; + } + } From bda096ca0df61a98f03f8fad7980f051ba6cbb95 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 13 May 2020 11:30:16 +0200 Subject: [PATCH 3/3] DATAMONGO-2545 - Fix full Query Document binding resulting from SpEL. We reenabled annotated queries using a SpEL expression resulting in the actual query document. --- .../json/ParameterBindingDocumentCodec.java | 24 +++++++++++-- .../util/json/ParameterBindingJsonReader.java | 4 +++ .../ParameterBindingJsonReaderUnitTests.java | 35 +++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingDocumentCodec.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingDocumentCodec.java index baffcaf8cb..72e6467b91 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingDocumentCodec.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingDocumentCodec.java @@ -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; @@ -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(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java index bdc98d532f..77cd057475 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java @@ -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; @@ -495,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; } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java index 6e6e14dbda..93d54f446f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java @@ -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; @@ -310,6 +313,26 @@ void shouldABindArgumentsViaIndexInSpelExpressions() { 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); @@ -321,4 +344,16 @@ public static boolean isBatman() { return false; } + @Data + @AllArgsConstructor + public static class DummySecurityObject { + DummyWithId principal; + } + + @Data + @AllArgsConstructor + public static class DummyWithId { + String id; + } + }