Fix expression defining entire query in annotated repository methods.
This fix enables defining an entire JSON-based query in Query and Aggregate annotations using a single parameter or SpEL Expression. Resolves: #3871 Original Pull Request: #3907
This commit is contained in:
committed by
Christoph Strobl
parent
67edae8602
commit
a8a0fb5dba
@@ -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<Document> {
|
||||
@@ -172,7 +173,7 @@ public class ParameterBindingDocumentCodec implements CollectibleCodec<Document>
|
||||
|
||||
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<Document>
|
||||
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<Document>
|
||||
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<Document>
|
||||
}
|
||||
}
|
||||
|
||||
reader.readEndDocument();
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
|
||||
@@ -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> 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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user