diff --git a/pom.xml b/pom.xml index 63af8ca470..55f610a039 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 3.4.0-SNAPSHOT + 3.3.0-GH-3871-SNAPSHOT pom Spring Data MongoDB @@ -15,7 +15,7 @@ org.springframework.data.build spring-data-parent - 2.7.0-SNAPSHOT + 2.6.0-SNAPSHOT diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml index e2704a6753..aff481b04b 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.4.0-SNAPSHOT + 3.3.0-GH-3871-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index b75f8bf624..52e25afc4c 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.4.0-SNAPSHOT + 3.3.0-GH-3871-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index ca96626cc9..49e06c0492 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.4.0-SNAPSHOT + 3.3.0-GH-3871-SNAPSHOT ../pom.xml @@ -237,6 +237,13 @@ true + + org.slf4j + jul-to-slf4j + ${slf4j} + test + + nl.jqno.equalsverifier equalsverifier diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java index e5ee6f7492..30cbd2bb02 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java @@ -60,7 +60,7 @@ public ParameterBindingContext(ValueProvider valueProvider, ExpressionParser exp Supplier evaluationContext) { this(valueProvider, new SpELExpressionEvaluator() { - @Override + @Override @SuppressWarnings("unchecked") public T evaluate(String expressionString) { return (T) expressionParser.parseExpression(expressionString).getValue(evaluationContext.get(), Object.class); } @@ -91,7 +91,7 @@ public static ParameterBindingContext forExpressions(ValueProvider valueProvider ExpressionParser expressionParser, Function contextFunction) { return new ParameterBindingContext(valueProvider, new SpELExpressionEvaluator() { - @Override + @Override @SuppressWarnings("unchecked") public T evaluate(String expressionString) { Expression expression = expressionParser.parseExpression(expressionString); 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 18c9c25283..ab7df9bc1a 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 @@ -15,12 +15,11 @@ */ package org.springframework.data.mongodb.util.json; -import static java.util.Arrays.*; -import static org.bson.assertions.Assertions.*; -import static org.bson.codecs.configuration.CodecRegistries.*; +import static java.util.Arrays.asList; +import static org.bson.assertions.Assertions.notNull; +import static org.bson.codecs.configuration.CodecRegistries.fromProviders; import java.util.ArrayList; -import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; @@ -31,13 +30,24 @@ import org.bson.BsonBinarySubType; import org.bson.BsonDocument; import org.bson.BsonDocumentWriter; +import org.bson.BsonInvalidOperationException; import org.bson.BsonReader; import org.bson.BsonType; import org.bson.BsonValue; import org.bson.BsonWriter; import org.bson.Document; import org.bson.Transformer; -import org.bson.codecs.*; +import org.bson.codecs.BsonTypeClassMap; +import org.bson.codecs.BsonTypeCodecMap; +import org.bson.codecs.BsonValueCodecProvider; +import org.bson.codecs.Codec; +import org.bson.codecs.CollectibleCodec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.DocumentCodecProvider; +import org.bson.codecs.EncoderContext; +import org.bson.codecs.IdGenerator; +import org.bson.codecs.ObjectIdGenerator; +import org.bson.codecs.ValueCodecProvider; import org.bson.codecs.configuration.CodecRegistry; import org.bson.json.JsonParseException; import org.springframework.data.mapping.model.SpELExpressionEvaluator; @@ -61,6 +71,7 @@ * @author Ross Lawley * @author Ralph Schaer * @author Christoph Strobl + * @author Rocco Lagrotteria * @since 2.2 */ public class ParameterBindingDocumentCodec implements CollectibleCodec { @@ -172,7 +183,7 @@ public Document decode(@Nullable String json, Object[] values) { public Document decode(@Nullable String json, ParameterBindingContext bindingContext) { - if (StringUtils.isEmpty(json)) { + if (!StringUtils.hasText(json)) { return new Document(); } @@ -193,7 +204,7 @@ public Document decode(@Nullable String json, ParameterBindingContext bindingCon public ExpressionDependencies captureExpressionDependencies(@Nullable String json, ValueProvider valueProvider, ExpressionParser expressionParser) { - if (StringUtils.isEmpty(json)) { + if (!StringUtils.hasText(json)) { return ExpressionDependencies.none(); } @@ -217,19 +228,26 @@ public Document decode(final BsonReader reader, final DecoderContext decoderCont if (bindingReader.currentValue instanceof org.bson.Document) { return (Document) bindingReader.currentValue; } + if(ObjectUtils.nullSafeEquals(bindingReader.currentValue, ParameterBindingJsonReader.PLACEHOLDER)) { + return new Document(); + } } Document document = new Document(); - reader.readStartDocument(); try { + reader.readStartDocument(); + while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { String fieldName = reader.readName(); Object value = readValue(reader, decoderContext); document.put(fieldName, value); } - } catch (JsonParseException e) { + + reader.readEndDocument(); + + } catch (JsonParseException | BsonInvalidOperationException e) { try { Object value = readValue(reader, decoderContext); @@ -244,8 +262,6 @@ public Document decode(final BsonReader reader, final DecoderContext decoderCont } } - reader.readEndDocument(); - return document; } @@ -377,8 +393,6 @@ private List readList(final BsonReader reader, final DecoderContext deco */ static class DependencyCapturingExpressionEvaluator implements SpELExpressionEvaluator { - private static final Object PLACEHOLDER = new Object(); - private final ExpressionParser expressionParser; private final List dependencies = new ArrayList<>(); @@ -387,11 +401,11 @@ static class DependencyCapturingExpressionEvaluator implements SpELExpressionEva } @Nullable - @Override + @Override @SuppressWarnings("unchecked") public T evaluate(String expression) { dependencies.add(ExpressionDependencies.discover(expressionParser.parseExpression(expression))); - return (T) PLACEHOLDER; + return (T) ParameterBindingJsonReader.PLACEHOLDER; } ExpressionDependencies getCapturedDependencies() { 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 17f8915df1..21eac114ac 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 @@ -15,7 +15,7 @@ */ package org.springframework.data.mongodb.util.json; -import static java.lang.String.*; +import static java.lang.String.format; import java.text.DateFormat; import java.text.ParsePosition; @@ -29,7 +29,18 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.bson.*; +import org.bson.AbstractBsonReader; +import org.bson.BsonBinary; +import org.bson.BsonBinarySubType; +import org.bson.BsonContextType; +import org.bson.BsonDbPointer; +import org.bson.BsonInvalidOperationException; +import org.bson.BsonReaderMark; +import org.bson.BsonRegularExpression; +import org.bson.BsonTimestamp; +import org.bson.BsonType; +import org.bson.BsonUndefined; +import org.bson.Document; import org.bson.internal.Base64; import org.bson.json.JsonParseException; import org.bson.types.Decimal128; @@ -57,11 +68,14 @@ * @author Florian Buecklers * @author Brendon Puntin * @author Christoph Strobl + * @author Rocco Lagrotteria * @since 2.2 */ public class ParameterBindingJsonReader extends AbstractBsonReader { + + static final Object PLACEHOLDER = new Object(); - private static final Pattern PARAMETER_ONLY_BINDING_PATTERN = Pattern.compile("^\\?(\\d+)$"); + private static final Pattern ENTIRE_QUERY_BINDING_PATTERN = Pattern.compile("^\\?(\\d+)$|^[\\?:]#\\{.*\\}$"); private static final Pattern PARAMETER_BINDING_PATTERN = Pattern.compile("\\?(\\d+)"); private static final Pattern EXPRESSION_BINDING_PATTERN = Pattern.compile("[\\?:]#\\{.*\\}"); @@ -70,7 +84,6 @@ public class ParameterBindingJsonReader extends AbstractBsonReader { private final JsonScanner scanner; private JsonToken pushedToken; Object currentValue; - private Mark mark; /** * Constructs a new instance with the given JSON string. @@ -106,15 +119,8 @@ public ParameterBindingJsonReader(String json, ValueProvider accessor, SpelExpre public ParameterBindingJsonReader(String json, ValueProvider accessor, SpelExpressionParser spelExpressionParser, Supplier evaluationContext) { - this.scanner = new JsonScanner(json); - setContext(new Context(null, BsonContextType.TOP_LEVEL)); - - this.bindingContext = new ParameterBindingContext(accessor, spelExpressionParser, evaluationContext); + this(json, new ParameterBindingContext(accessor, spelExpressionParser, evaluationContext)); - Matcher matcher = PARAMETER_ONLY_BINDING_PATTERN.matcher(json); - if (matcher.find()) { - currentValue = bindableValueFor(new JsonToken(JsonTokenType.UNQUOTED_STRING, json)).getValue(); - } } public ParameterBindingJsonReader(String json, ParameterBindingContext bindingContext) { @@ -124,10 +130,23 @@ public ParameterBindingJsonReader(String json, ParameterBindingContext bindingCo this.bindingContext = bindingContext; - Matcher matcher = PARAMETER_ONLY_BINDING_PATTERN.matcher(json); + Matcher matcher = ENTIRE_QUERY_BINDING_PATTERN.matcher(json); if (matcher.find()) { - currentValue = bindableValueFor(new JsonToken(JsonTokenType.UNQUOTED_STRING, json)).getValue(); + BindableValue bindingResult = bindableValueFor(new JsonToken(JsonTokenType.UNQUOTED_STRING, json)); + try { + + if (bindingResult.getValue() instanceof String) { + currentValue = Document.parse((String)bindingResult.getValue()); + } else { + currentValue = bindingResult.getValue(); + } + + } catch (JsonParseException jsonParseException) { + throw new IllegalArgumentException( + String.format("Resulting value of expression '%s' is not a valid json query", json), jsonParseException); + } } + } // Spring Data Customization END diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java index 34a43c9103..10ca55b312 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java @@ -2560,7 +2560,6 @@ public T next() { return iterator.next(); } - @Override public int available() { return 1; } 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 1a684af164..5892d2131d 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 @@ -41,6 +41,7 @@ * * @author Christoph Strobl * @author Mark Paluch + * @author Rocco Lagrotteria */ class ParameterBindingJsonReaderUnitTests { @@ -192,7 +193,6 @@ void bindQuotedDateAsDate() { @Test // DATAMONGO-2315 void bindStringAsDate() { - Date date = new Date(); Document target = parse("{ 'end_date' : { $gte : { $date : ?0 } } }", "2019-07-04T12:19:23.000Z"); assertThat(target).isEqualTo(Document.parse("{ 'end_date' : { $gte : { $date : '2019-07-04T12:19:23.000Z' } } } ")); @@ -347,7 +347,35 @@ void evaluatesSpelExpressionDefiningEntireQuery() { evaluationContext.setRootObject(new DummySecurityObject(new DummyWithId("wonderwoman"))); String json = "?#{ T(" + this.getClass().getName() - + ").isBatman() ? {'_class': { '$eq' : 'region' }} : { '$and' : { {'_class': { '$eq' : 'region' } }, {'user.supervisor': principal.id } } } }"; + + ").isBatman() ? \"{'_class': { '$eq' : 'region' }}\" : \"{ '$and' : [ {'_class': { '$eq' : 'region' } }, {'user.supervisor': '\"+ 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.supervisor", "wonderwoman")))); + } + + @Test // GH-3871 + public void capturingExpressionDependenciesShouldNotThrowParseErrorForSpelOnlyJson() { + + Object[] args = new Object[] { "1", "2" }; + String json = "?#{ true ? { 'name': #name } : { 'name' : #name + 'trouble' } }"; + + new ParameterBindingDocumentCodec().captureExpressionDependencies(json, (index) -> args[index], new SpelExpressionParser()); + } + + @Test // GH-3871 + void bindEntireQueryUsingSpelExpression() { + + Object[] args = new Object[] {"region"}; + StandardEvaluationContext evaluationContext = (StandardEvaluationContext) EvaluationContextProvider.DEFAULT + .getEvaluationContext(args); + evaluationContext.setRootObject(new DummySecurityObject(new DummyWithId("wonderwoman"))); + + String json = "?#{ T(" + this.getClass().getName() + ").applyFilterByUser('?0' ,principal.id) }"; ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json, new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext)); @@ -358,6 +386,24 @@ void evaluatesSpelExpressionDefiningEntireQuery() { new Document("user.supervisor", "wonderwoman")))); } + @Test // GH-3871 + void bindEntireQueryUsingParameter() { + + Object[] args = new Object[] {"{ 'itWorks' : true }"}; + StandardEvaluationContext evaluationContext = (StandardEvaluationContext) EvaluationContextProvider.DEFAULT + .getEvaluationContext(args); + + String json = "?0"; + + 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("itWorks", true)); + + } + @Test // DATAMONGO-2571 void shouldParseRegexCorrectly() { @@ -400,6 +446,19 @@ private static Document parse(String json, Object... args) { public static boolean isBatman() { return false; } + + public static String applyFilterByUser(String _class, String username) { + switch (username) { + case "batman": + return "{'_class': { '$eq' : '" + + _class + + "' }}"; + default: + return "{ '$and' : [ {'_class': { '$eq' : '" + + _class + + "' } }, {'user.supervisor': '" + username + "' } ] }"; + } + } @Data @AllArgsConstructor