Skip to content

Commit a8a0fb5

Browse files
rolag-itchristophstrobl
authored andcommitted
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
1 parent 67edae8 commit a8a0fb5

File tree

3 files changed

+81
-21
lines changed

3 files changed

+81
-21
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingDocumentCodec.java

+11-7
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import static org.bson.codecs.configuration.CodecRegistries.*;
2121

2222
import java.util.ArrayList;
23-
import java.util.Collection;
2423
import java.util.Date;
2524
import java.util.List;
2625
import java.util.Map;
@@ -31,6 +30,7 @@
3130
import org.bson.BsonBinarySubType;
3231
import org.bson.BsonDocument;
3332
import org.bson.BsonDocumentWriter;
33+
import org.bson.BsonInvalidOperationException;
3434
import org.bson.BsonReader;
3535
import org.bson.BsonType;
3636
import org.bson.BsonValue;
@@ -61,6 +61,7 @@
6161
* @author Ross Lawley
6262
* @author Ralph Schaer
6363
* @author Christoph Strobl
64+
* @author Rocco Lagrotteria
6465
* @since 2.2
6566
*/
6667
public class ParameterBindingDocumentCodec implements CollectibleCodec<Document> {
@@ -172,7 +173,7 @@ public Document decode(@Nullable String json, Object[] values) {
172173

173174
public Document decode(@Nullable String json, ParameterBindingContext bindingContext) {
174175

175-
if (StringUtils.isEmpty(json)) {
176+
if (!StringUtils.hasText(json)) {
176177
return new Document();
177178
}
178179

@@ -193,7 +194,7 @@ public Document decode(@Nullable String json, ParameterBindingContext bindingCon
193194
public ExpressionDependencies captureExpressionDependencies(@Nullable String json, ValueProvider valueProvider,
194195
ExpressionParser expressionParser) {
195196

196-
if (StringUtils.isEmpty(json)) {
197+
if (!StringUtils.hasText(json)) {
197198
return ExpressionDependencies.none();
198199
}
199200

@@ -217,19 +218,24 @@ public Document decode(final BsonReader reader, final DecoderContext decoderCont
217218
if (bindingReader.currentValue instanceof org.bson.Document) {
218219
return (Document) bindingReader.currentValue;
219220
}
221+
220222
}
221223

222224
Document document = new Document();
223-
reader.readStartDocument();
224225

225226
try {
226227

228+
reader.readStartDocument();
229+
227230
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
228231
String fieldName = reader.readName();
229232
Object value = readValue(reader, decoderContext);
230233
document.put(fieldName, value);
231234
}
232-
} catch (JsonParseException e) {
235+
236+
reader.readEndDocument();
237+
238+
} catch (JsonParseException | BsonInvalidOperationException e) {
233239
try {
234240

235241
Object value = readValue(reader, decoderContext);
@@ -244,8 +250,6 @@ public Document decode(final BsonReader reader, final DecoderContext decoderCont
244250
}
245251
}
246252

247-
reader.readEndDocument();
248-
249253
return document;
250254
}
251255

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java

+18-12
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,12 @@
5757
* @author Florian Buecklers
5858
* @author Brendon Puntin
5959
* @author Christoph Strobl
60+
* @author Rocco Lagrotteria
6061
* @since 2.2
6162
*/
6263
public class ParameterBindingJsonReader extends AbstractBsonReader {
6364

64-
private static final Pattern PARAMETER_ONLY_BINDING_PATTERN = Pattern.compile("^\\?(\\d+)$");
65+
private static final Pattern ENTIRE_QUERY_BINDING_PATTERN = Pattern.compile("^\\?(\\d+)$|^[\\?:]#\\{.*\\}$");
6566
private static final Pattern PARAMETER_BINDING_PATTERN = Pattern.compile("\\?(\\d+)");
6667
private static final Pattern EXPRESSION_BINDING_PATTERN = Pattern.compile("[\\?:]#\\{.*\\}");
6768

@@ -70,7 +71,6 @@ public class ParameterBindingJsonReader extends AbstractBsonReader {
7071
private final JsonScanner scanner;
7172
private JsonToken pushedToken;
7273
Object currentValue;
73-
private Mark mark;
7474

7575
/**
7676
* Constructs a new instance with the given JSON string.
@@ -106,15 +106,8 @@ public ParameterBindingJsonReader(String json, ValueProvider accessor, SpelExpre
106106
public ParameterBindingJsonReader(String json, ValueProvider accessor, SpelExpressionParser spelExpressionParser,
107107
Supplier<EvaluationContext> evaluationContext) {
108108

109-
this.scanner = new JsonScanner(json);
110-
setContext(new Context(null, BsonContextType.TOP_LEVEL));
111-
112-
this.bindingContext = new ParameterBindingContext(accessor, spelExpressionParser, evaluationContext);
109+
this(json, new ParameterBindingContext(accessor, spelExpressionParser, evaluationContext));
113110

114-
Matcher matcher = PARAMETER_ONLY_BINDING_PATTERN.matcher(json);
115-
if (matcher.find()) {
116-
currentValue = bindableValueFor(new JsonToken(JsonTokenType.UNQUOTED_STRING, json)).getValue();
117-
}
118111
}
119112

120113
public ParameterBindingJsonReader(String json, ParameterBindingContext bindingContext) {
@@ -124,10 +117,23 @@ public ParameterBindingJsonReader(String json, ParameterBindingContext bindingCo
124117

125118
this.bindingContext = bindingContext;
126119

127-
Matcher matcher = PARAMETER_ONLY_BINDING_PATTERN.matcher(json);
120+
Matcher matcher = ENTIRE_QUERY_BINDING_PATTERN.matcher(json);
128121
if (matcher.find()) {
129-
currentValue = bindableValueFor(new JsonToken(JsonTokenType.UNQUOTED_STRING, json)).getValue();
122+
BindableValue bindingResult = bindableValueFor(new JsonToken(JsonTokenType.UNQUOTED_STRING, json));
123+
try {
124+
125+
if (bindingResult.getValue() instanceof String) {
126+
currentValue = Document.parse((String)bindingResult.getValue());
127+
} else {
128+
currentValue = bindingResult.getValue();
129+
}
130+
131+
} catch (JsonParseException jsonParseException) {
132+
throw new IllegalArgumentException(
133+
String.format("Resulting value of expression '%s' is not a valid json query", json), jsonParseException);
134+
}
130135
}
136+
131137
}
132138

133139
// Spring Data Customization END

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java

+52-2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
*
4242
* @author Christoph Strobl
4343
* @author Mark Paluch
44+
* @author Rocco Lagrotteria
4445
*/
4546
class ParameterBindingJsonReaderUnitTests {
4647

@@ -192,7 +193,6 @@ void bindQuotedDateAsDate() {
192193
@Test // DATAMONGO-2315
193194
void bindStringAsDate() {
194195

195-
Date date = new Date();
196196
Document target = parse("{ 'end_date' : { $gte : { $date : ?0 } } }", "2019-07-04T12:19:23.000Z");
197197

198198
assertThat(target).isEqualTo(Document.parse("{ 'end_date' : { $gte : { $date : '2019-07-04T12:19:23.000Z' } } } "));
@@ -347,7 +347,26 @@ void evaluatesSpelExpressionDefiningEntireQuery() {
347347
evaluationContext.setRootObject(new DummySecurityObject(new DummyWithId("wonderwoman")));
348348

349349
String json = "?#{ T(" + this.getClass().getName()
350-
+ ").isBatman() ? {'_class': { '$eq' : 'region' }} : { '$and' : { {'_class': { '$eq' : 'region' } }, {'user.supervisor': principal.id } } } }";
350+
+ ").isBatman() ? \"{'_class': { '$eq' : 'region' }}\" : \"{ '$and' : [ {'_class': { '$eq' : 'region' } }, {'user.supervisor': '\"+ principal.id +\"' } ] }\" }";
351+
352+
ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json,
353+
new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext));
354+
Document target = new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build());
355+
356+
assertThat(target)
357+
.isEqualTo(new Document("$and", Arrays.asList(new Document("_class", new Document("$eq", "region")),
358+
new Document("user.supervisor", "wonderwoman"))));
359+
}
360+
361+
@Test
362+
void bindEntireQueryUsingSpelExpression() {
363+
364+
Object[] args = new Object[] {"region"};
365+
StandardEvaluationContext evaluationContext = (StandardEvaluationContext) EvaluationContextProvider.DEFAULT
366+
.getEvaluationContext(args);
367+
evaluationContext.setRootObject(new DummySecurityObject(new DummyWithId("wonderwoman")));
368+
369+
String json = "?#{ T(" + this.getClass().getName() + ").applyFilterByUser('?0' ,principal.id) }";
351370

352371
ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json,
353372
new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext));
@@ -358,6 +377,24 @@ void evaluatesSpelExpressionDefiningEntireQuery() {
358377
new Document("user.supervisor", "wonderwoman"))));
359378
}
360379

380+
@Test
381+
void bindEntireQueryUsingParameter() {
382+
383+
Object[] args = new Object[] {"{ 'itWorks' : true }"};
384+
StandardEvaluationContext evaluationContext = (StandardEvaluationContext) EvaluationContextProvider.DEFAULT
385+
.getEvaluationContext(args);
386+
387+
String json = "?0";
388+
389+
ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json,
390+
new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext));
391+
Document target = new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build());
392+
393+
assertThat(target)
394+
.isEqualTo(new Document("itWorks", true));
395+
396+
}
397+
361398
@Test // DATAMONGO-2571
362399
void shouldParseRegexCorrectly() {
363400

@@ -400,6 +437,19 @@ private static Document parse(String json, Object... args) {
400437
public static boolean isBatman() {
401438
return false;
402439
}
440+
441+
public static String applyFilterByUser(String _class, String username) {
442+
switch (username) {
443+
case "batman":
444+
return "{'_class': { '$eq' : '"
445+
+ _class
446+
+ "' }}";
447+
default:
448+
return "{ '$and' : [ {'_class': { '$eq' : '"
449+
+ _class
450+
+ "' } }, {'user.supervisor': '" + username + "' } ] }";
451+
}
452+
}
403453

404454
@Data
405455
@AllArgsConstructor

0 commit comments

Comments
 (0)