Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Value Expressions for Repository Query methods #4683

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.3.0-SNAPSHOT</version>
<version>4.3.0-GH-4677-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand All @@ -26,7 +26,7 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>3.3.0-SNAPSHOT</springdata.commons>
<springdata.commons>3.3.0-GH-3049-SNAPSHOT</springdata.commons>
<mongo>5.0.0</mongo>
<mongo.reactivestreams>${mongo}</mongo.reactivestreams>
<jmh.version>1.19</jmh.version>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.3.0-SNAPSHOT</version>
<version>4.3.0-GH-4677-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.3.0-SNAPSHOT</version>
<version>4.3.0-GH-4677-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.3.0-SNAPSHOT</version>
<version>4.3.0-GH-4677-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@

import org.bson.Document;
import org.bson.codecs.configuration.CodecRegistry;

import org.springframework.data.expression.ValueEvaluationContext;
import org.springframework.data.expression.ValueExpression;
import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mapping.model.ValueExpressionEvaluator;
import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind;
import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery;
import org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind;
Expand All @@ -41,13 +46,14 @@
import org.springframework.data.mongodb.util.json.ParameterBindingContext;
import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ValueExpressionSupportHolder;
import org.springframework.data.spel.ExpressionDependencies;
import org.springframework.data.util.Lazy;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
Expand All @@ -70,8 +76,8 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
private final MongoOperations operations;
private final ExecutableFind<?> executableFind;
private final ExecutableUpdate<?> executableUpdate;
private final ExpressionParser expressionParser;
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
private final ValueExpressionParser expressionParser;
private final QueryMethodValueEvaluationContextProvider evaluationContextProvider;
private final Lazy<ParameterBindingDocumentCodec> codec = Lazy
.of(() -> new ParameterBindingDocumentCodec(getCodecRegistry()));

Expand All @@ -80,16 +86,14 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
*
* @param method must not be {@literal null}.
* @param operations must not be {@literal null}.
* @param expressionParser must not be {@literal null}.
* @param evaluationContextProvider must not be {@literal null}.
* @param expressionSupportHolder must not be {@literal null}.
*/
public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations, ExpressionParser expressionParser,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations,
ValueExpressionSupportHolder expressionSupportHolder) {

Assert.notNull(operations, "MongoOperations must not be null");
Assert.notNull(method, "MongoQueryMethod must not be null");
Assert.notNull(expressionParser, "SpelExpressionParser must not be null");
Assert.notNull(evaluationContextProvider, "QueryMethodEvaluationContextProvider must not be null");
Assert.notNull(expressionSupportHolder, "ValueExpressionSupportHolder must not be null");

this.method = method;
this.operations = operations;
Expand All @@ -99,10 +103,11 @@ public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations, E

this.executableFind = operations.query(type);
this.executableUpdate = operations.update(type);
this.expressionParser = expressionParser;
this.evaluationContextProvider = evaluationContextProvider;
this.expressionParser = expressionSupportHolder;
this.evaluationContextProvider = expressionSupportHolder.createValueContextProvider(method.getParameters());
}

@Override
public MongoQueryMethod getQueryMethod() {
return method;
}
Expand Down Expand Up @@ -155,7 +160,7 @@ protected Object doExecute(MongoQueryMethod method, ResultProcessor processor, C
* @since 4.2
*/
private Query applyAnnotatedReadPreferenceIfPresent(Query query) {

if (!method.hasAnnotatedReadPreference()) {
return query;
}
Expand Down Expand Up @@ -242,7 +247,7 @@ Query applyAnnotatedDefaultSortIfPresent(Query query) {
Query applyAnnotatedCollationIfPresent(Query query, ConvertingParameterAccessor accessor) {

return QueryUtils.applyCollation(query, method.hasAnnotatedCollation() ? method.getAnnotatedCollation() : null,
accessor, getQueryMethod().getParameters(), expressionParser, evaluationContextProvider);
accessor, getExpressionEvaluatorFor(accessor));
}

/**
Expand Down Expand Up @@ -344,10 +349,7 @@ private Document bindParameters(String source, ConvertingParameterAccessor acces
*/
protected ParameterBindingContext prepareBindingContext(String source, ConvertingParameterAccessor accessor) {

ExpressionDependencies dependencies = getParameterBindingCodec().captureExpressionDependencies(source,
accessor::getBindableValue, expressionParser);

SpELExpressionEvaluator evaluator = getSpELExpressionEvaluatorFor(dependencies, accessor);
ValueExpressionEvaluator evaluator = getExpressionEvaluatorFor(accessor);
return new ParameterBindingContext(accessor::getBindableValue, evaluator);
}

Expand All @@ -372,8 +374,31 @@ protected ParameterBindingDocumentCodec getParameterBindingCodec() {
protected SpELExpressionEvaluator getSpELExpressionEvaluatorFor(ExpressionDependencies dependencies,
ConvertingParameterAccessor accessor) {

return new DefaultSpELExpressionEvaluator(expressionParser, evaluationContextProvider
.getEvaluationContext(getQueryMethod().getParameters(), accessor.getValues(), dependencies));
return new DefaultSpELExpressionEvaluator(new SpelExpressionParser(),
evaluationContextProvider.getEvaluationContext(accessor.getValues(), dependencies).getEvaluationContext());
}

/**
* Obtain a {@link ValueExpressionEvaluator} suitable to evaluate expressions.
*
* @param accessor must not be {@literal null}.
* @return the {@link ValueExpressionEvaluator}.
* @since 4.3
*/
protected ValueExpressionEvaluator getExpressionEvaluatorFor(MongoParameterAccessor accessor) {

return new ValueExpressionEvaluator() {

@Override
public <T> T evaluate(String expressionString) {

ValueExpression expression = expressionParser.parse(expressionString);
ValueEvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(accessor.getValues(),
expression.getExpressionDependencies());

return (T) expression.evaluate(evaluationContext);
}
};
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@
import org.bson.Document;
import org.bson.codecs.configuration.CodecRegistry;
import org.reactivestreams.Publisher;

import org.springframework.core.convert.converter.Converter;
import org.springframework.data.expression.ValueExpression;
import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mapping.model.ValueExpressionEvaluator;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithProjection;
import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithQuery;
Expand All @@ -48,12 +51,13 @@
import org.springframework.data.mongodb.util.json.ParameterBindingContext;
import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.ReactiveQueryMethodValueEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ValueExpressionSupportHolder;
import org.springframework.data.spel.ExpressionDependencies;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
Expand All @@ -76,31 +80,30 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
private final EntityInstantiators instantiators;
private final FindWithProjection<?> findOperationWithProjection;
private final ReactiveUpdate<?> updateOps;
private final ExpressionParser expressionParser;
private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider;
private final ValueExpressionSupportHolder expressionSupportHolder;
private final ReactiveQueryMethodValueEvaluationContextProvider evaluationContextProvider;

/**
* Creates a new {@link AbstractReactiveMongoQuery} from the given {@link MongoQueryMethod} and
* {@link MongoOperations}.
*
* @param method must not be {@literal null}.
* @param operations must not be {@literal null}.
* @param expressionParser must not be {@literal null}.
* @param evaluationContextProvider must not be {@literal null}.
* @param expressionSupportHolder must not be {@literal null}.
*/
public AbstractReactiveMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations operations,
ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
ValueExpressionSupportHolder expressionSupportHolder) {

Assert.notNull(method, "MongoQueryMethod must not be null");
Assert.notNull(operations, "ReactiveMongoOperations must not be null");
Assert.notNull(expressionParser, "SpelExpressionParser must not be null");
Assert.notNull(evaluationContextProvider, "ReactiveEvaluationContextExtension must not be null");
Assert.notNull(expressionSupportHolder, "ValueExpressionSupportHolder must not be null");

this.method = method;
this.operations = operations;
this.instantiators = new EntityInstantiators();
this.expressionParser = expressionParser;
this.evaluationContextProvider = evaluationContextProvider;
this.expressionSupportHolder = expressionSupportHolder;
this.evaluationContextProvider = (ReactiveQueryMethodValueEvaluationContextProvider) expressionSupportHolder
.createValueContextProvider(method.getParameters());

MongoEntityMetadata<?> metadata = method.getEntityInformation();
Class<?> type = metadata.getCollectionEntity().getType();
Expand All @@ -109,10 +112,12 @@ public AbstractReactiveMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongo
this.updateOps = operations.update(type);
}

@Override
public MongoQueryMethod getQueryMethod() {
return method;
}

@Override
public Publisher<Object> execute(Object[] parameters) {

return method.hasReactiveWrapperParameter() ? executeDeferred(parameters)
Expand Down Expand Up @@ -231,7 +236,6 @@ private boolean isTailable(MongoQueryMethod method) {
return method.getTailableAnnotation() != null;
}


Query applyQueryMetaAttributesWhenPresent(Query query) {

if (method.hasQueryMetaAttributes()) {
Expand Down Expand Up @@ -270,7 +274,7 @@ Query applyAnnotatedDefaultSortIfPresent(Query query) {
Query applyAnnotatedCollationIfPresent(Query query, ConvertingParameterAccessor accessor) {

return QueryUtils.applyCollation(query, method.hasAnnotatedCollation() ? method.getAnnotatedCollation() : null,
accessor, getQueryMethod().getParameters(), expressionParser, evaluationContextProvider);
accessor, getValueExpressionEvaluator(accessor));
}

/**
Expand All @@ -290,7 +294,8 @@ Query applyHintIfPresent(Query query) {
}

/**
* If present apply the {@link com.mongodb.ReadPreference} from the {@link org.springframework.data.mongodb.repository.ReadPreference} annotation.
* If present apply the {@link com.mongodb.ReadPreference} from the
* {@link org.springframework.data.mongodb.repository.ReadPreference} annotation.
*
* @param query must not be {@literal null}.
* @return never {@literal null}.
Expand Down Expand Up @@ -382,15 +387,15 @@ private Mono<AggregationOperation> computePipelineStage(String source, MongoPara
});
}

private Mono<Tuple2<SpELExpressionEvaluator, ParameterBindingDocumentCodec>> expressionEvaluator(String source,
private Mono<Tuple2<ValueExpressionEvaluator, ParameterBindingDocumentCodec>> expressionEvaluator(String source,
MongoParameterAccessor accessor, ParameterBindingDocumentCodec codec) {

ExpressionDependencies dependencies = codec.captureExpressionDependencies(source, accessor::getBindableValue,
expressionParser);
return getSpelEvaluatorFor(dependencies, accessor).zipWith(Mono.just(codec));
expressionSupportHolder.getValueExpressionParser());
return getValueExpressionEvaluatorLater(dependencies, accessor).zipWith(Mono.just(codec));
}

private Document decode(SpELExpressionEvaluator expressionEvaluator, String source, MongoParameterAccessor accessor,
private Document decode(ValueExpressionEvaluator expressionEvaluator, String source, MongoParameterAccessor accessor,
ParameterBindingDocumentCodec codec) {

ParameterBindingContext bindingContext = new ParameterBindingContext(accessor::getBindableValue,
Expand All @@ -416,17 +421,66 @@ protected Mono<ParameterBindingDocumentCodec> getParameterBindingCodec() {
* @param accessor must not be {@literal null}.
* @return a {@link Mono} emitting the {@link SpELExpressionEvaluator} when ready.
* @since 3.4
* @deprecated since 4.3, use
* {@link #getValueExpressionEvaluatorLater(ExpressionDependencies, MongoParameterAccessor)} instead
*/
@Deprecated(since = "4.3")
protected Mono<SpELExpressionEvaluator> getSpelEvaluatorFor(ExpressionDependencies dependencies,
MongoParameterAccessor accessor) {

return evaluationContextProvider
.getEvaluationContextLater(getQueryMethod().getParameters(), accessor.getValues(), dependencies)
.map(evaluationContext -> (SpELExpressionEvaluator) new DefaultSpELExpressionEvaluator(expressionParser,
evaluationContext))
return evaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies)
.map(evaluationContext -> (SpELExpressionEvaluator) new DefaultSpELExpressionEvaluator(
new SpelExpressionParser(), evaluationContext.getEvaluationContext()))
.defaultIfEmpty(DefaultSpELExpressionEvaluator.unsupported());
}

/**
* Obtain a {@link ValueExpressionEvaluator} suitable to evaluate expressions.
*
* @param accessor must not be {@literal null}.
* @since 4.3
*/
ValueExpressionEvaluator getValueExpressionEvaluator(MongoParameterAccessor accessor) {

return new ValueExpressionEvaluator() {

@Override
public <T> T evaluate(String expressionString) {

ValueExpression expression = expressionSupportHolder.parse(expressionString);
return (T) expression.evaluate(evaluationContextProvider.getEvaluationContext(accessor.getValues(),
expression.getExpressionDependencies()));
}
};
}

/**
* Obtain a {@link Mono publisher} emitting the {@link ValueExpressionEvaluator} suitable to evaluate expressions
* backed by the given dependencies.
*
* @param dependencies must not be {@literal null}.
* @param accessor must not be {@literal null}.
* @return a {@link Mono} emitting the {@link ValueExpressionEvaluator} when ready.
* @since 4.3
*/
protected Mono<ValueExpressionEvaluator> getValueExpressionEvaluatorLater(ExpressionDependencies dependencies,
MongoParameterAccessor accessor) {

return evaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies)
.map(evaluationContext -> {

return new ValueExpressionEvaluator() {
@Override
public <T> T evaluate(String expressionString) {

ValueExpression expression = expressionSupportHolder.parse(expressionString);

return (T) expression.evaluate(evaluationContext);
}
};
});
}

/**
* @return a {@link Mono} emitting the {@link CodecRegistry} when ready.
* @since 2.4
Expand Down
Loading
Loading