Skip to content

Commit 8b9182e

Browse files
marcingrzejszczakmp911de
authored andcommitted
Add support for Value Expressions in Repository Query methods.
Closes #4677 Original pull request: #4683
1 parent db44512 commit 8b9182e

25 files changed

+681
-250
lines changed

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

+56-11
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@
2121
import org.bson.Document;
2222
import org.bson.codecs.configuration.CodecRegistry;
2323

24+
import org.springframework.core.env.StandardEnvironment;
25+
import org.springframework.data.expression.ValueEvaluationContextProvider;
26+
import org.springframework.data.expression.ValueExpression;
27+
import org.springframework.data.expression.ValueExpressionParser;
2428
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
29+
import org.springframework.data.mapping.model.ValueExpressionEvaluator;
2530
import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind;
2631
import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery;
2732
import org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind;
@@ -43,12 +48,15 @@
4348
import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec;
4449
import org.springframework.data.repository.query.ParameterAccessor;
4550
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
51+
import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
4652
import org.springframework.data.repository.query.RepositoryQuery;
4753
import org.springframework.data.repository.query.ResultProcessor;
54+
import org.springframework.data.repository.query.ValueExpressionDelegate;
4855
import org.springframework.data.spel.ExpressionDependencies;
4956
import org.springframework.data.util.Lazy;
5057
import org.springframework.expression.EvaluationContext;
5158
import org.springframework.expression.ExpressionParser;
59+
import org.springframework.expression.spel.standard.SpelExpressionParser;
5260
import org.springframework.lang.Nullable;
5361
import org.springframework.util.Assert;
5462
import org.springframework.util.ObjectUtils;
@@ -71,10 +79,10 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
7179
private final MongoOperations operations;
7280
private final ExecutableFind<?> executableFind;
7381
private final ExecutableUpdate<?> executableUpdate;
74-
private final ExpressionParser expressionParser;
75-
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
7682
private final Lazy<ParameterBindingDocumentCodec> codec = Lazy
7783
.of(() -> new ParameterBindingDocumentCodec(getCodecRegistry()));
84+
private final ValueExpressionDelegate valueExpressionDelegate;
85+
private final ValueEvaluationContextProvider valueEvaluationContextProvider;
7886

7987
/**
8088
* Creates a new {@link AbstractMongoQuery} from the given {@link MongoQueryMethod} and {@link MongoOperations}.
@@ -83,7 +91,9 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
8391
* @param operations must not be {@literal null}.
8492
* @param expressionParser must not be {@literal null}.
8593
* @param evaluationContextProvider must not be {@literal null}.
94+
* @deprecated use the constructor version with {@link ValueExpressionDelegate}
8695
*/
96+
@Deprecated(since = "4.4.0")
8797
public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations, ExpressionParser expressionParser,
8898
QueryMethodEvaluationContextProvider evaluationContextProvider) {
8999

@@ -100,10 +110,36 @@ public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations, E
100110

101111
this.executableFind = operations.query(type);
102112
this.executableUpdate = operations.update(type);
103-
this.expressionParser = expressionParser;
104-
this.evaluationContextProvider = evaluationContextProvider;
113+
this.valueExpressionDelegate = new ValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), evaluationContextProvider.getEvaluationContextProvider()), ValueExpressionParser.create(() -> expressionParser));
114+
this.valueEvaluationContextProvider = valueExpressionDelegate.createValueContextProvider(method.getParameters());
105115
}
106116

117+
/**
118+
* Creates a new {@link AbstractMongoQuery} from the given {@link MongoQueryMethod} and {@link MongoOperations}.
119+
*
120+
* @param method must not be {@literal null}.
121+
* @param operations must not be {@literal null}.
122+
* @param delegate must not be {@literal null}
123+
* @since 4.4.0
124+
*/
125+
public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations, ValueExpressionDelegate delegate) {
126+
127+
Assert.notNull(operations, "MongoOperations must not be null");
128+
Assert.notNull(method, "MongoQueryMethod must not be null");
129+
130+
this.method = method;
131+
this.operations = operations;
132+
133+
MongoEntityMetadata<?> metadata = method.getEntityInformation();
134+
Class<?> type = metadata.getCollectionEntity().getType();
135+
136+
this.executableFind = operations.query(type);
137+
this.executableUpdate = operations.update(type);
138+
this.valueExpressionDelegate = delegate;
139+
this.valueEvaluationContextProvider = delegate.createValueContextProvider(method.getParameters());
140+
}
141+
142+
@Override
107143
public MongoQueryMethod getQueryMethod() {
108144
return method;
109145
}
@@ -243,7 +279,7 @@ Query applyAnnotatedDefaultSortIfPresent(Query query) {
243279
Query applyAnnotatedCollationIfPresent(Query query, ConvertingParameterAccessor accessor) {
244280

245281
return QueryUtils.applyCollation(query, method.hasAnnotatedCollation() ? method.getAnnotatedCollation() : null,
246-
accessor, getQueryMethod().getParameters(), expressionParser, evaluationContextProvider);
282+
accessor, getExpressionEvaluatorFor(accessor));
247283
}
248284

249285
/**
@@ -346,10 +382,7 @@ private Document bindParameters(String source, ConvertingParameterAccessor acces
346382
*/
347383
protected ParameterBindingContext prepareBindingContext(String source, ConvertingParameterAccessor accessor) {
348384

349-
ExpressionDependencies dependencies = getParameterBindingCodec().captureExpressionDependencies(source,
350-
accessor::getBindableValue, expressionParser);
351-
352-
SpELExpressionEvaluator evaluator = getSpELExpressionEvaluatorFor(dependencies, accessor);
385+
ValueExpressionEvaluator evaluator = getExpressionEvaluatorFor(accessor);
353386
return new ParameterBindingContext(accessor::getBindableValue, evaluator);
354387
}
355388

@@ -374,8 +407,19 @@ protected ParameterBindingDocumentCodec getParameterBindingCodec() {
374407
protected SpELExpressionEvaluator getSpELExpressionEvaluatorFor(ExpressionDependencies dependencies,
375408
ConvertingParameterAccessor accessor) {
376409

377-
return new DefaultSpELExpressionEvaluator(expressionParser, evaluationContextProvider
378-
.getEvaluationContext(getQueryMethod().getParameters(), accessor.getValues(), dependencies));
410+
return new DefaultSpELExpressionEvaluator(new SpelExpressionParser(), valueEvaluationContextProvider.getEvaluationContext(accessor.getValues(), dependencies).getEvaluationContext());
411+
}
412+
413+
/**
414+
* Obtain a {@link ValueExpressionEvaluator} suitable to evaluate expressions.
415+
*
416+
* @param accessor must not be {@literal null}.
417+
* @return the {@link ValueExpressionEvaluator}.
418+
* @since 4.4.0
419+
*/
420+
protected ValueExpressionEvaluator getExpressionEvaluatorFor(MongoParameterAccessor accessor) {
421+
return new ValueExpressionDelegateValueExpressionEvaluator(valueExpressionDelegate, (ValueExpression expression) ->
422+
valueEvaluationContextProvider.getEvaluationContext(accessor.getValues(), expression.getExpressionDependencies()));
379423
}
380424

381425
/**
@@ -424,4 +468,5 @@ protected CodecRegistry getCodecRegistry() {
424468
* @since 2.0.4
425469
*/
426470
protected abstract boolean isLimiting();
471+
427472
}

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

+103-16
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,15 @@
2626
import org.reactivestreams.Publisher;
2727

2828
import org.springframework.core.convert.converter.Converter;
29+
import org.springframework.core.env.StandardEnvironment;
30+
import org.springframework.data.expression.ReactiveValueEvaluationContextProvider;
31+
import org.springframework.data.expression.ValueEvaluationContext;
32+
import org.springframework.data.expression.ValueEvaluationContextProvider;
33+
import org.springframework.data.expression.ValueExpression;
34+
import org.springframework.data.expression.ValueExpressionParser;
2935
import org.springframework.data.mapping.model.EntityInstantiators;
3036
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
37+
import org.springframework.data.mapping.model.ValueExpressionEvaluator;
3138
import org.springframework.data.mongodb.core.MongoOperations;
3239
import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithProjection;
3340
import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithQuery;
@@ -48,18 +55,22 @@
4855
import org.springframework.data.mongodb.util.json.ParameterBindingContext;
4956
import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec;
5057
import org.springframework.data.repository.query.ParameterAccessor;
58+
import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
5159
import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
5260
import org.springframework.data.repository.query.RepositoryQuery;
5361
import org.springframework.data.repository.query.ResultProcessor;
62+
import org.springframework.data.repository.query.ValueExpressionDelegate;
5463
import org.springframework.data.spel.ExpressionDependencies;
5564
import org.springframework.data.util.TypeInformation;
5665
import org.springframework.expression.ExpressionParser;
66+
import org.springframework.expression.spel.standard.SpelExpressionParser;
5767
import org.springframework.lang.Nullable;
5868
import org.springframework.util.Assert;
5969
import org.springframework.util.ObjectUtils;
6070
import org.springframework.util.StringUtils;
6171

6272
import com.mongodb.MongoClientSettings;
73+
import reactor.util.function.Tuple2;
6374

6475
/**
6576
* Base class for reactive {@link RepositoryQuery} implementations for MongoDB.
@@ -76,8 +87,8 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
7687
private final EntityInstantiators instantiators;
7788
private final FindWithProjection<?> findOperationWithProjection;
7889
private final ReactiveUpdate<?> updateOps;
79-
private final ExpressionParser expressionParser;
80-
private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider;
90+
private final ValueExpressionDelegate valueExpressionDelegate;
91+
private final ReactiveValueEvaluationContextProvider valueEvaluationContextProvider;
8192

8293
/**
8394
* Creates a new {@link AbstractReactiveMongoQuery} from the given {@link MongoQueryMethod} and
@@ -87,7 +98,9 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
8798
* @param operations must not be {@literal null}.
8899
* @param expressionParser must not be {@literal null}.
89100
* @param evaluationContextProvider must not be {@literal null}.
101+
* @deprecated use the constructor version with {@link ValueExpressionDelegate}
90102
*/
103+
@Deprecated(since = "4.4.0")
91104
public AbstractReactiveMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations operations,
92105
ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
93106

@@ -99,20 +112,57 @@ public AbstractReactiveMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongo
99112
this.method = method;
100113
this.operations = operations;
101114
this.instantiators = new EntityInstantiators();
102-
this.expressionParser = expressionParser;
103-
this.evaluationContextProvider = evaluationContextProvider;
115+
this.valueExpressionDelegate = new ValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), evaluationContextProvider.getEvaluationContextProvider()), ValueExpressionParser.create(() -> expressionParser));
104116

105117
MongoEntityMetadata<?> metadata = method.getEntityInformation();
106118
Class<?> type = metadata.getCollectionEntity().getType();
107119

108120
this.findOperationWithProjection = operations.query(type);
109121
this.updateOps = operations.update(type);
122+
ValueEvaluationContextProvider valueContextProvider = valueExpressionDelegate.createValueContextProvider(
123+
method.getParameters());
124+
Assert.isInstanceOf(ReactiveValueEvaluationContextProvider.class, valueContextProvider, "ValueEvaluationContextProvider must be reactive");
125+
this.valueEvaluationContextProvider = (ReactiveValueEvaluationContextProvider) valueContextProvider;
110126
}
111127

128+
/**
129+
* Creates a new {@link AbstractReactiveMongoQuery} from the given {@link MongoQueryMethod} and
130+
* {@link MongoOperations}.
131+
*
132+
* @param method must not be {@literal null}.
133+
* @param operations must not be {@literal null}.
134+
* @param delegate must not be {@literal null}.
135+
* @since 4.4.0
136+
*/
137+
public AbstractReactiveMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations operations,
138+
ValueExpressionDelegate delegate) {
139+
140+
Assert.notNull(method, "MongoQueryMethod must not be null");
141+
Assert.notNull(operations, "ReactiveMongoOperations must not be null");
142+
Assert.notNull(delegate, "ValueExpressionDelegate must not be null");
143+
144+
this.method = method;
145+
this.operations = operations;
146+
this.instantiators = new EntityInstantiators();
147+
this.valueExpressionDelegate = delegate;
148+
149+
MongoEntityMetadata<?> metadata = method.getEntityInformation();
150+
Class<?> type = metadata.getCollectionEntity().getType();
151+
152+
this.findOperationWithProjection = operations.query(type);
153+
this.updateOps = operations.update(type);
154+
ValueEvaluationContextProvider valueContextProvider = valueExpressionDelegate.createValueContextProvider(
155+
method.getParameters());
156+
Assert.isInstanceOf(ReactiveValueEvaluationContextProvider.class, valueContextProvider, "ValueEvaluationContextProvider must be reactive");
157+
this.valueEvaluationContextProvider = (ReactiveValueEvaluationContextProvider) valueContextProvider;
158+
}
159+
160+
@Override
112161
public MongoQueryMethod getQueryMethod() {
113162
return method;
114163
}
115164

165+
@Override
116166
public Publisher<Object> execute(Object[] parameters) {
117167

118168
return method.hasReactiveWrapperParameter() ? executeDeferred(parameters)
@@ -269,7 +319,7 @@ Query applyAnnotatedDefaultSortIfPresent(Query query) {
269319
Query applyAnnotatedCollationIfPresent(Query query, ConvertingParameterAccessor accessor) {
270320

271321
return QueryUtils.applyCollation(query, method.hasAnnotatedCollation() ? method.getAnnotatedCollation() : null,
272-
accessor, getQueryMethod().getParameters(), expressionParser, evaluationContextProvider);
322+
accessor, getValueExpressionEvaluator(accessor));
273323
}
274324

275325
/**
@@ -381,19 +431,19 @@ private Mono<AggregationOperation> computePipelineStage(String source, MongoPara
381431
bsonString -> AbstractReactiveMongoQuery.this.decode(evaluator, bsonString, accessor, codec)));
382432
}
383433

384-
private Mono<SpELExpressionEvaluator> expressionEvaluator(String source, MongoParameterAccessor accessor,
385-
ParameterBindingDocumentCodec codec) {
434+
private Mono<Tuple2<ValueExpressionEvaluator, ParameterBindingDocumentCodec>> expressionEvaluator(String source,
435+
MongoParameterAccessor accessor, ParameterBindingDocumentCodec codec) {
386436

387437
ExpressionDependencies dependencies = codec.captureExpressionDependencies(source, accessor::getBindableValue,
388-
expressionParser);
389-
return getSpelEvaluatorFor(dependencies, accessor);
438+
valueExpressionDelegate.getValueExpressionParser());
439+
return getValueExpressionEvaluatorLater(dependencies, accessor).zipWith(Mono.just(codec));
390440
}
391441

392-
private Document decode(SpELExpressionEvaluator expressionEvaluator, String source, MongoParameterAccessor accessor,
442+
private Document decode(Tuple2<ValueExpressionEvaluator, ParameterBindingDocumentCodec> expressionEvaluator, String source, MongoParameterAccessor accessor,
393443
ParameterBindingDocumentCodec codec) {
394444

395445
ParameterBindingContext bindingContext = new ParameterBindingContext(accessor::getBindableValue,
396-
expressionEvaluator);
446+
expressionEvaluator.getT1());
397447
return codec.decode(source, bindingContext);
398448
}
399449

@@ -415,17 +465,54 @@ protected Mono<ParameterBindingDocumentCodec> getParameterBindingCodec() {
415465
* @param accessor must not be {@literal null}.
416466
* @return a {@link Mono} emitting the {@link SpELExpressionEvaluator} when ready.
417467
* @since 3.4
468+
* @deprecated since 4.4.0, use
469+
* {@link #getValueExpressionEvaluatorLater(ExpressionDependencies, MongoParameterAccessor)} instead
418470
*/
471+
@Deprecated(since = "4.4.0")
419472
protected Mono<SpELExpressionEvaluator> getSpelEvaluatorFor(ExpressionDependencies dependencies,
420473
MongoParameterAccessor accessor) {
421-
422-
return evaluationContextProvider
423-
.getEvaluationContextLater(getQueryMethod().getParameters(), accessor.getValues(), dependencies)
424-
.map(evaluationContext -> (SpELExpressionEvaluator) new DefaultSpELExpressionEvaluator(expressionParser,
425-
evaluationContext))
474+
return valueEvaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies)
475+
.map(evaluationContext -> (SpELExpressionEvaluator) new DefaultSpELExpressionEvaluator(
476+
new SpelExpressionParser(), evaluationContext.getEvaluationContext()))
426477
.defaultIfEmpty(DefaultSpELExpressionEvaluator.unsupported());
427478
}
428479

480+
/**
481+
* Obtain a {@link ValueExpressionEvaluator} suitable to evaluate expressions.
482+
*
483+
* @param accessor must not be {@literal null}.
484+
* @since 4.3
485+
*/
486+
ValueExpressionEvaluator getValueExpressionEvaluator(MongoParameterAccessor accessor) {
487+
488+
return new ValueExpressionEvaluator() {
489+
490+
@Override
491+
public <T> T evaluate(String expressionString) {
492+
ValueExpression expression = valueExpressionDelegate.parse(expressionString);
493+
ValueEvaluationContext evaluationContext = valueEvaluationContextProvider.getEvaluationContext(accessor.getValues(),
494+
expression.getExpressionDependencies());
495+
return (T) expression.evaluate(evaluationContext);
496+
}
497+
};
498+
}
499+
500+
/**
501+
* Obtain a {@link Mono publisher} emitting the {@link ValueExpressionEvaluator} suitable to evaluate expressions
502+
* backed by the given dependencies.
503+
*
504+
* @param dependencies must not be {@literal null}.
505+
* @param accessor must not be {@literal null}.
506+
* @return a {@link Mono} emitting the {@link ValueExpressionEvaluator} when ready.
507+
* @since 4.3
508+
*/
509+
protected Mono<ValueExpressionEvaluator> getValueExpressionEvaluatorLater(ExpressionDependencies dependencies,
510+
MongoParameterAccessor accessor) {
511+
512+
return valueEvaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies)
513+
.map(evaluationContext -> new ValueExpressionDelegateValueExpressionEvaluator(valueExpressionDelegate, valueExpression -> evaluationContext));
514+
}
515+
429516
/**
430517
* @return a {@link Mono} emitting the {@link CodecRegistry} when ready.
431518
* @since 2.4

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

+4-8
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

0 commit comments

Comments
 (0)