Skip to content

Commit

Permalink
Add support for Value Expressions for Repository Query methods.
Browse files Browse the repository at this point in the history
Original pull request #4683
Closes #4677
  • Loading branch information
marcingrzejszczak committed Sep 23, 2024
1 parent 414ef64 commit e219fc9
Show file tree
Hide file tree
Showing 14 changed files with 285 additions and 197 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
import org.bson.Document;
import org.bson.codecs.configuration.CodecRegistry;

import org.springframework.core.env.StandardEnvironment;
import org.springframework.data.expression.ValueEvaluationContext;
import org.springframework.data.expression.ValueEvaluationContextProvider;
import org.springframework.data.expression.ValueExpression;
import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
Expand All @@ -46,13 +48,15 @@
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.QueryMethodValueEvaluationContextProvider;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
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.repository.query.ValueExpressionDelegate;
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;
Expand All @@ -76,24 +80,28 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
private final MongoOperations operations;
private final ExecutableFind<?> executableFind;
private final ExecutableUpdate<?> executableUpdate;
private final ValueExpressionParser expressionParser;
private final QueryMethodValueEvaluationContextProvider evaluationContextProvider;
private final Lazy<ParameterBindingDocumentCodec> codec = Lazy
.of(() -> new ParameterBindingDocumentCodec(getCodecRegistry()));
private final ValueExpressionDelegate valueExpressionDelegate;
private final ValueEvaluationContextProvider valueEvaluationContextProvider;

/**
* Creates a new {@link AbstractMongoQuery} from the given {@link MongoQueryMethod} and {@link MongoOperations}.
*
* @param method must not be {@literal null}.
* @param operations must not be {@literal null}.
* @param expressionSupportHolder must not be {@literal null}.
* @param expressionParser must not be {@literal null}.
* @param evaluationContextProvider must not be {@literal null}.
* @deprecated use the constructor version with {@link ValueExpressionDelegate}
*/
public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations,
ValueExpressionSupportHolder expressionSupportHolder) {
@Deprecated(since = "4.4.0")
public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations, ExpressionParser expressionParser,
QueryMethodEvaluationContextProvider evaluationContextProvider) {

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

this.method = method;
this.operations = operations;
Expand All @@ -103,8 +111,32 @@ public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations,

this.executableFind = operations.query(type);
this.executableUpdate = operations.update(type);
this.expressionParser = expressionSupportHolder;
this.evaluationContextProvider = expressionSupportHolder.createValueContextProvider(method.getParameters());
this.valueExpressionDelegate = new ValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), evaluationContextProvider.getEvaluationContextProvider()), ValueExpressionParser.create(() -> expressionParser));
this.valueEvaluationContextProvider = valueExpressionDelegate.createValueContextProvider(method.getParameters());
}

/**
* Creates a new {@link AbstractMongoQuery} from the given {@link MongoQueryMethod} and {@link MongoOperations}.
*
* @param method must not be {@literal null}.
* @param operations must not be {@literal null}.
* @param delegate must not be {@literal null}
*/
public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations, ValueExpressionDelegate delegate) {

Assert.notNull(operations, "MongoOperations must not be null");
Assert.notNull(method, "MongoQueryMethod must not be null");

this.method = method;
this.operations = operations;

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

this.executableFind = operations.query(type);
this.executableUpdate = operations.update(type);
this.valueExpressionDelegate = delegate;
this.valueEvaluationContextProvider = delegate.createValueContextProvider(method.getParameters());
}

@Override
Expand Down Expand Up @@ -375,8 +407,7 @@ protected ParameterBindingDocumentCodec getParameterBindingCodec() {
protected SpELExpressionEvaluator getSpELExpressionEvaluatorFor(ExpressionDependencies dependencies,
ConvertingParameterAccessor accessor) {

return new DefaultSpELExpressionEvaluator(new SpelExpressionParser(),
evaluationContextProvider.getEvaluationContext(accessor.getValues(), dependencies).getEvaluationContext());
return new DefaultSpELExpressionEvaluator(new SpelExpressionParser(), valueEvaluationContextProvider.getEvaluationContext(accessor.getValues(), dependencies).getEvaluationContext());
}

/**
Expand All @@ -393,11 +424,12 @@ protected ValueExpressionEvaluator getExpressionEvaluatorFor(MongoParameterAcces
@Override
public <T> T evaluate(String expressionString) {

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

return (T) expression.evaluate(evaluationContext);

return (T) expression.evaluate(evaluationContext);
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@
import org.reactivestreams.Publisher;

import org.springframework.core.convert.converter.Converter;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.data.expression.ReactiveValueEvaluationContextProvider;
import org.springframework.data.expression.ValueEvaluationContext;
import org.springframework.data.expression.ValueEvaluationContextProvider;
import org.springframework.data.expression.ValueExpression;
import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mapping.model.ValueExpressionEvaluator;
Expand All @@ -50,12 +55,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.ReactiveQueryMethodValueEvaluationContextProvider;
import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
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.repository.query.ValueExpressionDelegate;
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;
Expand All @@ -80,36 +87,72 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
private final EntityInstantiators instantiators;
private final FindWithProjection<?> findOperationWithProjection;
private final ReactiveUpdate<?> updateOps;
private final ValueExpressionSupportHolder expressionSupportHolder;
private final ReactiveQueryMethodValueEvaluationContextProvider evaluationContextProvider;
private final ValueExpressionDelegate valueExpressionDelegate;
private final ReactiveValueEvaluationContextProvider valueEvaluationContextProvider;

/**
* 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 expressionSupportHolder must not be {@literal null}.
* @param expressionParser must not be {@literal null}.
* @param evaluationContextProvider must not be {@literal null}.
* @deprecated use the constructor version with {@link ValueExpressionDelegate}
*/
@Deprecated(since = "4.4.0")
public AbstractReactiveMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations operations,
ValueExpressionSupportHolder expressionSupportHolder) {
ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {

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

this.method = method;
this.operations = operations;
this.instantiators = new EntityInstantiators();
this.expressionSupportHolder = expressionSupportHolder;
this.evaluationContextProvider = (ReactiveQueryMethodValueEvaluationContextProvider) expressionSupportHolder
.createValueContextProvider(method.getParameters());
this.valueExpressionDelegate = new ValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), evaluationContextProvider.getEvaluationContextProvider()), ValueExpressionParser.create(() -> expressionParser));

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

this.findOperationWithProjection = operations.query(type);
this.updateOps = operations.update(type);
ValueEvaluationContextProvider valueContextProvider = valueExpressionDelegate.createValueContextProvider(
method.getParameters());
Assert.isInstanceOf(ReactiveValueEvaluationContextProvider.class, valueContextProvider, "ValueEvaluationContextProvider must be reactive");
this.valueEvaluationContextProvider = (ReactiveValueEvaluationContextProvider) valueContextProvider;
}
/**
* 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 delegate must not be {@literal null}.
*/
public AbstractReactiveMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations operations,
ValueExpressionDelegate delegate) {

Assert.notNull(method, "MongoQueryMethod must not be null");
Assert.notNull(operations, "ReactiveMongoOperations must not be null");
Assert.notNull(delegate, "ValueExpressionDelegate must not be null");

this.method = method;
this.operations = operations;
this.instantiators = new EntityInstantiators();
this.valueExpressionDelegate = delegate;

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

this.findOperationWithProjection = operations.query(type);
this.updateOps = operations.update(type);
ValueEvaluationContextProvider valueContextProvider = valueExpressionDelegate.createValueContextProvider(
method.getParameters());
Assert.isInstanceOf(ReactiveValueEvaluationContextProvider.class, valueContextProvider, "ValueEvaluationContextProvider must be reactive");
this.valueEvaluationContextProvider = (ReactiveValueEvaluationContextProvider) valueContextProvider;
}

@Override
Expand Down Expand Up @@ -390,7 +433,7 @@ private Mono<Tuple2<ValueExpressionEvaluator, ParameterBindingDocumentCodec>> ex
MongoParameterAccessor accessor, ParameterBindingDocumentCodec codec) {

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

Expand Down Expand Up @@ -426,8 +469,7 @@ protected Mono<ParameterBindingDocumentCodec> getParameterBindingCodec() {
@Deprecated(since = "4.3")
protected Mono<SpELExpressionEvaluator> getSpelEvaluatorFor(ExpressionDependencies dependencies,
MongoParameterAccessor accessor) {

return evaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies)
return valueEvaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies)
.map(evaluationContext -> (SpELExpressionEvaluator) new DefaultSpELExpressionEvaluator(
new SpelExpressionParser(), evaluationContext.getEvaluationContext()))
.defaultIfEmpty(DefaultSpELExpressionEvaluator.unsupported());
Expand All @@ -445,10 +487,10 @@ ValueExpressionEvaluator getValueExpressionEvaluator(MongoParameterAccessor acce

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

ValueExpression expression = expressionSupportHolder.parse(expressionString);
return (T) expression.evaluate(evaluationContextProvider.getEvaluationContext(accessor.getValues(),
expression.getExpressionDependencies()));
ValueExpression expression = valueExpressionDelegate.parse(expressionString);
ValueEvaluationContext evaluationContext = valueEvaluationContextProvider.getEvaluationContext(accessor.getValues(),
expression.getExpressionDependencies());
return (T) expression.evaluate(evaluationContext);
}
};
}
Expand All @@ -465,19 +507,19 @@ public <T> T evaluate(String expressionString) {
protected Mono<ValueExpressionEvaluator> getValueExpressionEvaluatorLater(ExpressionDependencies dependencies,
MongoParameterAccessor accessor) {

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

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

ValueExpression expression = expressionSupportHolder.parse(expressionString);
ValueExpression expression = valueExpressionDelegate.parse(expressionString);

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

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@
import org.springframework.data.mongodb.core.query.TextCriteria;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory;
import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.repository.query.ValueExpressionSupportHolder;
import org.springframework.data.repository.query.ValueExpressionDelegate;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.expression.ExpressionParser;
import org.springframework.util.StringUtils;
Expand All @@ -61,29 +61,31 @@ public class PartTreeMongoQuery extends AbstractMongoQuery {
* @param mongoOperations must not be {@literal null}.
* @param expressionParser must not be {@literal null}.
* @param evaluationContextProvider must not be {@literal null}.
* @deprecated since 4.3, use the constructors accepting {@link ValueExpressionSupportHolder} instead.
* @deprecated since 4.3, use the constructors accepting {@link QueryMethodValueEvaluationContextAccessor} instead.
*/
@Deprecated(since = "4.3")
public PartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations, ExpressionParser expressionParser,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
this(method, mongoOperations,
new ValueExpressionSupportHolder(
new QueryMethodValueEvaluationContextProviderFactory(new StandardEnvironment(), evaluationContextProvider),
ValueExpressionParser.create(() -> expressionParser)));
super(method, mongoOperations, expressionParser, evaluationContextProvider);

this.processor = method.getResultProcessor();
this.tree = new PartTree(method.getName(), processor.getReturnedType().getDomainType());
this.isGeoNearQuery = method.isGeoNearQuery();
this.context = mongoOperations.getConverter().getMappingContext();
}

/**
* Creates a new {@link PartTreeMongoQuery} from the given {@link QueryMethod} and {@link MongoTemplate}.
*
* @param method must not be {@literal null}.
* @param mongoOperations must not be {@literal null}.
* @param expressionSupportHolder must not be {@literal null}.
* @param delegate must not be {@literal null}.
* @since 4.3
*/
public PartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations,
ValueExpressionSupportHolder expressionSupportHolder) {
ValueExpressionDelegate delegate) {

super(method, mongoOperations, expressionSupportHolder);
super(method, mongoOperations, delegate);

this.processor = method.getResultProcessor();
this.tree = new PartTree(method.getName(), processor.getReturnedType().getDomainType());
Expand Down
Loading

0 comments on commit e219fc9

Please sign in to comment.