Skip to content

Commit 51b0948

Browse files
committed
Add support for Value Expressions in Repository Query methods.
1 parent c03d86b commit 51b0948

23 files changed

+471
-212
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@
2020

2121
import org.bson.Document;
2222
import org.bson.codecs.configuration.CodecRegistry;
23+
24+
import org.springframework.data.expression.ValueEvaluationContext;
25+
import org.springframework.data.expression.ValueExpression;
26+
import org.springframework.data.expression.ValueExpressionParser;
2327
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
28+
import org.springframework.data.mapping.model.ValueExpressionEvaluator;
2429
import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind;
2530
import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery;
2631
import org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind;
@@ -41,13 +46,14 @@
4146
import org.springframework.data.mongodb.util.json.ParameterBindingContext;
4247
import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec;
4348
import org.springframework.data.repository.query.ParameterAccessor;
44-
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
49+
import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProvider;
4550
import org.springframework.data.repository.query.RepositoryQuery;
4651
import org.springframework.data.repository.query.ResultProcessor;
52+
import org.springframework.data.repository.query.ValueExpressionSupportHolder;
4753
import org.springframework.data.spel.ExpressionDependencies;
4854
import org.springframework.data.util.Lazy;
4955
import org.springframework.expression.EvaluationContext;
50-
import org.springframework.expression.ExpressionParser;
56+
import org.springframework.expression.spel.standard.SpelExpressionParser;
5157
import org.springframework.lang.Nullable;
5258
import org.springframework.util.Assert;
5359
import org.springframework.util.ObjectUtils;
@@ -70,8 +76,8 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
7076
private final MongoOperations operations;
7177
private final ExecutableFind<?> executableFind;
7278
private final ExecutableUpdate<?> executableUpdate;
73-
private final ExpressionParser expressionParser;
74-
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
79+
private final ValueExpressionParser expressionParser;
80+
private final QueryMethodValueEvaluationContextProvider evaluationContextProvider;
7581
private final Lazy<ParameterBindingDocumentCodec> codec = Lazy
7682
.of(() -> new ParameterBindingDocumentCodec(getCodecRegistry()));
7783

@@ -80,16 +86,14 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
8086
*
8187
* @param method must not be {@literal null}.
8288
* @param operations must not be {@literal null}.
83-
* @param expressionParser must not be {@literal null}.
84-
* @param evaluationContextProvider must not be {@literal null}.
89+
* @param expressionSupportHolder must not be {@literal null}.
8590
*/
86-
public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations, ExpressionParser expressionParser,
87-
QueryMethodEvaluationContextProvider evaluationContextProvider) {
91+
public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations,
92+
ValueExpressionSupportHolder expressionSupportHolder) {
8893

8994
Assert.notNull(operations, "MongoOperations must not be null");
9095
Assert.notNull(method, "MongoQueryMethod must not be null");
91-
Assert.notNull(expressionParser, "SpelExpressionParser must not be null");
92-
Assert.notNull(evaluationContextProvider, "QueryMethodEvaluationContextProvider must not be null");
96+
Assert.notNull(expressionSupportHolder, "ValueExpressionSupportHolder must not be null");
9397

9498
this.method = method;
9599
this.operations = operations;
@@ -99,8 +103,8 @@ public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations, E
99103

100104
this.executableFind = operations.query(type);
101105
this.executableUpdate = operations.update(type);
102-
this.expressionParser = expressionParser;
103-
this.evaluationContextProvider = evaluationContextProvider;
106+
this.expressionParser = expressionSupportHolder;
107+
this.evaluationContextProvider = expressionSupportHolder.createValueContextProvider(method.getParameters());
104108
}
105109

106110
public MongoQueryMethod getQueryMethod() {
@@ -155,7 +159,7 @@ protected Object doExecute(MongoQueryMethod method, ResultProcessor processor, C
155159
* @since 4.2
156160
*/
157161
private Query applyAnnotatedReadPreferenceIfPresent(Query query) {
158-
162+
159163
if (!method.hasAnnotatedReadPreference()) {
160164
return query;
161165
}
@@ -242,7 +246,7 @@ Query applyAnnotatedDefaultSortIfPresent(Query query) {
242246
Query applyAnnotatedCollationIfPresent(Query query, ConvertingParameterAccessor accessor) {
243247

244248
return QueryUtils.applyCollation(query, method.hasAnnotatedCollation() ? method.getAnnotatedCollation() : null,
245-
accessor, getQueryMethod().getParameters(), expressionParser, evaluationContextProvider);
249+
accessor, getExpressionEvaluatorFor(accessor));
246250
}
247251

248252
/**
@@ -344,10 +348,7 @@ private Document bindParameters(String source, ConvertingParameterAccessor acces
344348
*/
345349
protected ParameterBindingContext prepareBindingContext(String source, ConvertingParameterAccessor accessor) {
346350

347-
ExpressionDependencies dependencies = getParameterBindingCodec().captureExpressionDependencies(source,
348-
accessor::getBindableValue, expressionParser);
349-
350-
SpELExpressionEvaluator evaluator = getSpELExpressionEvaluatorFor(dependencies, accessor);
351+
ValueExpressionEvaluator evaluator = getExpressionEvaluatorFor(accessor);
351352
return new ParameterBindingContext(accessor::getBindableValue, evaluator);
352353
}
353354

@@ -372,8 +373,31 @@ protected ParameterBindingDocumentCodec getParameterBindingCodec() {
372373
protected SpELExpressionEvaluator getSpELExpressionEvaluatorFor(ExpressionDependencies dependencies,
373374
ConvertingParameterAccessor accessor) {
374375

375-
return new DefaultSpELExpressionEvaluator(expressionParser, evaluationContextProvider
376-
.getEvaluationContext(getQueryMethod().getParameters(), accessor.getValues(), dependencies));
376+
return new DefaultSpELExpressionEvaluator(new SpelExpressionParser(),
377+
evaluationContextProvider.getEvaluationContext(accessor.getValues(), dependencies).getEvaluationContext());
378+
}
379+
380+
/**
381+
* Obtain a {@link ValueExpressionEvaluator} suitable to evaluate expressions.
382+
*
383+
* @param accessor must not be {@literal null}.
384+
* @return the {@link ValueExpressionEvaluator}.
385+
* @since 4.3
386+
*/
387+
protected ValueExpressionEvaluator getExpressionEvaluatorFor(MongoParameterAccessor accessor) {
388+
389+
return new ValueExpressionEvaluator() {
390+
391+
@Override
392+
public <T> T evaluate(String expressionString) {
393+
394+
ValueExpression expression = expressionParser.parse(expressionString);
395+
ValueEvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(accessor.getValues(),
396+
expression.getExpressionDependencies());
397+
398+
return (T) expression.evaluate(evaluationContext);
399+
}
400+
};
377401
}
378402

379403
/**

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java

Lines changed: 72 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@
2525
import org.bson.Document;
2626
import org.bson.codecs.configuration.CodecRegistry;
2727
import org.reactivestreams.Publisher;
28+
2829
import org.springframework.core.convert.converter.Converter;
30+
import org.springframework.data.expression.ValueExpression;
2931
import org.springframework.data.mapping.model.EntityInstantiators;
3032
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
33+
import org.springframework.data.mapping.model.ValueExpressionEvaluator;
3134
import org.springframework.data.mongodb.core.MongoOperations;
3235
import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithProjection;
3336
import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithQuery;
@@ -48,12 +51,13 @@
4851
import org.springframework.data.mongodb.util.json.ParameterBindingContext;
4952
import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec;
5053
import org.springframework.data.repository.query.ParameterAccessor;
51-
import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
54+
import org.springframework.data.repository.query.ReactiveQueryMethodValueEvaluationContextProvider;
5255
import org.springframework.data.repository.query.RepositoryQuery;
5356
import org.springframework.data.repository.query.ResultProcessor;
57+
import org.springframework.data.repository.query.ValueExpressionSupportHolder;
5458
import org.springframework.data.spel.ExpressionDependencies;
5559
import org.springframework.data.util.TypeInformation;
56-
import org.springframework.expression.ExpressionParser;
60+
import org.springframework.expression.spel.standard.SpelExpressionParser;
5761
import org.springframework.lang.Nullable;
5862
import org.springframework.util.Assert;
5963
import org.springframework.util.ObjectUtils;
@@ -76,31 +80,30 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
7680
private final EntityInstantiators instantiators;
7781
private final FindWithProjection<?> findOperationWithProjection;
7882
private final ReactiveUpdate<?> updateOps;
79-
private final ExpressionParser expressionParser;
80-
private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider;
83+
private final ValueExpressionSupportHolder expressionSupportHolder;
84+
private final ReactiveQueryMethodValueEvaluationContextProvider evaluationContextProvider;
8185

8286
/**
8387
* Creates a new {@link AbstractReactiveMongoQuery} from the given {@link MongoQueryMethod} and
8488
* {@link MongoOperations}.
8589
*
8690
* @param method must not be {@literal null}.
8791
* @param operations must not be {@literal null}.
88-
* @param expressionParser must not be {@literal null}.
89-
* @param evaluationContextProvider must not be {@literal null}.
92+
* @param expressionSupportHolder must not be {@literal null}.
9093
*/
9194
public AbstractReactiveMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations operations,
92-
ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
95+
ValueExpressionSupportHolder expressionSupportHolder) {
9396

9497
Assert.notNull(method, "MongoQueryMethod must not be null");
9598
Assert.notNull(operations, "ReactiveMongoOperations must not be null");
96-
Assert.notNull(expressionParser, "SpelExpressionParser must not be null");
97-
Assert.notNull(evaluationContextProvider, "ReactiveEvaluationContextExtension must not be null");
99+
Assert.notNull(expressionSupportHolder, "ValueExpressionSupportHolder must not be null");
98100

99101
this.method = method;
100102
this.operations = operations;
101103
this.instantiators = new EntityInstantiators();
102-
this.expressionParser = expressionParser;
103-
this.evaluationContextProvider = evaluationContextProvider;
104+
this.expressionSupportHolder = expressionSupportHolder;
105+
this.evaluationContextProvider = (ReactiveQueryMethodValueEvaluationContextProvider) expressionSupportHolder
106+
.createValueContextProvider(method.getParameters());
104107

105108
MongoEntityMetadata<?> metadata = method.getEntityInformation();
106109
Class<?> type = metadata.getCollectionEntity().getType();
@@ -231,7 +234,6 @@ private boolean isTailable(MongoQueryMethod method) {
231234
return method.getTailableAnnotation() != null;
232235
}
233236

234-
235237
Query applyQueryMetaAttributesWhenPresent(Query query) {
236238

237239
if (method.hasQueryMetaAttributes()) {
@@ -270,7 +272,7 @@ Query applyAnnotatedDefaultSortIfPresent(Query query) {
270272
Query applyAnnotatedCollationIfPresent(Query query, ConvertingParameterAccessor accessor) {
271273

272274
return QueryUtils.applyCollation(query, method.hasAnnotatedCollation() ? method.getAnnotatedCollation() : null,
273-
accessor, getQueryMethod().getParameters(), expressionParser, evaluationContextProvider);
275+
accessor, getValueExpressionEvaluator(accessor));
274276
}
275277

276278
/**
@@ -290,7 +292,8 @@ Query applyHintIfPresent(Query query) {
290292
}
291293

292294
/**
293-
* If present apply the {@link com.mongodb.ReadPreference} from the {@link org.springframework.data.mongodb.repository.ReadPreference} annotation.
295+
* If present apply the {@link com.mongodb.ReadPreference} from the
296+
* {@link org.springframework.data.mongodb.repository.ReadPreference} annotation.
294297
*
295298
* @param query must not be {@literal null}.
296299
* @return never {@literal null}.
@@ -382,15 +385,15 @@ private Mono<AggregationOperation> computePipelineStage(String source, MongoPara
382385
});
383386
}
384387

385-
private Mono<Tuple2<SpELExpressionEvaluator, ParameterBindingDocumentCodec>> expressionEvaluator(String source,
388+
private Mono<Tuple2<ValueExpressionEvaluator, ParameterBindingDocumentCodec>> expressionEvaluator(String source,
386389
MongoParameterAccessor accessor, ParameterBindingDocumentCodec codec) {
387390

388391
ExpressionDependencies dependencies = codec.captureExpressionDependencies(source, accessor::getBindableValue,
389-
expressionParser);
390-
return getSpelEvaluatorFor(dependencies, accessor).zipWith(Mono.just(codec));
392+
expressionSupportHolder.getValueExpressionParser());
393+
return getValueExpressionEvaluatorLater(dependencies, accessor).zipWith(Mono.just(codec));
391394
}
392395

393-
private Document decode(SpELExpressionEvaluator expressionEvaluator, String source, MongoParameterAccessor accessor,
396+
private Document decode(ValueExpressionEvaluator expressionEvaluator, String source, MongoParameterAccessor accessor,
394397
ParameterBindingDocumentCodec codec) {
395398

396399
ParameterBindingContext bindingContext = new ParameterBindingContext(accessor::getBindableValue,
@@ -420,13 +423,60 @@ protected Mono<ParameterBindingDocumentCodec> getParameterBindingCodec() {
420423
protected Mono<SpELExpressionEvaluator> getSpelEvaluatorFor(ExpressionDependencies dependencies,
421424
MongoParameterAccessor accessor) {
422425

423-
return evaluationContextProvider
424-
.getEvaluationContextLater(getQueryMethod().getParameters(), accessor.getValues(), dependencies)
425-
.map(evaluationContext -> (SpELExpressionEvaluator) new DefaultSpELExpressionEvaluator(expressionParser,
426-
evaluationContext))
426+
return evaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies)
427+
.map(evaluationContext -> (SpELExpressionEvaluator) new DefaultSpELExpressionEvaluator(
428+
new SpelExpressionParser(), evaluationContext.getEvaluationContext()))
427429
.defaultIfEmpty(DefaultSpELExpressionEvaluator.unsupported());
428430
}
429431

432+
/**
433+
* Obtain a {@link ValueExpressionEvaluator} suitable to evaluate expressions.
434+
*
435+
* @param accessor must not be {@literal null}.
436+
* @since 4.3
437+
*/
438+
ValueExpressionEvaluator getValueExpressionEvaluator(MongoParameterAccessor accessor) {
439+
440+
return new ValueExpressionEvaluator() {
441+
442+
@Override
443+
public <T> T evaluate(String expressionString) {
444+
445+
ValueExpression expression = expressionSupportHolder.parse(expressionString);
446+
return (T) expression.evaluate(evaluationContextProvider.getEvaluationContext(accessor.getValues(),
447+
expression.getExpressionDependencies()));
448+
}
449+
};
450+
}
451+
452+
/**
453+
* Obtain a {@link Mono publisher} emitting the {@link ValueExpressionEvaluator} suitable to evaluate expressions
454+
* backed by the given dependencies.
455+
*
456+
* @param dependencies must not be {@literal null}.
457+
* @param accessor must not be {@literal null}.
458+
* @return a {@link Mono} emitting the {@link ValueExpressionEvaluator} when ready.
459+
* @since 4.3
460+
*/
461+
protected Mono<ValueExpressionEvaluator> getValueExpressionEvaluatorLater(ExpressionDependencies dependencies,
462+
MongoParameterAccessor accessor) {
463+
464+
return evaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies)
465+
.map(evaluationContext -> {
466+
467+
return new ValueExpressionEvaluator() {
468+
@Override
469+
public <T> T evaluate(String expressionString) {
470+
471+
ValueExpression expression = expressionSupportHolder.parse(expressionString);
472+
473+
return (T) expression.evaluate(evaluationContext);
474+
}
475+
};
476+
477+
});
478+
}
479+
430480
/**
431481
* @return a {@link Mono} emitting the {@link CodecRegistry} when ready.
432482
* @since 2.4

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AggregationUtils.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
import java.util.function.LongUnaryOperator;
2222

2323
import org.bson.Document;
24+
2425
import org.springframework.data.domain.Pageable;
2526
import org.springframework.data.domain.Sort.Order;
27+
import org.springframework.data.mapping.model.ValueExpressionEvaluator;
2628
import org.springframework.data.mongodb.core.aggregation.Aggregation;
2729
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
2830
import org.springframework.data.mongodb.core.aggregation.AggregationPipeline;
@@ -31,8 +33,6 @@
3133
import org.springframework.data.mongodb.core.query.Collation;
3234
import org.springframework.data.mongodb.core.query.Meta;
3335
import org.springframework.data.mongodb.core.query.Query;
34-
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
35-
import org.springframework.expression.ExpressionParser;
3636
import org.springframework.lang.Nullable;
3737
import org.springframework.util.ClassUtils;
3838
import org.springframework.util.ObjectUtils;
@@ -62,15 +62,11 @@ private AggregationUtils() {}
6262
* @param accessor must not be {@literal null}.
6363
* @return the {@link Query} having proper {@link Collation}.
6464
* @see AggregationOptions#getCollation()
65-
* @see CollationUtils#computeCollation(String, ConvertingParameterAccessor, MongoParameters, ExpressionParser,
66-
* QueryMethodEvaluationContextProvider)
6765
*/
6866
static AggregationOptions.Builder applyCollation(AggregationOptions.Builder builder,
69-
@Nullable String collationExpression, ConvertingParameterAccessor accessor, MongoParameters parameters,
70-
ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
67+
@Nullable String collationExpression, ConvertingParameterAccessor accessor, ValueExpressionEvaluator evaluator) {
7168

72-
Collation collation = CollationUtils.computeCollation(collationExpression, accessor, parameters, expressionParser,
73-
evaluationContextProvider);
69+
Collation collation = CollationUtils.computeCollation(collationExpression, accessor, evaluator);
7470
return collation == null ? builder : builder.collation(collation);
7571
}
7672

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/CollationUtils.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@
2020
import java.util.regex.Pattern;
2121

2222
import org.bson.Document;
23+
24+
import org.springframework.data.mapping.model.ValueExpressionEvaluator;
2325
import org.springframework.data.mongodb.core.query.Collation;
2426
import org.springframework.data.mongodb.util.json.ParameterBindingContext;
2527
import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec;
26-
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
27-
import org.springframework.expression.ExpressionParser;
2828
import org.springframework.lang.Nullable;
2929
import org.springframework.util.NumberUtils;
3030
import org.springframework.util.ObjectUtils;
@@ -51,16 +51,13 @@ private CollationUtils() {
5151
*
5252
* @param collationExpression
5353
* @param accessor
54-
* @param parameters
55-
* @param expressionParser
56-
* @param evaluationContextProvider
54+
* @param expressionEvaluator
5755
* @return can be {@literal null} if neither {@link ConvertingParameterAccessor#getCollation()} nor
5856
* {@literal collationExpression} are present.
5957
*/
6058
@Nullable
6159
static Collation computeCollation(@Nullable String collationExpression, ConvertingParameterAccessor accessor,
62-
MongoParameters parameters, ExpressionParser expressionParser,
63-
QueryMethodEvaluationContextProvider evaluationContextProvider) {
60+
ValueExpressionEvaluator expressionEvaluator) {
6461

6562
if (accessor.getCollation() != null) {
6663
return accessor.getCollation();
@@ -73,8 +70,7 @@ static Collation computeCollation(@Nullable String collationExpression, Converti
7370
if (collationExpression.stripLeading().startsWith("{")) {
7471

7572
ParameterBindingContext bindingContext = ParameterBindingContext.forExpressions(accessor::getBindableValue,
76-
expressionParser, dependencies -> evaluationContextProvider.getEvaluationContext(parameters,
77-
accessor.getValues(), dependencies));
73+
expressionEvaluator);
7874

7975
return Collation.from(CODEC.decode(collationExpression, bindingContext));
8076
}

0 commit comments

Comments
 (0)