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 18c9c2528..f8464a580 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 @@ -20,7 +20,6 @@ import static org.bson.assertions.Assertions.*; import static org.bson.codecs.configuration.CodecRegistries.*; import java.util.ArrayList; -import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; @@ -31,6 +30,7 @@ import org.bson.AbstractBsonReader.State; 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; @@ -61,6 +61,7 @@ import org.springframework.util.StringUtils; * @author Ross Lawley * @author Ralph Schaer * @author Christoph Strobl + * @author Rocco Lagrotteria * @since 2.2 */ public class ParameterBindingDocumentCodec implements CollectibleCodec { @@ -172,7 +173,7 @@ public class ParameterBindingDocumentCodec implements CollectibleCodec public Document decode(@Nullable String json, ParameterBindingContext bindingContext) { - if (StringUtils.isEmpty(json)) { + if (!StringUtils.hasText(json)) { return new Document(); } @@ -193,7 +194,7 @@ public class ParameterBindingDocumentCodec implements CollectibleCodec public ExpressionDependencies captureExpressionDependencies(@Nullable String json, ValueProvider valueProvider, ExpressionParser expressionParser) { - if (StringUtils.isEmpty(json)) { + if (!StringUtils.hasText(json)) { return ExpressionDependencies.none(); } @@ -217,19 +218,24 @@ public class ParameterBindingDocumentCodec implements CollectibleCodec if (bindingReader.currentValue instanceof org.bson.Document) { return (Document) bindingReader.currentValue; } + } 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 +250,6 @@ public class ParameterBindingDocumentCodec implements CollectibleCodec } } - reader.readEndDocument(); - return document; } 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 17f8915df..00b2da2b1 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 @@ -57,11 +57,12 @@ import org.springframework.util.ObjectUtils; * @author Florian Buecklers * @author Brendon Puntin * @author Christoph Strobl + * @author Rocco Lagrotteria * @since 2.2 */ public class ParameterBindingJsonReader extends AbstractBsonReader { - 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 +71,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 +106,8 @@ public class ParameterBindingJsonReader extends AbstractBsonReader { public ParameterBindingJsonReader(String json, ValueProvider accessor, SpelExpressionParser spelExpressionParser, Supplier evaluationContext) { - this.scanner = new JsonScanner(json); - setContext(new Context(null, BsonContextType.TOP_LEVEL)); + this(json, new ParameterBindingContext(accessor, spelExpressionParser, evaluationContext)); - this.bindingContext = 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 +117,23 @@ public class ParameterBindingJsonReader extends AbstractBsonReader { 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/util/json/ParameterBindingJsonReaderUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java index 1a684af16..5a4318e1b 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 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; * * @author Christoph Strobl * @author Mark Paluch + * @author Rocco Lagrotteria */ class ParameterBindingJsonReaderUnitTests { @@ -192,7 +193,6 @@ class ParameterBindingJsonReaderUnitTests { @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,7 @@ class ParameterBindingJsonReaderUnitTests { 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)); @@ -357,6 +357,43 @@ class ParameterBindingJsonReaderUnitTests { .isEqualTo(new Document("$and", Arrays.asList(new Document("_class", new Document("$eq", "region")), new Document("user.supervisor", "wonderwoman")))); } + + @Test + 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)); + 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 + 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 +437,19 @@ class ParameterBindingJsonReaderUnitTests { 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