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 f8464a580..3926a5379 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 @@ -217,8 +217,15 @@ public class ParameterBindingDocumentCodec implements CollectibleCodec // binds just placeholder queries like: `@Query(?0)` if (bindingReader.currentValue instanceof org.bson.Document) { return (Document) bindingReader.currentValue; + } else if (bindingReader.currentValue instanceof String) { + try { + return decode((String) bindingReader.currentValue, new Object[0]); + } catch (JsonParseException jsonParseException) { + throw new IllegalArgumentException("Expression result is not a valid json document!", jsonParseException); + } + } else if (bindingReader.currentValue instanceof Map) { + return new Document((Map) bindingReader.currentValue); } - } Document document = new 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 00b2da2b1..811477970 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 @@ -36,7 +36,6 @@ import org.bson.types.Decimal128; import org.bson.types.MaxKey; import org.bson.types.MinKey; import org.bson.types.ObjectId; - import org.springframework.data.spel.EvaluationContextProvider; import org.springframework.expression.EvaluationContext; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -120,20 +119,8 @@ public class ParameterBindingJsonReader extends AbstractBsonReader { Matcher matcher = ENTIRE_QUERY_BINDING_PATTERN.matcher(json); if (matcher.find()) { 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); - } + currentValue = bindingResult.getValue(); } - } // 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 5a4318e1b..b3ca4db83 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 java.util.Collections; import java.util.Date; import java.util.List; +import org.bson.BsonBinary; import org.bson.Document; import org.bson.codecs.DecoderContext; import org.junit.jupiter.api.Test; @@ -347,7 +348,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,11 +358,66 @@ class ParameterBindingJsonReaderUnitTests { .isEqualTo(new Document("$and", Arrays.asList(new Document("_class", new Document("$eq", "region")), new Document("user.supervisor", "wonderwoman")))); } - - @Test + + @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 + public void bindEntireQueryUsingSpelExpressionWhenEvaluationResultIsJsonString() { + + Object[] args = new Object[] { "expected", "unexpected" }; + String json = "?#{ true ? \"{ 'name': ?0 }\" : \"{ 'name' : ?1 }\" }"; + StandardEvaluationContext evaluationContext = (StandardEvaluationContext) EvaluationContextProvider.DEFAULT + .getEvaluationContext(args); + + 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("name", "expected")); + } + + @Test // GH-3871 + public void throwsExceptionWhenbindEntireQueryUsingSpelExpressionResultsInInvalidJsonString() { + + Object[] args = new Object[] { "expected", "unexpected" }; + String json = "?#{ true ? \"{ 'name': ?0 { }\" : \"{ 'name' : ?1 }\" }"; + StandardEvaluationContext evaluationContext = (StandardEvaluationContext) EvaluationContextProvider.DEFAULT + .getEvaluationContext(args); + + ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json, + new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext)); + + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build())); + } + + @Test // GH-3871 + public void bindEntireQueryUsingSpelExpressionWhenEvaluationResultIsJsonStringContainingUUID() { + + Object[] args = new Object[] { "UUID('cfbca728-4e39-4613-96bc-f920b5c37e16')", "unexpected" }; + String json = "?#{ true ? \"{ 'name': ?0 }\" : \"{ 'name' : ?1 }\" }"; + StandardEvaluationContext evaluationContext = (StandardEvaluationContext) EvaluationContextProvider.DEFAULT + .getEvaluationContext(args); + + ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json, + new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext)); + + Document target = new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build()); + + assertThat(target.get("name")).isInstanceOf(BsonBinary.class); + } + + @Test // GH-3871 void bindEntireQueryUsingSpelExpression() { - Object[] args = new Object[] {"region"}; + Object[] args = new Object[] { "region" }; StandardEvaluationContext evaluationContext = (StandardEvaluationContext) EvaluationContextProvider.DEFAULT .getEvaluationContext(args); evaluationContext.setRootObject(new DummySecurityObject(new DummyWithId("wonderwoman"))); @@ -377,10 +433,10 @@ class ParameterBindingJsonReaderUnitTests { new Document("user.supervisor", "wonderwoman")))); } - @Test + @Test // GH-3871 void bindEntireQueryUsingParameter() { - Object[] args = new Object[] {"{ 'itWorks' : true }"}; + Object[] args = new Object[] { "{ 'itWorks' : true }" }; StandardEvaluationContext evaluationContext = (StandardEvaluationContext) EvaluationContextProvider.DEFAULT .getEvaluationContext(args); @@ -390,9 +446,7 @@ class ParameterBindingJsonReaderUnitTests { new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext)); Document target = new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build()); - assertThat(target) - .isEqualTo(new Document("itWorks", true)); - + assertThat(target).isEqualTo(new Document("itWorks", true)); } @Test // DATAMONGO-2571 @@ -437,17 +491,13 @@ class ParameterBindingJsonReaderUnitTests { public static boolean isBatman() { return false; } - + public static String applyFilterByUser(String _class, String username) { switch (username) { case "batman": - return "{'_class': { '$eq' : '" - + _class - + "' }}"; + return "{'_class': { '$eq' : '" + _class + "' }}"; default: - return "{ '$and' : [ {'_class': { '$eq' : '" - + _class - + "' } }, {'user.supervisor': '" + username + "' } ] }"; + return "{ '$and' : [ {'_class': { '$eq' : '" + _class + "' } }, {'user.supervisor': '" + username + "' } ] }"; } }