Skip to content

Commit 7b6e260

Browse files
committed
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.
1 parent 8dd6ab1 commit 7b6e260

File tree

3 files changed

+76
-16
lines changed

3 files changed

+76
-16
lines changed

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.bson.BsonBinarySubType;
3232
import org.bson.BsonDocument;
3333
import org.bson.BsonDocumentWriter;
34+
import org.bson.BsonInvalidOperationException;
3435
import org.bson.BsonReader;
3536
import org.bson.BsonType;
3637
import org.bson.BsonValue;
@@ -61,6 +62,7 @@
6162
* @author Ross Lawley
6263
* @author Ralph Schaer
6364
* @author Christoph Strobl
65+
* @author Rocco Lagrotteria
6466
* @since 2.2
6567
*/
6668
public class ParameterBindingDocumentCodec implements CollectibleCodec<Document> {
@@ -217,19 +219,24 @@ public Document decode(final BsonReader reader, final DecoderContext decoderCont
217219
if (bindingReader.currentValue instanceof org.bson.Document) {
218220
return (Document) bindingReader.currentValue;
219221
}
222+
220223
}
221224

222225
Document document = new Document();
223-
reader.readStartDocument();
224226

225227
try {
226228

229+
reader.readStartDocument();
230+
227231
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
228232
String fieldName = reader.readName();
229233
Object value = readValue(reader, decoderContext);
230234
document.put(fieldName, value);
231235
}
232-
} catch (JsonParseException e) {
236+
237+
reader.readEndDocument();
238+
239+
} catch (JsonParseException | BsonInvalidOperationException e) {
233240
try {
234241

235242
Object value = readValue(reader, decoderContext);
@@ -244,8 +251,6 @@ public Document decode(final BsonReader reader, final DecoderContext decoderCont
244251
}
245252
}
246253

247-
reader.readEndDocument();
248-
249254
return document;
250255
}
251256

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

Lines changed: 15 additions & 11 deletions
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

@@ -106,15 +107,8 @@ public ParameterBindingJsonReader(String json, ValueProvider accessor, SpelExpre
106107
public ParameterBindingJsonReader(String json, ValueProvider accessor, SpelExpressionParser spelExpressionParser,
107108
Supplier<EvaluationContext> evaluationContext) {
108109

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

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

120114
public ParameterBindingJsonReader(String json, ParameterBindingContext bindingContext) {
@@ -124,10 +118,20 @@ public ParameterBindingJsonReader(String json, ParameterBindingContext bindingCo
124118

125119
this.bindingContext = bindingContext;
126120

127-
Matcher matcher = PARAMETER_ONLY_BINDING_PATTERN.matcher(json);
121+
Matcher matcher = ENTIRE_QUERY_BINDING_PATTERN.matcher(json);
128122
if (matcher.find()) {
129-
currentValue = bindableValueFor(new JsonToken(JsonTokenType.UNQUOTED_STRING, json)).getValue();
123+
BindableValue bindingResult = bindableValueFor(new JsonToken(JsonTokenType.UNQUOTED_STRING, json));
124+
try {
125+
if (bindingResult.getType().equals(BsonType.DOCUMENT)) {
126+
currentValue = Document.parse(bindingResult.getValue().toString());
127+
}
128+
129+
} catch (JsonParseException jsonParseException) {
130+
throw new IllegalArgumentException(
131+
String.format("Resulting value of expression '%s' is not a valid json query", json), jsonParseException);
132+
}
130133
}
134+
131135
}
132136

133137
// Spring Data Customization END

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

Lines changed: 52 additions & 1 deletion
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

@@ -347,7 +348,26 @@ void evaluatesSpelExpressionDefiningEntireQuery() {
347348
evaluationContext.setRootObject(new DummySecurityObject(new DummyWithId("wonderwoman")));
348349

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

352372
ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json,
353373
new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext));
@@ -358,6 +378,24 @@ void evaluatesSpelExpressionDefiningEntireQuery() {
358378
new Document("user.supervisor", "wonderwoman"))));
359379
}
360380

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

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

404455
@Data
405456
@AllArgsConstructor

0 commit comments

Comments
 (0)