From 677633fd2320924169f22dae3d9f67f0463cd8c5 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 3 Apr 2024 09:57:08 +0200 Subject: [PATCH 1/9] Prepare issue branch. --- pom.xml | 4 ++-- spring-data-mongodb-benchmarks/pom.xml | 2 +- spring-data-mongodb-distribution/pom.xml | 2 +- spring-data-mongodb/pom.xml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 3fa54424af..78fb3035d0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 4.4.0-SNAPSHOT + 4.4.0-GH-4677-SNAPSHOT pom Spring Data MongoDB @@ -26,7 +26,7 @@ multi spring-data-mongodb - 3.4.0-SNAPSHOT + 3.4.0-GH-3049-SNAPSHOT 5.1.4 ${mongo} 1.19 diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml index a3dc49f892..ae7137cdd5 100644 --- a/spring-data-mongodb-benchmarks/pom.xml +++ b/spring-data-mongodb-benchmarks/pom.xml @@ -7,7 +7,7 @@ org.springframework.data spring-data-mongodb-parent - 4.4.0-SNAPSHOT + 4.4.0-GH-4677-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index acdc13437d..227a13850d 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -15,7 +15,7 @@ org.springframework.data spring-data-mongodb-parent - 4.4.0-SNAPSHOT + 4.4.0-GH-4677-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index fafe9c8793..801c987059 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -13,7 +13,7 @@ org.springframework.data spring-data-mongodb-parent - 4.4.0-SNAPSHOT + 4.4.0-GH-4677-SNAPSHOT ../pom.xml From e263573ad239c3a0686d8ab8e2c66593c5ad8a3f Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 3 Apr 2024 09:59:41 +0200 Subject: [PATCH 2/9] Add support for Value Expressions in Repository Query methods. --- .../repository/query/AbstractMongoQuery.java | 61 +++++++++---- .../query/AbstractReactiveMongoQuery.java | 91 ++++++++++++++----- .../repository/query/AggregationUtils.java | 12 +-- .../repository/query/CollationUtils.java | 14 +-- .../repository/query/PartTreeMongoQuery.java | 23 ++++- .../mongodb/repository/query/QueryUtils.java | 11 +-- .../query/ReactivePartTreeMongoQuery.java | 24 ++++- .../query/ReactiveStringBasedAggregation.java | 29 ++++-- .../query/ReactiveStringBasedMongoQuery.java | 44 ++++++++- .../query/StringBasedAggregation.java | 31 +++++-- .../query/StringBasedMongoQuery.java | 31 +++++-- .../support/CachingExpressionParser.java | 51 ----------- .../support/MongoRepositoryFactory.java | 26 +++--- .../ReactiveMongoRepositoryFactory.java | 26 +++--- .../data/mongodb/util/json/JsonScanner.java | 2 +- .../util/json/ParameterBindingContext.java | 46 +++++++--- .../json/ParameterBindingDocumentCodec.java | 10 +- .../util/json/ParameterBindingJsonReader.java | 15 ++- .../query/AbstractMongoQueryUnitTests.java | 11 ++- .../AbstractReactiveMongoQueryUnitTests.java | 14 ++- ...eactiveStringBasedMongoQueryUnitTests.java | 44 +++++++-- .../query/StringBasedMongoQueryUnitTests.java | 51 ++++++++++- .../ParameterBindingJsonReaderUnitTests.java | 10 +- 23 files changed, 467 insertions(+), 210 deletions(-) delete mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/CachingExpressionParser.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java index 61bfa0f7b3..a701e5c32f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java @@ -21,7 +21,11 @@ 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; @@ -42,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; @@ -71,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 codec = Lazy .of(() -> new ParameterBindingDocumentCodec(getCodecRegistry())); @@ -81,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; @@ -100,8 +103,8 @@ 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()); } public MongoQueryMethod getQueryMethod() { @@ -243,7 +246,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)); } /** @@ -346,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); } @@ -374,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 evaluate(String expressionString) { + + ValueExpression expression = expressionParser.parse(expressionString); + ValueEvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(accessor.getValues(), + expression.getExpressionDependencies()); + + return (T) expression.evaluate(evaluationContext); + } + }; } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java index e653f1b39a..d9ccc9ddfb 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java @@ -26,8 +26,10 @@ 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; @@ -48,12 +50,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; @@ -76,8 +79,8 @@ 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 @@ -85,22 +88,21 @@ public abstract class AbstractReactiveMongoQuery 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 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(); @@ -269,7 +271,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)); } /** @@ -381,15 +383,15 @@ private Mono computePipelineStage(String source, MongoPara bsonString -> AbstractReactiveMongoQuery.this.decode(evaluator, bsonString, accessor, codec))); } - private Mono expressionEvaluator(String source, MongoParameterAccessor accessor, - ParameterBindingDocumentCodec codec) { + private Mono> expressionEvaluator(String source, + MongoParameterAccessor accessor, ParameterBindingDocumentCodec codec) { ExpressionDependencies dependencies = codec.captureExpressionDependencies(source, accessor::getBindableValue, - expressionParser); - return getSpelEvaluatorFor(dependencies, accessor); + 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, @@ -419,13 +421,60 @@ protected Mono getParameterBindingCodec() { protected Mono 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 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 getValueExpressionEvaluatorLater(ExpressionDependencies dependencies, + MongoParameterAccessor accessor) { + + return evaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies) + .map(evaluationContext -> { + + return new ValueExpressionEvaluator() { + @Override + public 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 diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AggregationUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AggregationUtils.java index 8e3238429d..bef1659308 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AggregationUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AggregationUtils.java @@ -21,8 +21,10 @@ import java.util.function.LongUnaryOperator; import org.bson.Document; + import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort.Order; +import org.springframework.data.mapping.model.ValueExpressionEvaluator; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationOptions; import org.springframework.data.mongodb.core.aggregation.AggregationPipeline; @@ -31,8 +33,6 @@ import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.core.query.Meta; import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; -import org.springframework.expression.ExpressionParser; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -62,15 +62,11 @@ private AggregationUtils() {} * @param accessor must not be {@literal null}. * @return the {@link Query} having proper {@link Collation}. * @see AggregationOptions#getCollation() - * @see CollationUtils#computeCollation(String, ConvertingParameterAccessor, MongoParameters, ExpressionParser, - * QueryMethodEvaluationContextProvider) */ static AggregationOptions.Builder applyCollation(AggregationOptions.Builder builder, - @Nullable String collationExpression, ConvertingParameterAccessor accessor, MongoParameters parameters, - ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { + @Nullable String collationExpression, ConvertingParameterAccessor accessor, ValueExpressionEvaluator evaluator) { - Collation collation = CollationUtils.computeCollation(collationExpression, accessor, parameters, expressionParser, - evaluationContextProvider); + Collation collation = CollationUtils.computeCollation(collationExpression, accessor, evaluator); return collation == null ? builder : builder.collation(collation); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/CollationUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/CollationUtils.java index b60b3e6bd1..02084523fb 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/CollationUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/CollationUtils.java @@ -20,11 +20,11 @@ import java.util.regex.Pattern; import org.bson.Document; + +import org.springframework.data.mapping.model.ValueExpressionEvaluator; import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.util.json.ParameterBindingContext; import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec; -import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; -import org.springframework.expression.ExpressionParser; import org.springframework.lang.Nullable; import org.springframework.util.NumberUtils; import org.springframework.util.ObjectUtils; @@ -51,16 +51,13 @@ private CollationUtils() { * * @param collationExpression * @param accessor - * @param parameters - * @param expressionParser - * @param evaluationContextProvider + * @param expressionEvaluator * @return can be {@literal null} if neither {@link ConvertingParameterAccessor#getCollation()} nor * {@literal collationExpression} are present. */ @Nullable static Collation computeCollation(@Nullable String collationExpression, ConvertingParameterAccessor accessor, - MongoParameters parameters, ExpressionParser expressionParser, - QueryMethodEvaluationContextProvider evaluationContextProvider) { + ValueExpressionEvaluator expressionEvaluator) { if (accessor.getCollation() != null) { return accessor.getCollation(); @@ -73,8 +70,7 @@ static Collation computeCollation(@Nullable String collationExpression, Converti if (collationExpression.stripLeading().startsWith("{")) { ParameterBindingContext bindingContext = ParameterBindingContext.forExpressions(accessor::getBindableValue, - expressionParser, dependencies -> evaluationContextProvider.getEvaluationContext(parameters, - accessor.getValues(), dependencies)); + expressionEvaluator); return Collation.from(CODEC.decode(collationExpression, bindingContext)); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java index 26e0dfd89f..e8a36bed19 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java @@ -18,6 +18,8 @@ import org.bson.Document; import org.bson.json.JsonParseException; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoTemplate; @@ -28,9 +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.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.parser.PartTree; import org.springframework.expression.ExpressionParser; import org.springframework.util.StringUtils; @@ -58,10 +62,25 @@ public class PartTreeMongoQuery extends AbstractMongoQuery { * @param expressionParser must not be {@literal null}. * @param evaluationContextProvider must not be {@literal null}. */ + public PartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations, ExpressionParser expressionParser, + QueryMethodEvaluationContextProvider evaluationContextProvider) { + this(method, mongoOperations, + new ValueExpressionSupportHolder( + new QueryMethodValueEvaluationContextProviderFactory(new StandardEnvironment(), evaluationContextProvider), + ValueExpressionParser.create(() -> expressionParser))); + } + + /** + * 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}. + */ public PartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations, - ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { + ValueExpressionSupportHolder expressionSupportHolder) { - super(method, mongoOperations, expressionParser, evaluationContextProvider); + super(method, mongoOperations, expressionSupportHolder); this.processor = method.getResultProcessor(); this.tree = new PartTree(method.getName(), processor.getReturnedType().getDomainType()); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/QueryUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/QueryUtils.java index c6ad7a634f..8ace6afce3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/QueryUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/QueryUtils.java @@ -22,11 +22,11 @@ import org.apache.commons.logging.LogFactory; import org.bson.Document; +import org.springframework.aop.framework.ProxyFactory; import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; -import org.springframework.expression.ExpressionParser; +import org.springframework.data.mapping.model.ValueExpressionEvaluator; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; @@ -73,16 +73,15 @@ static Query decorateSort(Query query, Document defaultSort) { * @param query must not be {@literal null}. * @param collationExpression must not be {@literal null}. * @param accessor must not be {@literal null}. + * @param expressionEvaluator must not be {@literal null}. * @return the {@link Query} having proper {@link Collation}. * @see Query#collation(Collation) * @since 2.2 */ static Query applyCollation(Query query, @Nullable String collationExpression, ConvertingParameterAccessor accessor, - MongoParameters parameters, ExpressionParser expressionParser, - QueryMethodEvaluationContextProvider evaluationContextProvider) { + ValueExpressionEvaluator expressionEvaluator) { - Collation collation = CollationUtils.computeCollation(collationExpression, accessor, parameters, expressionParser, - evaluationContextProvider); + Collation collation = CollationUtils.computeCollation(collationExpression, accessor, expressionEvaluator); return collation == null ? query : query.collation(collation); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java index b181a9fcd9..6f3cfd5108 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java @@ -19,6 +19,9 @@ import org.bson.Document; import org.bson.json.JsonParseException; + +import org.springframework.core.env.StandardEnvironment; +import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.ReactiveMongoOperations; @@ -27,10 +30,12 @@ import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.TextCriteria; import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory; 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.ReturnedType; +import org.springframework.data.repository.query.ValueExpressionSupportHolder; import org.springframework.data.repository.query.parser.PartTree; import org.springframework.expression.ExpressionParser; import org.springframework.util.StringUtils; @@ -60,7 +65,24 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery { public ReactivePartTreeMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations, ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { - super(method, mongoOperations, expressionParser, evaluationContextProvider); + this(method, mongoOperations, + new ValueExpressionSupportHolder( + new QueryMethodValueEvaluationContextProviderFactory(new StandardEnvironment(), evaluationContextProvider), + ValueExpressionParser.create(() -> expressionParser))); + } + + /** + * Creates a new {@link ReactivePartTreeMongoQuery} 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}. + * @since 4.3 + */ + public ReactivePartTreeMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations, + ValueExpressionSupportHolder expressionSupportHolder) { + + super(method, mongoOperations, expressionSupportHolder); this.processor = method.getResultProcessor(); this.tree = new PartTree(method.getName(), processor.getReturnedType().getDomainType()); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java index 4fdbf711a8..f92a0a68cc 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java @@ -22,6 +22,9 @@ import org.bson.Document; import org.reactivestreams.Publisher; + +import org.springframework.core.env.StandardEnvironment; +import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationOperation; @@ -31,8 +34,10 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes; import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory; import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.ResultProcessor; +import org.springframework.data.repository.query.ValueExpressionSupportHolder; import org.springframework.data.util.ReflectionUtils; import org.springframework.data.util.TypeInformation; import org.springframework.expression.ExpressionParser; @@ -48,8 +53,6 @@ */ public class ReactiveStringBasedAggregation extends AbstractReactiveMongoQuery { - private final ExpressionParser expressionParser; - private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider; private final ReactiveMongoOperations reactiveMongoOperations; private final MongoConverter mongoConverter; @@ -63,12 +66,24 @@ public ReactiveStringBasedAggregation(ReactiveMongoQueryMethod method, ReactiveMongoOperations reactiveMongoOperations, ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { - super(method, reactiveMongoOperations, expressionParser, evaluationContextProvider); + this(method, reactiveMongoOperations, + new ValueExpressionSupportHolder( + new QueryMethodValueEvaluationContextProviderFactory(new StandardEnvironment(), evaluationContextProvider), + ValueExpressionParser.create(() -> expressionParser))); + } + + /** + * @param method must not be {@literal null}. + * @param reactiveMongoOperations must not be {@literal null}. + * @param expressionSupportHolder must not be {@literal null}. + */ + public ReactiveStringBasedAggregation(ReactiveMongoQueryMethod method, + ReactiveMongoOperations reactiveMongoOperations, ValueExpressionSupportHolder expressionSupportHolder) { + + super(method, reactiveMongoOperations, expressionSupportHolder); this.reactiveMongoOperations = reactiveMongoOperations; this.mongoConverter = reactiveMongoOperations.getConverter(); - this.expressionParser = expressionParser; - this.evaluationContextProvider = evaluationContextProvider; } @Override @@ -128,8 +143,8 @@ private AggregationOptions computeOptions(MongoQueryMethod method, ConvertingPar AggregationOptions.Builder builder = Aggregation.newAggregationOptions(); - AggregationUtils.applyCollation(builder, method.getAnnotatedCollation(), accessor, method.getParameters(), - expressionParser, evaluationContextProvider); + AggregationUtils.applyCollation(builder, method.getAnnotatedCollation(), accessor, + getValueExpressionEvaluator(accessor)); AggregationUtils.applyMeta(builder, method); AggregationUtils.applyHint(builder, method); AggregationUtils.applyReadPreference(builder, method); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java index d1f047edd9..77eb0a474b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java @@ -21,14 +21,18 @@ import org.apache.commons.logging.LogFactory; import org.bson.Document; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.util.json.ParameterBindingContext; import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec; +import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory; import org.springframework.data.repository.query.ReactiveExtensionAwareQueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider; +import org.springframework.data.repository.query.ValueExpressionSupportHolder; import org.springframework.data.spel.ExpressionDependencies; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -49,7 +53,7 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery { private final String query; private final String fieldSpec; - private final ExpressionParser expressionParser; + private final ValueExpressionParser expressionParser; private final boolean isCountQuery; private final boolean isExistsQuery; @@ -82,14 +86,44 @@ public ReactiveStringBasedMongoQuery(ReactiveMongoQueryMethod method, ReactiveMo public ReactiveStringBasedMongoQuery(String query, ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations, ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { + this(query, method, mongoOperations, + new ValueExpressionSupportHolder( + new QueryMethodValueEvaluationContextProviderFactory(new StandardEnvironment(), evaluationContextProvider), + ValueExpressionParser.create(() -> expressionParser))); + } + + /** + * Creates a new {@link ReactiveStringBasedMongoQuery} for the given {@link MongoQueryMethod} and + * {@link MongoOperations}. + * + * @param method must not be {@literal null}. + * @param mongoOperations must not be {@literal null}. + * @param expressionSupportHolder must not be {@literal null}. + */ + public ReactiveStringBasedMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations, + ValueExpressionSupportHolder expressionSupportHolder) { + this(method.getAnnotatedQuery(), method, mongoOperations, expressionSupportHolder); + } + + /** + * Creates a new {@link ReactiveStringBasedMongoQuery} for the given {@link String}, {@link MongoQueryMethod}, + * {@link MongoOperations}, {@link SpelExpressionParser} and + * {@link ReactiveExtensionAwareQueryMethodEvaluationContextProvider}. + * + * @param query must not be {@literal null}. + * @param method must not be {@literal null}. + * @param mongoOperations must not be {@literal null}. + * @param expressionSupportHolder must not be {@literal null}. + */ + public ReactiveStringBasedMongoQuery(String query, ReactiveMongoQueryMethod method, + ReactiveMongoOperations mongoOperations, ValueExpressionSupportHolder expressionSupportHolder) { - super(method, mongoOperations, expressionParser, evaluationContextProvider); + super(method, mongoOperations, expressionSupportHolder); Assert.notNull(query, "Query must not be null"); - Assert.notNull(expressionParser, "SpelExpressionParser must not be null"); this.query = query; - this.expressionParser = expressionParser; + this.expressionParser = expressionSupportHolder; this.fieldSpec = method.getFieldSpecification(); if (method.hasAnnotatedQuery()) { @@ -141,7 +175,7 @@ private Mono getBindingContext(String json, ConvertingP ExpressionDependencies dependencies = codec.captureExpressionDependencies(json, accessor::getBindableValue, expressionParser); - return getSpelEvaluatorFor(dependencies, accessor) + return getValueExpressionEvaluatorLater(dependencies, accessor) .map(it -> new ParameterBindingContext(accessor::getBindableValue, it)); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java index 9243ed70d5..5182af89c0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java @@ -21,8 +21,11 @@ import java.util.stream.Stream; import org.bson.Document; + +import org.springframework.core.env.StandardEnvironment; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.SliceImpl; +import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mongodb.InvalidMongoDbApiUsageException; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.aggregation.Aggregation; @@ -34,7 +37,9 @@ import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory; import org.springframework.data.repository.query.ResultProcessor; +import org.springframework.data.repository.query.ValueExpressionSupportHolder; import org.springframework.data.util.ReflectionUtils; import org.springframework.expression.ExpressionParser; import org.springframework.lang.Nullable; @@ -53,8 +58,6 @@ public class StringBasedAggregation extends AbstractMongoQuery { private final MongoOperations mongoOperations; private final MongoConverter mongoConverter; - private final ExpressionParser expressionParser; - private final QueryMethodEvaluationContextProvider evaluationContextProvider; /** * Creates a new {@link StringBasedAggregation} from the given {@link MongoQueryMethod} and {@link MongoOperations}. @@ -66,7 +69,23 @@ public class StringBasedAggregation extends AbstractMongoQuery { */ public StringBasedAggregation(MongoQueryMethod method, MongoOperations mongoOperations, ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { - super(method, mongoOperations, expressionParser, evaluationContextProvider); + this(method, mongoOperations, + new ValueExpressionSupportHolder( + new QueryMethodValueEvaluationContextProviderFactory(new StandardEnvironment(), evaluationContextProvider), + ValueExpressionParser.create(() -> expressionParser))); + } + + /** + * Creates a new {@link StringBasedAggregation} from the given {@link MongoQueryMethod} and {@link MongoOperations}. + * + * @param method must not be {@literal null}. + * @param mongoOperations must not be {@literal null}. + * @param expressionSupport must not be {@literal null}. + * @since 4.3 + */ + public StringBasedAggregation(MongoQueryMethod method, MongoOperations mongoOperations, + ValueExpressionSupportHolder expressionSupport) { + super(method, mongoOperations, expressionSupport); if (method.isPageQuery()) { throw new InvalidMongoDbApiUsageException(String.format( @@ -76,8 +95,6 @@ public StringBasedAggregation(MongoQueryMethod method, MongoOperations mongoOper this.mongoOperations = mongoOperations; this.mongoConverter = mongoOperations.getConverter(); - this.expressionParser = expressionParser; - this.evaluationContextProvider = evaluationContextProvider; } @Override @@ -177,8 +194,8 @@ private AggregationOptions computeOptions(MongoQueryMethod method, ConvertingPar AggregationOptions.Builder builder = Aggregation.newAggregationOptions(); - AggregationUtils.applyCollation(builder, method.getAnnotatedCollation(), accessor, method.getParameters(), - expressionParser, evaluationContextProvider); + AggregationUtils.applyCollation(builder, method.getAnnotatedCollation(), accessor, + getExpressionEvaluatorFor(accessor)); AggregationUtils.applyMeta(builder, method); AggregationUtils.applyHint(builder, method); AggregationUtils.applyReadPreference(builder, method); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java index 325ece597c..d8b5b56385 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java @@ -19,10 +19,14 @@ import org.apache.commons.logging.LogFactory; import org.bson.Document; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory; +import org.springframework.data.repository.query.ValueExpressionSupportHolder; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.util.Assert; @@ -58,25 +62,40 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { */ public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations, ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { - this(method.getAnnotatedQuery(), method, mongoOperations, expressionParser, evaluationContextProvider); + this(method.getAnnotatedQuery(), method, mongoOperations, + new ValueExpressionSupportHolder( + new QueryMethodValueEvaluationContextProviderFactory(new StandardEnvironment(), evaluationContextProvider), + ValueExpressionParser.create(() -> expressionParser))); + } + + /** + * Creates a new {@link StringBasedMongoQuery} for the given {@link MongoQueryMethod}, {@link MongoOperations}, + * {@link ValueExpressionSupportHolder}. + * + * @param method must not be {@literal null}. + * @param mongoOperations must not be {@literal null}. + * @param expressionSupport must not be {@literal null}. + */ + public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations, + ValueExpressionSupportHolder expressionSupport) { + this(method.getAnnotatedQuery(), method, mongoOperations, expressionSupport); } /** * Creates a new {@link StringBasedMongoQuery} for the given {@link String}, {@link MongoQueryMethod}, - * {@link MongoOperations}, {@link SpelExpressionParser} and {@link QueryMethodEvaluationContextProvider}. + * {@link MongoOperations}, {@link ValueExpressionSupportHolder}. * * @param query must not be {@literal null}. * @param method must not be {@literal null}. * @param mongoOperations must not be {@literal null}. - * @param expressionParser must not be {@literal null}. + * @param expressionSupport must not be {@literal null}. */ public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperations mongoOperations, - ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { + ValueExpressionSupportHolder expressionSupport) { - super(method, mongoOperations, expressionParser, evaluationContextProvider); + super(method, mongoOperations, expressionSupport); Assert.notNull(query, "Query must not be null"); - Assert.notNull(expressionParser, "SpelExpressionParser must not be null"); this.query = query; this.fieldSpec = method.getFieldSpecification(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/CachingExpressionParser.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/CachingExpressionParser.java deleted file mode 100644 index ccbc9f8de5..0000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/CachingExpressionParser.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2020-2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.mongodb.repository.support; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.springframework.expression.Expression; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.ParseException; -import org.springframework.expression.ParserContext; - -/** - * Caching variant of {@link ExpressionParser}. This implementation does not support - * {@link #parseExpression(String, ParserContext) parsing with ParseContext}. - * - * @author Mark Paluch - * @since 3.1 - */ -class CachingExpressionParser implements ExpressionParser { - - private final ExpressionParser delegate; - private final Map cache = new ConcurrentHashMap<>(); - - CachingExpressionParser(ExpressionParser delegate) { - this.delegate = delegate; - } - - @Override - public Expression parseExpression(String expressionString) throws ParseException { - return cache.computeIfAbsent(expressionString, delegate::parseExpression); - } - - @Override - public Expression parseExpression(String expressionString, ParserContext context) throws ParseException { - throw new UnsupportedOperationException("Parsing using ParserContext is not supported"); - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java index 14bbd4af06..82d9189985 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java @@ -40,11 +40,11 @@ import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments; import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.data.repository.query.CachingValueExpressionSupportHolder; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; -import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.RepositoryQuery; -import org.springframework.expression.ExpressionParser; +import org.springframework.data.repository.query.ValueExpressionSupportHolder; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -148,8 +148,8 @@ protected Object getTargetRepository(RepositoryInformation information) { @Override protected Optional getQueryLookupStrategy(@Nullable Key key, - QueryMethodEvaluationContextProvider evaluationContextProvider) { - return Optional.of(new MongoQueryLookupStrategy(operations, evaluationContextProvider, mappingContext)); + ValueExpressionSupportHolder expressionSupport) { + return Optional.of(new MongoQueryLookupStrategy(operations, mappingContext, expressionSupport)); } public MongoEntityInformation getEntityInformation(Class domainClass) { @@ -173,17 +173,16 @@ private MongoEntityInformation getEntityInformation(Class doma private static class MongoQueryLookupStrategy implements QueryLookupStrategy { private final MongoOperations operations; - private final QueryMethodEvaluationContextProvider evaluationContextProvider; private final MappingContext, MongoPersistentProperty> mappingContext; - private final ExpressionParser expressionParser = new CachingExpressionParser(EXPRESSION_PARSER); + private final ValueExpressionSupportHolder expressionSupport; public MongoQueryLookupStrategy(MongoOperations operations, - QueryMethodEvaluationContextProvider evaluationContextProvider, - MappingContext, MongoPersistentProperty> mappingContext) { + MappingContext, MongoPersistentProperty> mappingContext, + ValueExpressionSupportHolder expressionSupport) { this.operations = operations; - this.evaluationContextProvider = evaluationContextProvider; this.mappingContext = mappingContext; + this.expressionSupport = new CachingValueExpressionSupportHolder(expressionSupport); } @Override @@ -197,14 +196,13 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, if (namedQueries.hasQuery(namedQueryName)) { String namedQuery = namedQueries.getQuery(namedQueryName); - return new StringBasedMongoQuery(namedQuery, queryMethod, operations, expressionParser, - evaluationContextProvider); + return new StringBasedMongoQuery(namedQuery, queryMethod, operations, expressionSupport); } else if (queryMethod.hasAnnotatedAggregation()) { - return new StringBasedAggregation(queryMethod, operations, expressionParser, evaluationContextProvider); + return new StringBasedAggregation(queryMethod, operations, expressionSupport); } else if (queryMethod.hasAnnotatedQuery()) { - return new StringBasedMongoQuery(queryMethod, operations, expressionParser, evaluationContextProvider); + return new StringBasedMongoQuery(queryMethod, operations, expressionSupport); } else { - return new PartTreeMongoQuery(queryMethod, operations, expressionParser, evaluationContextProvider); + return new PartTreeMongoQuery(queryMethod, operations, expressionSupport); } } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveMongoRepositoryFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveMongoRepositoryFactory.java index 0d3f829b8f..9679ec1ed9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveMongoRepositoryFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveMongoRepositoryFactory.java @@ -40,12 +40,12 @@ import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport; import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments; import org.springframework.data.repository.core.support.RepositoryFragment; +import org.springframework.data.repository.query.CachingValueExpressionSupportHolder; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; -import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.RepositoryQuery; -import org.springframework.expression.ExpressionParser; +import org.springframework.data.repository.query.ValueExpressionSupportHolder; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -135,9 +135,8 @@ protected Object getTargetRepository(RepositoryInformation information) { @Override protected Optional getQueryLookupStrategy(@Nullable Key key, - QueryMethodEvaluationContextProvider evaluationContextProvider) { - return Optional.of(new MongoQueryLookupStrategy(operations, - (ReactiveQueryMethodEvaluationContextProvider) evaluationContextProvider, mappingContext)); + ValueExpressionSupportHolder valueExpressionSupportHolder) { + return Optional.of(new MongoQueryLookupStrategy(operations, valueExpressionSupportHolder, mappingContext)); } public MongoEntityInformation getEntityInformation(Class domainClass) { @@ -163,16 +162,14 @@ private MongoEntityInformation getEntityInformation(Class doma private static class MongoQueryLookupStrategy implements QueryLookupStrategy { private final ReactiveMongoOperations operations; - private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider; private final MappingContext, MongoPersistentProperty> mappingContext; - private final ExpressionParser expressionParser = new CachingExpressionParser(EXPRESSION_PARSER); + private final ValueExpressionSupportHolder expressionSupportHolder; - MongoQueryLookupStrategy(ReactiveMongoOperations operations, - ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider, + MongoQueryLookupStrategy(ReactiveMongoOperations operations, ValueExpressionSupportHolder expressionSupportHolder, MappingContext, MongoPersistentProperty> mappingContext) { this.operations = operations; - this.evaluationContextProvider = evaluationContextProvider; + this.expressionSupportHolder = new CachingValueExpressionSupportHolder(expressionSupportHolder); this.mappingContext = mappingContext; } @@ -187,14 +184,13 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, if (namedQueries.hasQuery(namedQueryName)) { String namedQuery = namedQueries.getQuery(namedQueryName); - return new ReactiveStringBasedMongoQuery(namedQuery, queryMethod, operations, expressionParser, - evaluationContextProvider); + return new ReactiveStringBasedMongoQuery(namedQuery, queryMethod, operations, expressionSupportHolder); } else if (queryMethod.hasAnnotatedAggregation()) { - return new ReactiveStringBasedAggregation(queryMethod, operations, expressionParser, evaluationContextProvider); + return new ReactiveStringBasedAggregation(queryMethod, operations, expressionSupportHolder); } else if (queryMethod.hasAnnotatedQuery()) { - return new ReactiveStringBasedMongoQuery(queryMethod, operations, expressionParser, evaluationContextProvider); + return new ReactiveStringBasedMongoQuery(queryMethod, operations, expressionSupportHolder); } else { - return new ReactivePartTreeMongoQuery(queryMethod, operations, expressionParser, evaluationContextProvider); + return new ReactivePartTreeMongoQuery(queryMethod, operations, expressionSupportHolder); } } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/JsonScanner.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/JsonScanner.java index d5a41582c4..37f3a0a60a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/JsonScanner.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/JsonScanner.java @@ -222,7 +222,7 @@ private JsonToken scanBindString() { while (c == '$' || c == '_' || Character.isLetterOrDigit(c) || c == '#' || c == '{' || c == '[' || (isExpression && isExpressionAllowedChar(c))) { - if (charCount == 0 && c == '#') { + if (charCount == 0 && (c == '#' || c == '$')) { isExpression = true; } else if (isExpression) { if (c == '{') { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java index 1ad1f31a37..61034244fc 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java @@ -19,13 +19,14 @@ import java.util.function.Function; import java.util.function.Supplier; -import org.springframework.data.mapping.model.SpELExpressionEvaluator; import org.springframework.data.mapping.model.ValueExpressionEvaluator; import org.springframework.data.spel.ExpressionDependencies; import org.springframework.data.util.Lazy; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; +import org.springframework.expression.ParseException; +import org.springframework.expression.ParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.lang.Nullable; @@ -62,7 +63,7 @@ public ParameterBindingContext(ValueProvider valueProvider, SpelExpressionParser */ public ParameterBindingContext(ValueProvider valueProvider, ExpressionParser expressionParser, Supplier evaluationContext) { - this(valueProvider, new EvaluationContextExpressionEvaluator(valueProvider, expressionParser) { + this(valueProvider, new EvaluationContextExpressionEvaluator(valueProvider, unwrap(expressionParser)) { @Override public EvaluationContext getEvaluationContext(String expressionString) { return evaluationContext.get(); @@ -70,15 +71,24 @@ public EvaluationContext getEvaluationContext(String expressionString) { }); } - /** - * @param valueProvider - * @param expressionEvaluator - * @since 3.1 - * @deprecated since 4.3, use {@link #ParameterBindingContext(ValueProvider, ValueExpressionEvaluator)} instead. - */ - @Deprecated(since = "4.3") - public ParameterBindingContext(ValueProvider valueProvider, SpELExpressionEvaluator expressionEvaluator) { - this(valueProvider, (ValueExpressionEvaluator) expressionEvaluator); + private static ExpressionParser unwrap(ExpressionParser expressionParser) { + return new ExpressionParser() { + @Override + public Expression parseExpression(String expressionString) throws ParseException { + return expressionParser.parseExpression(unwrap(expressionString)); + } + + @Override + public Expression parseExpression(String expressionString, ParserContext context) throws ParseException { + return expressionParser.parseExpression(unwrap(expressionString), context); + } + }; + } + + private static String unwrap(String expressionString) { + return expressionString.startsWith("#{") && expressionString.endsWith("}") + ? expressionString.substring(2, expressionString.length() - 1).trim() + : expressionString; } /** @@ -117,6 +127,20 @@ public EvaluationContext getEvaluationContext(String expressionString) { }); } + /** + * Create a new {@link ParameterBindingContext} that is capable of expression parsing. + * + * @param valueProvider + * @param expressionEvaluator + * @return + * @since 4.3 + */ + public static ParameterBindingContext forExpressions(ValueProvider valueProvider, + ValueExpressionEvaluator expressionEvaluator) { + + return new ParameterBindingContext(valueProvider, expressionEvaluator); + } + @Nullable public Object bindableValueForIndex(int index) { return valueProvider.getBindableValue(index); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingDocumentCodec.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingDocumentCodec.java index f3cffaa798..61e0791f95 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingDocumentCodec.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingDocumentCodec.java @@ -41,11 +41,11 @@ import org.bson.codecs.configuration.CodecRegistry; import org.bson.json.JsonParseException; +import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mapping.model.ValueExpressionEvaluator; import org.springframework.data.mongodb.core.mapping.FieldName; import org.springframework.data.spel.EvaluationContextProvider; import org.springframework.data.spel.ExpressionDependencies; -import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.lang.Nullable; import org.springframework.util.NumberUtils; @@ -194,7 +194,7 @@ public Document decode(@Nullable String json, ParameterBindingContext bindingCon * @since 3.1 */ public ExpressionDependencies captureExpressionDependencies(@Nullable String json, ValueProvider valueProvider, - ExpressionParser expressionParser) { + ValueExpressionParser expressionParser) { if (!StringUtils.hasText(json)) { return ExpressionDependencies.none(); @@ -389,10 +389,10 @@ static class DependencyCapturingExpressionEvaluator implements ValueExpressionEv private static final Object PLACEHOLDER = new Object(); - private final ExpressionParser expressionParser; + private final ValueExpressionParser expressionParser; private final List dependencies = new ArrayList<>(); - DependencyCapturingExpressionEvaluator(ExpressionParser expressionParser) { + DependencyCapturingExpressionEvaluator(ValueExpressionParser expressionParser) { this.expressionParser = expressionParser; } @@ -400,7 +400,7 @@ static class DependencyCapturingExpressionEvaluator implements ValueExpressionEv @Override public T evaluate(String expression) { - dependencies.add(ExpressionDependencies.discover(expressionParser.parseExpression(expression))); + dependencies.add(expressionParser.parse(expression).getExpressionDependencies()); return (T) PLACEHOLDER; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java index 4ef61835f1..2d1d939242 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java @@ -64,9 +64,9 @@ */ public class ParameterBindingJsonReader extends AbstractBsonReader { - private static final Pattern ENTIRE_QUERY_BINDING_PATTERN = Pattern.compile("^\\?(\\d+)$|^[\\?:]#\\{.*\\}$"); + private static final Pattern ENTIRE_QUERY_BINDING_PATTERN = Pattern.compile("^\\?(\\d+)$|^[\\?:][#$]\\{.*\\}$"); private static final Pattern PARAMETER_BINDING_PATTERN = Pattern.compile("\\?(\\d+)"); - private static final Pattern EXPRESSION_BINDING_PATTERN = Pattern.compile("[\\?:]#\\{.*\\}"); + private static final Pattern EXPRESSION_BINDING_PATTERN = Pattern.compile("[\\?:][#$]\\{.*\\}"); private static final Pattern SPEL_PARAMETER_BINDING_PATTERN = Pattern.compile("('\\?(\\d+)'|\\?(\\d+))"); private final ParameterBindingContext bindingContext; @@ -374,6 +374,7 @@ private BindableValue bindableValueFor(JsonToken token) { String binding = regexMatcher.group(); String expression = binding.substring(3, binding.length() - 1); + String expressionString = binding.substring(1); Matcher inSpelMatcher = SPEL_PARAMETER_BINDING_PATTERN.matcher(expression); // ?0 '?0' Map innerSpelVariables = new HashMap<>(); @@ -385,6 +386,7 @@ private BindableValue bindableValueFor(JsonToken token) { Object value = getBindableValueForIndex(index); String varName = "__QVar" + innerSpelVariables.size(); expression = expression.replace(group, "#" + varName); + expressionString = expressionString.replace(group, "#" + varName); if(group.startsWith("'")) { // retain the string semantic innerSpelVariables.put(varName, nullSafeToString(value)); } else { @@ -392,7 +394,7 @@ private BindableValue bindableValueFor(JsonToken token) { } } - Object value = evaluateExpression(expression, innerSpelVariables); + Object value = evaluateExpression(expressionString, innerSpelVariables); bindableValue.setValue(value); bindableValue.setType(bsonTypeForValue(value)); return bindableValue; @@ -421,6 +423,7 @@ private BindableValue bindableValueFor(JsonToken token) { String binding = regexMatcher.group(); String expression = binding.substring(3, binding.length() - 1); + String expressionString = binding.substring(1); Matcher inSpelMatcher = SPEL_PARAMETER_BINDING_PATTERN.matcher(expression); Map innerSpelVariables = new HashMap<>(); @@ -432,6 +435,7 @@ private BindableValue bindableValueFor(JsonToken token) { Object value = getBindableValueForIndex(index); String varName = "__QVar" + innerSpelVariables.size(); expression = expression.replace(group, "#" + varName); + expressionString = expressionString.replace(group, "#" + varName); if(group.startsWith("'")) { // retain the string semantic innerSpelVariables.put(varName, nullSafeToString(value)); } else { @@ -439,7 +443,8 @@ private BindableValue bindableValueFor(JsonToken token) { } } - computedValue = computedValue.replace(binding, nullSafeToString(evaluateExpression(expression, innerSpelVariables))); + computedValue = computedValue.replace(binding, + nullSafeToString(evaluateExpression(expressionString, innerSpelVariables))); bindableValue.setValue(computedValue); bindableValue.setType(BsonType.STRING); @@ -1081,7 +1086,7 @@ private long visitISODateTimeConstructor() { } verifyToken(JsonTokenType.RIGHT_PAREN); - + String dateTimeString = token.getValue(String.class); try { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java index c35a328a1f..b8646cf88b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java @@ -36,6 +36,8 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; + +import org.springframework.core.env.StandardEnvironment; import org.springframework.data.domain.Limit; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -43,6 +45,7 @@ import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind; import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery; @@ -72,6 +75,8 @@ import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory; +import org.springframework.data.repository.query.ValueExpressionSupportHolder; import org.springframework.expression.spel.standard.SpelExpressionParser; import com.mongodb.MongoClientSettings; @@ -566,7 +571,11 @@ private static class MongoQueryFake extends AbstractMongoQuery { private boolean isLimitingQuery; MongoQueryFake(MongoQueryMethod method, MongoOperations operations) { - super(method, operations, new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT); + super(method, operations, + new ValueExpressionSupportHolder( + new QueryMethodValueEvaluationContextProviderFactory(new StandardEnvironment(), + QueryMethodEvaluationContextProvider.DEFAULT), + ValueExpressionParser.create(SpelExpressionParser::new))); } @Override diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQueryUnitTests.java index c524d5edf8..6f587ce9cb 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQueryUnitTests.java @@ -36,6 +36,9 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; + +import org.springframework.core.env.StandardEnvironment; +import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mongodb.core.Person; import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithQuery; import org.springframework.data.mongodb.core.ReactiveFindOperation.ReactiveFind; @@ -58,7 +61,9 @@ import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; +import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory; import org.springframework.data.repository.query.ReactiveExtensionAwareQueryMethodEvaluationContextProvider; +import org.springframework.data.repository.query.ValueExpressionSupportHolder; import org.springframework.expression.spel.standard.SpelExpressionParser; import com.mongodb.MongoClientSettings; @@ -197,7 +202,7 @@ void shouldApplyDynamicAnnotatedCollationWithMultiplePlaceholders() { createQueryForMethod("findWithCollationUsingPlaceholdersInDocumentByFirstName", String.class, String.class, int.class) // - .executeBlocking(new Object[] { "dalinar", "en_US", 2 }); + .executeBlocking(new Object[] { "dalinar", "en_US", 2 }); ArgumentCaptor captor = ArgumentCaptor.forClass(Query.class); verify(withQueryMock).matching(captor.capture()); @@ -299,8 +304,11 @@ private static class ReactiveMongoQueryFake extends AbstractReactiveMongoQuery { private boolean isLimitingQuery; ReactiveMongoQueryFake(ReactiveMongoQueryMethod method, ReactiveMongoOperations operations) { - super(method, operations, new SpelExpressionParser(), - ReactiveExtensionAwareQueryMethodEvaluationContextProvider.DEFAULT); + super(method, operations, + new ValueExpressionSupportHolder( + new QueryMethodValueEvaluationContextProviderFactory(new StandardEnvironment(), + ReactiveExtensionAwareQueryMethodEvaluationContextProvider.DEFAULT), + ValueExpressionParser.create(SpelExpressionParser::new))); } @Override diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java index 7859a426f8..cc3909de3f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java @@ -19,14 +19,13 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.lang.reflect.Method; import java.util.Base64; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import org.bson.Document; @@ -35,7 +34,12 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mongodb.core.ReactiveFindOperation.ReactiveFind; import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.data.mongodb.core.convert.DbRefResolver; @@ -51,8 +55,11 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory; import org.springframework.data.repository.query.ReactiveExtensionAwareQueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider; +import org.springframework.data.repository.query.ValueExpressionSupportHolder; import org.springframework.data.spel.spi.EvaluationContextExtension; import org.springframework.data.spel.spi.ReactiveEvaluationContextExtension; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -67,7 +74,8 @@ @MockitoSettings(strictness = Strictness.LENIENT) public class ReactiveStringBasedMongoQueryUnitTests { - SpelExpressionParser PARSER = new SpelExpressionParser(); + ValueExpressionParser PARSER = ValueExpressionParser.create(SpelExpressionParser::new); + StandardEnvironment environment = new StandardEnvironment(); @Mock ReactiveMongoOperations operations; @Mock DbRefResolver factory; @@ -75,13 +83,18 @@ public class ReactiveStringBasedMongoQueryUnitTests { MongoConverter converter; + Map properties = new HashMap<>(); + MapPropertySource propertySource = new MapPropertySource("mock", properties); + @BeforeEach public void setUp() { - when(operations.query(any())).thenReturn(reactiveFind); - when(operations.execute(any())).thenReturn(Flux.empty()); + environment.getPropertySources().addFirst(propertySource); this.converter = new MappingMongoConverter(factory, new MongoMappingContext()); + + when(operations.query(any())).thenReturn(reactiveFind); + when(operations.execute(any())).thenReturn(Flux.empty()); } @Test // DATAMONGO-1444 @@ -188,6 +201,19 @@ public void shouldSupportExpressionsInCustomQueries() throws Exception { assertThat(query.getQueryObject()).isEqualTo(reference.getQueryObject()); } + @Test // GH-3050 + public void shouldSupportPropertiesInCustomQueries() throws Exception { + + properties.put("foo", "bar"); + ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter); + ReactiveStringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithProperty"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor).block(); + org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'bar'}"); + + assertThat(query.getQueryObject()).isEqualTo(reference.getQueryObject()); + } + @Test // DATAMONGO-1444 public void shouldSupportExpressionsInCustomQueriesWithNestedObject() throws Exception { @@ -257,8 +283,9 @@ private ReactiveStringBasedMongoQuery createQueryForMethod( ReactiveMongoQueryMethod queryMethod = new ReactiveMongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class), factory, converter.getMappingContext()); - return new ReactiveStringBasedMongoQuery(queryMethod, operations, PARSER, - contextProvider); + return new ReactiveStringBasedMongoQuery(queryMethod, operations, new ValueExpressionSupportHolder( + new QueryMethodValueEvaluationContextProviderFactory(environment, QueryMethodEvaluationContextProvider.DEFAULT), + PARSER)); } private interface SampleRepository extends Repository { @@ -290,6 +317,9 @@ private interface SampleRepository extends Repository { @Query("{'lastname': ?#{[0]} }") Flux findByQueryWithExpression(String param0); + @Query("{'lastname': ?${foo} }") + Flux findByQueryWithProperty(); + @Query("{'id':?#{ [0] ? { $exists :true} : [1] }}") Flux findByQueryWithExpressionAndNestedObject(boolean param0, String param1); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java index 590b948bcb..086537465e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java @@ -25,6 +25,7 @@ import java.util.Arrays; import java.util.Base64; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -39,9 +40,12 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; + +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mongodb.core.DbCallback; import org.springframework.data.mongodb.core.DocumentTestUtils; import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind; @@ -60,6 +64,8 @@ import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory; +import org.springframework.data.repository.query.ValueExpressionSupportHolder; import org.springframework.expression.spel.standard.SpelExpressionParser; import com.mongodb.MongoClientSettings; @@ -77,7 +83,8 @@ @MockitoSettings(strictness = Strictness.LENIENT) public class StringBasedMongoQueryUnitTests { - SpelExpressionParser PARSER = new SpelExpressionParser(); + ValueExpressionParser PARSER = ValueExpressionParser.create(SpelExpressionParser::new); + StandardEnvironment environment = new StandardEnvironment(); @Mock MongoOperations operations; @Mock ExecutableFind findOperation; @@ -85,10 +92,14 @@ public class StringBasedMongoQueryUnitTests { MongoConverter converter; + Map properties = new HashMap<>(); + MapPropertySource propertySource = new MapPropertySource("mock", properties); + @BeforeEach public void setUp() { this.converter = new MappingMongoConverter(factory, new MongoMappingContext()); + environment.getPropertySources().addFirst(propertySource); doReturn(findOperation).when(operations).query(any()); doReturn(MongoClientSettings.getDefaultCodecRegistry()).when(operations).execute(any()); @@ -293,6 +304,32 @@ public void shouldSupportExpressionsInCustomQueries() { assertThat(query.getQueryObject()).isEqualTo(reference.getQueryObject()); } + @Test // GH-3050 + public void shouldSupportExpressionsAndPropertiesInCustomQueries() { + + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews"); + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpressionAndProperty", String.class); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + org.springframework.data.mongodb.core.query.Query reference = new BasicQuery( + "{'lastname' : 'Matthews', 'firstname' : 'some-default'}"); + + assertThat(query.getQueryObject()).isEqualTo(reference.getQueryObject()); + } + + @Test // GH-3050 + public void shouldSupportPropertiesInCustomQueries() { + + properties.put("foo", "bar"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter); + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithProperty"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'bar'}"); + + assertThat(query.getQueryObject()).isEqualTo(reference.getQueryObject()); + } + @Test // DATAMONGO-1244 public void shouldSupportExpressionsInCustomQueriesWithNestedObject() { @@ -707,7 +744,9 @@ private StringBasedMongoQuery createQueryForMethod(String name, Class... para ProjectionFactory factory = new SpelAwareProxyProjectionFactory(); MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class), factory, converter.getMappingContext()); - return new StringBasedMongoQuery(queryMethod, operations, PARSER, QueryMethodEvaluationContextProvider.DEFAULT); + return new StringBasedMongoQuery(queryMethod, operations, + new ValueExpressionSupportHolder(new QueryMethodValueEvaluationContextProviderFactory(environment, + QueryMethodEvaluationContextProvider.DEFAULT), PARSER)); } catch (Exception e) { throw new IllegalArgumentException(e.getMessage(), e); @@ -776,6 +815,12 @@ private interface SampleRepository extends Repository { @Query("{'lastname': ?#{[0]} }") List findByQueryWithExpression(String param0); + @Query("{'lastname': ?#{[0]}, 'firstname': ?${absent-property:some-default} }") + List findByQueryWithExpressionAndProperty(String param0); + + @Query("{'lastname': ?${foo} }") + List findByQueryWithProperty(); + @Query("{'id':?#{ [0] ? { $exists :true} : [1] }}") List findByQueryWithExpressionAndNestedObject(boolean param0, String param1); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java index e07a8c4008..3f033db6dc 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java @@ -29,6 +29,8 @@ import org.bson.Document; import org.bson.codecs.DecoderContext; import org.junit.jupiter.api.Test; + +import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.spel.EvaluationContextProvider; import org.springframework.data.spel.ExpressionDependencies; import org.springframework.expression.EvaluationContext; @@ -309,7 +311,8 @@ void discoversNoDependenciesInExpression() { String json = "{ $and : [?#{ [0] == null ? { '$where' : 'true' } : { 'v1' : { '$in' : {[0]} } } }]}"; ExpressionDependencies expressionDependencies = new ParameterBindingDocumentCodec() - .captureExpressionDependencies(json, it -> new Object(), new SpelExpressionParser()); + .captureExpressionDependencies(json, it -> new Object(), + ValueExpressionParser.create(SpelExpressionParser::new)); assertThat(expressionDependencies).isEqualTo(ExpressionDependencies.none()); } @@ -320,7 +323,8 @@ void discoversCorrectlyDependenciesInExpression() { String json = "{ hello: ?#{hasRole('foo')} }"; ExpressionDependencies expressionDependencies = new ParameterBindingDocumentCodec() - .captureExpressionDependencies(json, it -> new Object(), new SpelExpressionParser()); + .captureExpressionDependencies(json, it -> new Object(), + ValueExpressionParser.create(SpelExpressionParser::new)); assertThat(expressionDependencies).isNotEmpty(); assertThat(expressionDependencies.get()).hasSize(1); @@ -406,7 +410,7 @@ public void capturingExpressionDependenciesShouldNotThrowParseErrorForSpelOnlyJs String json = "?#{ true ? { 'name': #name } : { 'name' : #name + 'trouble' } }"; new ParameterBindingDocumentCodec().captureExpressionDependencies(json, (index) -> args[index], - new SpelExpressionParser()); + ValueExpressionParser.create(SpelExpressionParser::new)); } @Test // GH-3871, GH-4089 From ef5f4b4beee409e16d1261dcd8f18ad93fa2ffcc Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 3 Apr 2024 10:15:22 +0200 Subject: [PATCH 3/9] Polishing. Deprecations, missing Override annotations, test fix. --- .../repository/query/AbstractMongoQuery.java | 1 + .../query/AbstractReactiveMongoQuery.java | 6 +++++- .../repository/query/PartTreeMongoQuery.java | 3 +++ .../query/ReactivePartTreeMongoQuery.java | 2 ++ .../query/ReactiveStringBasedAggregation.java | 3 +++ .../query/ReactiveStringBasedMongoQuery.java | 4 ++++ .../query/StringBasedAggregation.java | 2 ++ .../query/StringBasedMongoQuery.java | 4 ++++ .../support/MongoRepositoryFactory.java | 10 ++++------ .../ReactiveMongoRepositoryFactory.java | 18 +++++++++--------- ...ReactiveStringBasedMongoQueryUnitTests.java | 4 +--- 11 files changed, 38 insertions(+), 19 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java index a701e5c32f..eed13f7cfd 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java @@ -107,6 +107,7 @@ public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations, this.evaluationContextProvider = expressionSupportHolder.createValueContextProvider(method.getParameters()); } + @Override public MongoQueryMethod getQueryMethod() { return method; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java index d9ccc9ddfb..6bc22df36b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java @@ -111,10 +111,12 @@ public AbstractReactiveMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongo this.updateOps = operations.update(type); } + @Override public MongoQueryMethod getQueryMethod() { return method; } + @Override public Publisher execute(Object[] parameters) { return method.hasReactiveWrapperParameter() ? executeDeferred(parameters) @@ -417,7 +419,10 @@ protected Mono 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 getSpelEvaluatorFor(ExpressionDependencies dependencies, MongoParameterAccessor accessor) { @@ -471,7 +476,6 @@ public T evaluate(String expressionString) { return (T) expression.evaluate(evaluationContext); } }; - }); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java index e8a36bed19..66537dd310 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java @@ -61,7 +61,9 @@ 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") public PartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations, ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { this(method, mongoOperations, @@ -76,6 +78,7 @@ public PartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperatio * @param method must not be {@literal null}. * @param mongoOperations must not be {@literal null}. * @param expressionSupportHolder must not be {@literal null}. + * @since 4.3 */ public PartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations, ValueExpressionSupportHolder expressionSupportHolder) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java index 6f3cfd5108..7f947c7414 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java @@ -61,7 +61,9 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery { * @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") public ReactivePartTreeMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations, ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java index f92a0a68cc..9680f89c27 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java @@ -61,7 +61,9 @@ public class ReactiveStringBasedAggregation extends AbstractReactiveMongoQuery { * @param reactiveMongoOperations 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") public ReactiveStringBasedAggregation(ReactiveMongoQueryMethod method, ReactiveMongoOperations reactiveMongoOperations, ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { @@ -76,6 +78,7 @@ public ReactiveStringBasedAggregation(ReactiveMongoQueryMethod method, * @param method must not be {@literal null}. * @param reactiveMongoOperations must not be {@literal null}. * @param expressionSupportHolder must not be {@literal null}. + * @since 4.3 */ public ReactiveStringBasedAggregation(ReactiveMongoQueryMethod method, ReactiveMongoOperations reactiveMongoOperations, ValueExpressionSupportHolder expressionSupportHolder) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java index 77eb0a474b..ad154048b0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java @@ -67,7 +67,9 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery { * @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") public ReactiveStringBasedMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations, ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { this(method.getAnnotatedQuery(), method, mongoOperations, expressionParser, evaluationContextProvider); @@ -82,7 +84,9 @@ public ReactiveStringBasedMongoQuery(ReactiveMongoQueryMethod method, ReactiveMo * @param method must not be {@literal null}. * @param mongoOperations must not be {@literal null}. * @param expressionParser must not be {@literal null}. + * @deprecated since 4.3, use the constructors accepting {@link ValueExpressionSupportHolder} instead. */ + @Deprecated(since = "4.3") public ReactiveStringBasedMongoQuery(String query, ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations, ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java index 5182af89c0..73898248e3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java @@ -66,7 +66,9 @@ public class StringBasedAggregation 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") public StringBasedAggregation(MongoQueryMethod method, MongoOperations mongoOperations, ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { this(method, mongoOperations, diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java index d8b5b56385..848fc8b696 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java @@ -59,7 +59,9 @@ public class StringBasedMongoQuery 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") public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations, ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { this(method.getAnnotatedQuery(), method, mongoOperations, @@ -75,6 +77,7 @@ public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOpera * @param method must not be {@literal null}. * @param mongoOperations must not be {@literal null}. * @param expressionSupport must not be {@literal null}. + * @since 4.3 */ public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations, ValueExpressionSupportHolder expressionSupport) { @@ -89,6 +92,7 @@ public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOpera * @param method must not be {@literal null}. * @param mongoOperations must not be {@literal null}. * @param expressionSupport must not be {@literal null}. + * @since 4.3 */ public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperations mongoOperations, ValueExpressionSupportHolder expressionSupport) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java index 82d9189985..8243df45e2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java @@ -170,13 +170,11 @@ private MongoEntityInformation getEntityInformation(Class doma * @author Oliver Gierke * @author Thomas Darimont */ - private static class MongoQueryLookupStrategy implements QueryLookupStrategy { + private record MongoQueryLookupStrategy(MongoOperations operations, + MappingContext, MongoPersistentProperty> mappingContext, + ValueExpressionSupportHolder expressionSupport) implements QueryLookupStrategy { - private final MongoOperations operations; - private final MappingContext, MongoPersistentProperty> mappingContext; - private final ValueExpressionSupportHolder expressionSupport; - - public MongoQueryLookupStrategy(MongoOperations operations, + private MongoQueryLookupStrategy(MongoOperations operations, MappingContext, MongoPersistentProperty> mappingContext, ValueExpressionSupportHolder expressionSupport) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveMongoRepositoryFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveMongoRepositoryFactory.java index 9679ec1ed9..de940f7228 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveMongoRepositoryFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveMongoRepositoryFactory.java @@ -136,9 +136,10 @@ protected Object getTargetRepository(RepositoryInformation information) { @Override protected Optional getQueryLookupStrategy(@Nullable Key key, ValueExpressionSupportHolder valueExpressionSupportHolder) { - return Optional.of(new MongoQueryLookupStrategy(operations, valueExpressionSupportHolder, mappingContext)); + return Optional.of(new MongoQueryLookupStrategy(operations, mappingContext, valueExpressionSupportHolder)); } + @Override public MongoEntityInformation getEntityInformation(Class domainClass) { return getEntityInformation(domainClass, null); } @@ -159,18 +160,17 @@ private MongoEntityInformation getEntityInformation(Class doma * @author Mark Paluch * @author Christoph Strobl */ - private static class MongoQueryLookupStrategy implements QueryLookupStrategy { - - private final ReactiveMongoOperations operations; - private final MappingContext, MongoPersistentProperty> mappingContext; - private final ValueExpressionSupportHolder expressionSupportHolder; + private record MongoQueryLookupStrategy(ReactiveMongoOperations operations, + MappingContext, MongoPersistentProperty> mappingContext, + ValueExpressionSupportHolder expressionSupportHolder) implements QueryLookupStrategy { - MongoQueryLookupStrategy(ReactiveMongoOperations operations, ValueExpressionSupportHolder expressionSupportHolder, - MappingContext, MongoPersistentProperty> mappingContext) { + private MongoQueryLookupStrategy(ReactiveMongoOperations operations, + MappingContext, MongoPersistentProperty> mappingContext, + ValueExpressionSupportHolder expressionSupportHolder) { this.operations = operations; - this.expressionSupportHolder = new CachingValueExpressionSupportHolder(expressionSupportHolder); this.mappingContext = mappingContext; + this.expressionSupportHolder = new CachingValueExpressionSupportHolder(expressionSupportHolder); } @Override diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java index cc3909de3f..95fdea4db2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java @@ -55,7 +55,6 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; -import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory; import org.springframework.data.repository.query.ReactiveExtensionAwareQueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider; @@ -284,8 +283,7 @@ private ReactiveStringBasedMongoQuery createQueryForMethod( new DefaultRepositoryMetadata(SampleRepository.class), factory, converter.getMappingContext()); return new ReactiveStringBasedMongoQuery(queryMethod, operations, new ValueExpressionSupportHolder( - new QueryMethodValueEvaluationContextProviderFactory(environment, QueryMethodEvaluationContextProvider.DEFAULT), - PARSER)); + new QueryMethodValueEvaluationContextProviderFactory(environment, contextProvider), PARSER)); } private interface SampleRepository extends Repository { From 414ef64783501a4c7a1ac6f6e98705fc82522ab2 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 17 Sep 2024 13:10:43 +0200 Subject: [PATCH 4/9] Fixed compilation issues --- .../mongodb/repository/query/AbstractReactiveMongoQuery.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java index 6bc22df36b..4b9dc087b8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java @@ -63,6 +63,7 @@ import org.springframework.util.StringUtils; import com.mongodb.MongoClientSettings; +import reactor.util.function.Tuple2; /** * Base class for reactive {@link RepositoryQuery} implementations for MongoDB. @@ -393,11 +394,11 @@ private Mono> ex return getValueExpressionEvaluatorLater(dependencies, accessor).zipWith(Mono.just(codec)); } - private Document decode(ValueExpressionEvaluator expressionEvaluator, String source, MongoParameterAccessor accessor, + private Document decode(Tuple2 expressionEvaluator, String source, MongoParameterAccessor accessor, ParameterBindingDocumentCodec codec) { ParameterBindingContext bindingContext = new ParameterBindingContext(accessor::getBindableValue, - expressionEvaluator); + expressionEvaluator.getT1()); return codec.decode(source, bindingContext); } From e219fc9938e463977d919c9640bfb82b00bc0dd0 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 20 Sep 2024 15:50:28 +0200 Subject: [PATCH 5/9] Add support for Value Expressions for Repository Query methods. Original pull request #4683 Closes #4677 --- .../repository/query/AbstractMongoQuery.java | 64 +++++++++---- .../query/AbstractReactiveMongoQuery.java | 96 +++++++++++++------ .../repository/query/PartTreeMongoQuery.java | 22 +++-- .../query/ReactivePartTreeMongoQuery.java | 23 +++-- .../query/ReactiveStringBasedAggregation.java | 21 ++-- .../query/ReactiveStringBasedMongoQuery.java | 59 ++++++++---- .../query/StringBasedAggregation.java | 28 +++--- .../query/StringBasedMongoQuery.java | 46 ++++++--- .../support/MongoRepositoryFactory.java | 23 ++--- .../ReactiveMongoRepositoryFactory.java | 37 +++---- .../query/AbstractMongoQueryUnitTests.java | 13 +-- .../AbstractReactiveMongoQueryUnitTests.java | 14 +-- ...eactiveStringBasedMongoQueryUnitTests.java | 23 ++--- .../query/StringBasedMongoQueryUnitTests.java | 13 ++- 14 files changed, 285 insertions(+), 197 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java index eed13f7cfd..618f158fe5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java @@ -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; @@ -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; @@ -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 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; @@ -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 @@ -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()); } /** @@ -393,11 +424,12 @@ protected ValueExpressionEvaluator getExpressionEvaluatorFor(MongoParameterAcces @Override public 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); } }; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java index 4b9dc087b8..d37a46bb0b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java @@ -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; @@ -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; @@ -80,8 +87,8 @@ 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 @@ -89,27 +96,63 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery { * * @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 @@ -390,7 +433,7 @@ private Mono> ex MongoParameterAccessor accessor, ParameterBindingDocumentCodec codec) { ExpressionDependencies dependencies = codec.captureExpressionDependencies(source, accessor::getBindableValue, - expressionSupportHolder.getValueExpressionParser()); + valueExpressionDelegate.getValueExpressionParser()); return getValueExpressionEvaluatorLater(dependencies, accessor).zipWith(Mono.just(codec)); } @@ -426,8 +469,7 @@ protected Mono getParameterBindingCodec() { @Deprecated(since = "4.3") protected Mono 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()); @@ -445,10 +487,10 @@ ValueExpressionEvaluator getValueExpressionEvaluator(MongoParameterAccessor acce @Override public 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); } }; } @@ -465,19 +507,19 @@ public T evaluate(String expressionString) { protected Mono 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 evaluate(String expressionString) { + return new ValueExpressionEvaluator() { + @Override + public T evaluate(String expressionString) { - ValueExpression expression = expressionSupportHolder.parse(expressionString); + ValueExpression expression = valueExpressionDelegate.parse(expressionString); - return (T) expression.evaluate(evaluationContext); - } - }; - }); + return (T) expression.evaluate(evaluationContext); + } + }; + }); } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java index 66537dd310..a36c4a89d4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java @@ -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; @@ -61,15 +61,17 @@ 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(); } /** @@ -77,13 +79,13 @@ public PartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperatio * * @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()); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java index 7f947c7414..abf7ea3060 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java @@ -20,8 +20,6 @@ import org.bson.Document; import org.bson.json.JsonParseException; -import org.springframework.core.env.StandardEnvironment; -import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.ReactiveMongoOperations; @@ -30,12 +28,12 @@ import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.TextCriteria; import org.springframework.data.repository.query.QueryMethod; -import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory; +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.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; @@ -61,16 +59,17 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery { * @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 ReactivePartTreeMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations, ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { + super(method, mongoOperations, expressionParser, evaluationContextProvider); - this(method, mongoOperations, - new ValueExpressionSupportHolder( - new QueryMethodValueEvaluationContextProviderFactory(new StandardEnvironment(), evaluationContextProvider), - ValueExpressionParser.create(() -> expressionParser))); + this.processor = method.getResultProcessor(); + this.tree = new PartTree(method.getName(), processor.getReturnedType().getDomainType()); + this.isGeoNearQuery = method.isGeoNearQuery(); + this.context = mongoOperations.getConverter().getMappingContext(); } /** @@ -78,13 +77,13 @@ public ReactivePartTreeMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongo * * @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 ReactivePartTreeMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations 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()); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java index 9680f89c27..b1067015d2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java @@ -23,8 +23,6 @@ import org.bson.Document; import org.reactivestreams.Publisher; -import org.springframework.core.env.StandardEnvironment; -import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationOperation; @@ -34,10 +32,9 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes; import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory; import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider; 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.util.ReflectionUtils; import org.springframework.data.util.TypeInformation; import org.springframework.expression.ExpressionParser; @@ -61,29 +58,29 @@ public class ReactiveStringBasedAggregation extends AbstractReactiveMongoQuery { * @param reactiveMongoOperations 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 ValueExpressionDelegate} instead. */ @Deprecated(since = "4.3") public ReactiveStringBasedAggregation(ReactiveMongoQueryMethod method, ReactiveMongoOperations reactiveMongoOperations, ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { - this(method, reactiveMongoOperations, - new ValueExpressionSupportHolder( - new QueryMethodValueEvaluationContextProviderFactory(new StandardEnvironment(), evaluationContextProvider), - ValueExpressionParser.create(() -> expressionParser))); + super(method, reactiveMongoOperations, expressionParser, evaluationContextProvider); + + this.reactiveMongoOperations = reactiveMongoOperations; + this.mongoConverter = reactiveMongoOperations.getConverter(); } /** * @param method must not be {@literal null}. * @param reactiveMongoOperations must not be {@literal null}. - * @param expressionSupportHolder must not be {@literal null}. + * @param delegate must not be {@literal null}. * @since 4.3 */ public ReactiveStringBasedAggregation(ReactiveMongoQueryMethod method, - ReactiveMongoOperations reactiveMongoOperations, ValueExpressionSupportHolder expressionSupportHolder) { + ReactiveMongoOperations reactiveMongoOperations, ValueExpressionDelegate delegate) { - super(method, reactiveMongoOperations, expressionSupportHolder); + super(method, reactiveMongoOperations, delegate); this.reactiveMongoOperations = reactiveMongoOperations; this.mongoConverter = reactiveMongoOperations.getConverter(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java index ad154048b0..c1fe2409a5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java @@ -21,7 +21,6 @@ import org.apache.commons.logging.LogFactory; import org.bson.Document; -import org.springframework.core.env.StandardEnvironment; import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.ReactiveMongoOperations; @@ -29,10 +28,9 @@ import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.util.json.ParameterBindingContext; import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec; -import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory; import org.springframework.data.repository.query.ReactiveExtensionAwareQueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider; -import org.springframework.data.repository.query.ValueExpressionSupportHolder; +import org.springframework.data.repository.query.ValueExpressionDelegate; import org.springframework.data.spel.ExpressionDependencies; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -67,7 +65,7 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery { * @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 ValueExpressionDelegate} instead. */ @Deprecated(since = "4.3") public ReactiveStringBasedMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations, @@ -84,50 +82,71 @@ public ReactiveStringBasedMongoQuery(ReactiveMongoQueryMethod method, ReactiveMo * @param method must not be {@literal null}. * @param mongoOperations must not be {@literal null}. * @param expressionParser 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 ValueExpressionDelegate} instead. */ @Deprecated(since = "4.3") public ReactiveStringBasedMongoQuery(String query, ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations, ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { - this(query, method, mongoOperations, - new ValueExpressionSupportHolder( - new QueryMethodValueEvaluationContextProviderFactory(new StandardEnvironment(), evaluationContextProvider), - ValueExpressionParser.create(() -> expressionParser))); + super(method, mongoOperations, expressionParser, evaluationContextProvider); + + Assert.notNull(query, "Query must not be null"); + + this.query = query; + this.expressionParser = ValueExpressionParser.create(() -> expressionParser); + this.fieldSpec = method.getFieldSpecification(); + + if (method.hasAnnotatedQuery()) { + + org.springframework.data.mongodb.repository.Query queryAnnotation = method.getQueryAnnotation(); + + this.isCountQuery = queryAnnotation.count(); + this.isExistsQuery = queryAnnotation.exists(); + this.isDeleteQuery = queryAnnotation.delete(); + + if (hasAmbiguousProjectionFlags(this.isCountQuery, this.isExistsQuery, this.isDeleteQuery)) { + throw new IllegalArgumentException(String.format(COUNT_EXISTS_AND_DELETE, method)); + } + + } else { + + this.isCountQuery = false; + this.isExistsQuery = false; + this.isDeleteQuery = false; + } } /** - * Creates a new {@link ReactiveStringBasedMongoQuery} for the given {@link MongoQueryMethod} and - * {@link MongoOperations}. + * Creates a new {@link ReactiveStringBasedMongoQuery} for the given {@link MongoQueryMethod}, + * {@link MongoOperations} and {@link ValueExpressionDelegate}. * * @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}. */ public ReactiveStringBasedMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations, - ValueExpressionSupportHolder expressionSupportHolder) { - this(method.getAnnotatedQuery(), method, mongoOperations, expressionSupportHolder); + ValueExpressionDelegate delegate) { + this(method.getAnnotatedQuery(), method, mongoOperations, delegate); } /** * Creates a new {@link ReactiveStringBasedMongoQuery} for the given {@link String}, {@link MongoQueryMethod}, - * {@link MongoOperations}, {@link SpelExpressionParser} and - * {@link ReactiveExtensionAwareQueryMethodEvaluationContextProvider}. + * {@link MongoOperations}, {@link ValueExpressionDelegate}. * * @param query must not be {@literal null}. * @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}. */ public ReactiveStringBasedMongoQuery(String query, ReactiveMongoQueryMethod method, - ReactiveMongoOperations mongoOperations, ValueExpressionSupportHolder expressionSupportHolder) { + ReactiveMongoOperations mongoOperations, ValueExpressionDelegate delegate) { - super(method, mongoOperations, expressionSupportHolder); + super(method, mongoOperations, delegate); Assert.notNull(query, "Query must not be null"); this.query = query; - this.expressionParser = expressionSupportHolder; + this.expressionParser = delegate.getValueExpressionParser(); this.fieldSpec = method.getFieldSpecification(); if (method.hasAnnotatedQuery()) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java index 73898248e3..27d7ed0795 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java @@ -22,10 +22,8 @@ import org.bson.Document; -import org.springframework.core.env.StandardEnvironment; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.SliceImpl; -import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mongodb.InvalidMongoDbApiUsageException; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.aggregation.Aggregation; @@ -37,9 +35,9 @@ import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes; import org.springframework.data.mongodb.core.query.Query; 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.ResultProcessor; -import org.springframework.data.repository.query.ValueExpressionSupportHolder; +import org.springframework.data.repository.query.ValueExpressionDelegate; import org.springframework.data.util.ReflectionUtils; import org.springframework.expression.ExpressionParser; import org.springframework.lang.Nullable; @@ -66,15 +64,21 @@ public class StringBasedAggregation 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 StringBasedAggregation(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); + + if (method.isPageQuery()) { + throw new InvalidMongoDbApiUsageException(String.format( + "Repository aggregation method '%s' does not support '%s' return type; Please use 'Slice' or 'List' instead", + method.getName(), method.getReturnType().getType().getSimpleName())); + } + + this.mongoOperations = mongoOperations; + this.mongoConverter = mongoOperations.getConverter(); } /** @@ -82,12 +86,12 @@ public StringBasedAggregation(MongoQueryMethod method, MongoOperations mongoOper * * @param method must not be {@literal null}. * @param mongoOperations must not be {@literal null}. - * @param expressionSupport must not be {@literal null}. + * @param delegate must not be {@literal null}. * @since 4.3 */ public StringBasedAggregation(MongoQueryMethod method, MongoOperations mongoOperations, - ValueExpressionSupportHolder expressionSupport) { - super(method, mongoOperations, expressionSupport); + ValueExpressionDelegate delegate) { + super(method, mongoOperations, delegate); if (method.isPageQuery()) { throw new InvalidMongoDbApiUsageException(String.format( diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java index 848fc8b696..749d6a9cd8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java @@ -19,14 +19,12 @@ import org.apache.commons.logging.LogFactory; import org.bson.Document; -import org.springframework.core.env.StandardEnvironment; -import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; -import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory; -import org.springframework.data.repository.query.ValueExpressionSupportHolder; +import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor; +import org.springframework.data.repository.query.ValueExpressionDelegate; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.util.Assert; @@ -59,20 +57,42 @@ public class StringBasedMongoQuery 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 ValueExpressionDelegate} instead. */ @Deprecated(since = "4.3") public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations, ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { - this(method.getAnnotatedQuery(), method, mongoOperations, - new ValueExpressionSupportHolder( - new QueryMethodValueEvaluationContextProviderFactory(new StandardEnvironment(), evaluationContextProvider), - ValueExpressionParser.create(() -> expressionParser))); + super(method, mongoOperations, expressionParser, evaluationContextProvider); + + String query = method.getAnnotatedQuery(); + Assert.notNull(query, "Query must not be null"); + + this.query = query; + this.fieldSpec = method.getFieldSpecification(); + + if (method.hasAnnotatedQuery()) { + + org.springframework.data.mongodb.repository.Query queryAnnotation = method.getQueryAnnotation(); + + this.isCountQuery = queryAnnotation.count(); + this.isExistsQuery = queryAnnotation.exists(); + this.isDeleteQuery = queryAnnotation.delete(); + + if (hasAmbiguousProjectionFlags(this.isCountQuery, this.isExistsQuery, this.isDeleteQuery)) { + throw new IllegalArgumentException(String.format(COUNT_EXISTS_AND_DELETE, method)); + } + + } else { + + this.isCountQuery = false; + this.isExistsQuery = false; + this.isDeleteQuery = false; + } } /** * Creates a new {@link StringBasedMongoQuery} for the given {@link MongoQueryMethod}, {@link MongoOperations}, - * {@link ValueExpressionSupportHolder}. + * {@link ValueExpressionDelegate}. * * @param method must not be {@literal null}. * @param mongoOperations must not be {@literal null}. @@ -80,13 +100,13 @@ public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOpera * @since 4.3 */ public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations, - ValueExpressionSupportHolder expressionSupport) { + ValueExpressionDelegate expressionSupport) { this(method.getAnnotatedQuery(), method, mongoOperations, expressionSupport); } /** * Creates a new {@link StringBasedMongoQuery} for the given {@link String}, {@link MongoQueryMethod}, - * {@link MongoOperations}, {@link ValueExpressionSupportHolder}. + * {@link MongoOperations}, {@link ValueExpressionDelegate}, {@link QueryMethodValueEvaluationContextAccessor}. * * @param query must not be {@literal null}. * @param method must not be {@literal null}. @@ -95,7 +115,7 @@ public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOpera * @since 4.3 */ public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperations mongoOperations, - ValueExpressionSupportHolder expressionSupport) { + ValueExpressionDelegate expressionSupport) { super(method, mongoOperations, expressionSupport); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java index 8243df45e2..f83006ca24 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java @@ -40,12 +40,11 @@ import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments; import org.springframework.data.repository.core.support.RepositoryFactorySupport; -import org.springframework.data.repository.query.CachingValueExpressionSupportHolder; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; +import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor; import org.springframework.data.repository.query.RepositoryQuery; -import org.springframework.data.repository.query.ValueExpressionSupportHolder; -import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.data.repository.query.ValueExpressionDelegate; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -59,11 +58,10 @@ */ public class MongoRepositoryFactory extends RepositoryFactorySupport { - private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); - private final CrudMethodMetadataPostProcessor crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor(); private final MongoOperations operations; private final MappingContext, MongoPersistentProperty> mappingContext; + @Nullable private QueryMethodValueEvaluationContextAccessor accessor; /** * Creates a new {@link MongoRepositoryFactory} with the given {@link MongoOperations}. @@ -148,8 +146,8 @@ protected Object getTargetRepository(RepositoryInformation information) { @Override protected Optional getQueryLookupStrategy(@Nullable Key key, - ValueExpressionSupportHolder expressionSupport) { - return Optional.of(new MongoQueryLookupStrategy(operations, mappingContext, expressionSupport)); + ValueExpressionDelegate valueExpressionDelegate) { + return Optional.of(new MongoQueryLookupStrategy(operations, mappingContext, valueExpressionDelegate)); } public MongoEntityInformation getEntityInformation(Class domainClass) { @@ -172,16 +170,7 @@ private MongoEntityInformation getEntityInformation(Class doma */ private record MongoQueryLookupStrategy(MongoOperations operations, MappingContext, MongoPersistentProperty> mappingContext, - ValueExpressionSupportHolder expressionSupport) implements QueryLookupStrategy { - - private MongoQueryLookupStrategy(MongoOperations operations, - MappingContext, MongoPersistentProperty> mappingContext, - ValueExpressionSupportHolder expressionSupport) { - - this.operations = operations; - this.mappingContext = mappingContext; - this.expressionSupport = new CachingValueExpressionSupportHolder(expressionSupport); - } + ValueExpressionDelegate expressionSupport) implements QueryLookupStrategy { @Override public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveMongoRepositoryFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveMongoRepositoryFactory.java index de940f7228..a65740fe33 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveMongoRepositoryFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/ReactiveMongoRepositoryFactory.java @@ -21,6 +21,7 @@ import java.lang.reflect.Method; import java.util.Optional; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mongodb.core.ReactiveMongoOperations; @@ -40,13 +41,12 @@ import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport; import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments; import org.springframework.data.repository.core.support.RepositoryFragment; -import org.springframework.data.repository.query.CachingValueExpressionSupportHolder; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; +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.ValueExpressionSupportHolder; -import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.data.repository.query.ValueExpressionDelegate; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -60,11 +60,10 @@ */ public class ReactiveMongoRepositoryFactory extends ReactiveRepositoryFactorySupport { - private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser(); - private final CrudMethodMetadataPostProcessor crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor(); private final ReactiveMongoOperations operations; private final MappingContext, MongoPersistentProperty> mappingContext; + @Nullable private QueryMethodValueEvaluationContextAccessor accessor; /** * Creates a new {@link ReactiveMongoRepositoryFactory} with the given {@link ReactiveMongoOperations}. @@ -133,10 +132,9 @@ protected Object getTargetRepository(RepositoryInformation information) { return targetRepository; } - @Override - protected Optional getQueryLookupStrategy(@Nullable Key key, - ValueExpressionSupportHolder valueExpressionSupportHolder) { - return Optional.of(new MongoQueryLookupStrategy(operations, mappingContext, valueExpressionSupportHolder)); + @Override protected Optional getQueryLookupStrategy(Key key, + ValueExpressionDelegate valueExpressionDelegate) { + return Optional.of(new MongoQueryLookupStrategy(operations, mappingContext, valueExpressionDelegate)); } @Override @@ -161,17 +159,8 @@ private MongoEntityInformation getEntityInformation(Class doma * @author Christoph Strobl */ private record MongoQueryLookupStrategy(ReactiveMongoOperations operations, - MappingContext, MongoPersistentProperty> mappingContext, - ValueExpressionSupportHolder expressionSupportHolder) implements QueryLookupStrategy { - - private MongoQueryLookupStrategy(ReactiveMongoOperations operations, - MappingContext, MongoPersistentProperty> mappingContext, - ValueExpressionSupportHolder expressionSupportHolder) { - - this.operations = operations; - this.mappingContext = mappingContext; - this.expressionSupportHolder = new CachingValueExpressionSupportHolder(expressionSupportHolder); - } + MappingContext, MongoPersistentProperty> mappingContext, + ValueExpressionDelegate delegate) implements QueryLookupStrategy { @Override public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, @@ -184,13 +173,13 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, if (namedQueries.hasQuery(namedQueryName)) { String namedQuery = namedQueries.getQuery(namedQueryName); - return new ReactiveStringBasedMongoQuery(namedQuery, queryMethod, operations, expressionSupportHolder); + return new ReactiveStringBasedMongoQuery(namedQuery, queryMethod, operations, delegate); } else if (queryMethod.hasAnnotatedAggregation()) { - return new ReactiveStringBasedAggregation(queryMethod, operations, expressionSupportHolder); + return new ReactiveStringBasedAggregation(queryMethod, operations, delegate); } else if (queryMethod.hasAnnotatedQuery()) { - return new ReactiveStringBasedMongoQuery(queryMethod, operations, expressionSupportHolder); + return new ReactiveStringBasedMongoQuery(queryMethod, operations, delegate); } else { - return new ReactivePartTreeMongoQuery(queryMethod, operations, expressionSupportHolder); + return new ReactivePartTreeMongoQuery(queryMethod, operations, delegate); } } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java index b8646cf88b..79029812e2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.*; import java.lang.reflect.Method; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Optional; @@ -37,6 +38,7 @@ import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.env.StandardEnvironment; import org.springframework.data.domain.Limit; import org.springframework.data.domain.Page; @@ -74,9 +76,8 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; -import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; -import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory; -import org.springframework.data.repository.query.ValueExpressionSupportHolder; +import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor; +import org.springframework.data.repository.query.ValueExpressionDelegate; import org.springframework.expression.spel.standard.SpelExpressionParser; import com.mongodb.MongoClientSettings; @@ -572,9 +573,9 @@ private static class MongoQueryFake extends AbstractMongoQuery { MongoQueryFake(MongoQueryMethod method, MongoOperations operations) { super(method, operations, - new ValueExpressionSupportHolder( - new QueryMethodValueEvaluationContextProviderFactory(new StandardEnvironment(), - QueryMethodEvaluationContextProvider.DEFAULT), + new ValueExpressionDelegate( + new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), + Collections.emptySet()), ValueExpressionParser.create(SpelExpressionParser::new))); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQueryUnitTests.java index 6f587ce9cb..63967080c0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQueryUnitTests.java @@ -22,6 +22,7 @@ import reactor.core.publisher.Mono; import java.lang.reflect.Method; +import java.util.Collections; import java.util.List; import java.util.Locale; @@ -37,6 +38,7 @@ import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.env.StandardEnvironment; import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mongodb.core.Person; @@ -61,9 +63,8 @@ import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; -import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory; -import org.springframework.data.repository.query.ReactiveExtensionAwareQueryMethodEvaluationContextProvider; -import org.springframework.data.repository.query.ValueExpressionSupportHolder; +import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor; +import org.springframework.data.repository.query.ValueExpressionDelegate; import org.springframework.expression.spel.standard.SpelExpressionParser; import com.mongodb.MongoClientSettings; @@ -305,9 +306,10 @@ private static class ReactiveMongoQueryFake extends AbstractReactiveMongoQuery { ReactiveMongoQueryFake(ReactiveMongoQueryMethod method, ReactiveMongoOperations operations) { super(method, operations, - new ValueExpressionSupportHolder( - new QueryMethodValueEvaluationContextProviderFactory(new StandardEnvironment(), - ReactiveExtensionAwareQueryMethodEvaluationContextProvider.DEFAULT), + new ValueExpressionDelegate( + new QueryMethodValueEvaluationContextAccessor( + new StandardEnvironment(), + Collections.emptySet()), ValueExpressionParser.create(SpelExpressionParser::new))); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java index 95fdea4db2..61f7dc3d99 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java @@ -55,10 +55,10 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; -import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory; +import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor; import org.springframework.data.repository.query.ReactiveExtensionAwareQueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider; -import org.springframework.data.repository.query.ValueExpressionSupportHolder; +import org.springframework.data.repository.query.ValueExpressionDelegate; import org.springframework.data.spel.spi.EvaluationContextExtension; import org.springframework.data.spel.spi.ReactiveEvaluationContextExtension; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -216,7 +216,7 @@ public void shouldSupportPropertiesInCustomQueries() throws Exception { @Test // DATAMONGO-1444 public void shouldSupportExpressionsInCustomQueriesWithNestedObject() throws Exception { - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2"); + ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, true, "param1"); ReactiveStringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpressionAndNestedObject", boolean.class, String.class); @@ -257,11 +257,8 @@ public void shouldSupportNonQuotedBinaryDataReplacement() throws Exception { @Test // DATAMONGO-1894 void shouldConsiderReactiveSpelExtension() throws Exception { - ReactiveExtensionAwareQueryMethodEvaluationContextProvider contextProvider = new ReactiveExtensionAwareQueryMethodEvaluationContextProvider( - Collections.singletonList(ReactiveSpelExtension.INSTANCE)); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter); - ReactiveStringBasedMongoQuery mongoQuery = createQueryForMethod(contextProvider, "withReactiveSpelExtensions"); + ReactiveStringBasedMongoQuery mongoQuery = createQueryForMethod("withReactiveSpelExtensions"); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor).block(); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{lastname: true}", "{project: true}"); @@ -269,21 +266,17 @@ void shouldConsiderReactiveSpelExtension() throws Exception { assertThat(query.getQueryObject().toJson()).isEqualTo(reference.getQueryObject().toJson()); } - private ReactiveStringBasedMongoQuery createQueryForMethod(String name, Class... parameters) throws Exception { - return createQueryForMethod(ReactiveQueryMethodEvaluationContextProvider.DEFAULT, name, parameters); - } - private ReactiveStringBasedMongoQuery createQueryForMethod( - ReactiveQueryMethodEvaluationContextProvider contextProvider, String name, Class... parameters) + String name, Class... parameters) throws Exception { Method method = SampleRepository.class.getMethod(name, parameters); ProjectionFactory factory = new SpelAwareProxyProjectionFactory(); ReactiveMongoQueryMethod queryMethod = new ReactiveMongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class), factory, converter.getMappingContext()); - - return new ReactiveStringBasedMongoQuery(queryMethod, operations, new ValueExpressionSupportHolder( - new QueryMethodValueEvaluationContextProviderFactory(environment, contextProvider), PARSER)); + QueryMethodValueEvaluationContextAccessor accessor = new QueryMethodValueEvaluationContextAccessor( + environment, Collections.singletonList(ReactiveSpelExtension.INSTANCE)); + return new ReactiveStringBasedMongoQuery(queryMethod, operations, new ValueExpressionDelegate(accessor, PARSER)); } private interface SampleRepository extends Repository { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java index 086537465e..154958a951 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java @@ -63,9 +63,8 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; -import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; -import org.springframework.data.repository.query.QueryMethodValueEvaluationContextProviderFactory; -import org.springframework.data.repository.query.ValueExpressionSupportHolder; +import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor; +import org.springframework.data.repository.query.ValueExpressionDelegate; import org.springframework.expression.spel.standard.SpelExpressionParser; import com.mongodb.MongoClientSettings; @@ -333,7 +332,7 @@ public void shouldSupportPropertiesInCustomQueries() { @Test // DATAMONGO-1244 public void shouldSupportExpressionsInCustomQueriesWithNestedObject() { - ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, true, "param1"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpressionAndNestedObject", boolean.class, String.class); @@ -744,9 +743,9 @@ private StringBasedMongoQuery createQueryForMethod(String name, Class... para ProjectionFactory factory = new SpelAwareProxyProjectionFactory(); MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class), factory, converter.getMappingContext()); - return new StringBasedMongoQuery(queryMethod, operations, - new ValueExpressionSupportHolder(new QueryMethodValueEvaluationContextProviderFactory(environment, - QueryMethodEvaluationContextProvider.DEFAULT), PARSER)); + QueryMethodValueEvaluationContextAccessor accessor = new QueryMethodValueEvaluationContextAccessor( + environment, Collections.emptySet()); + return new StringBasedMongoQuery(queryMethod, operations, new ValueExpressionDelegate(accessor, PARSER)); } catch (Exception e) { throw new IllegalArgumentException(e.getMessage(), e); From fa7af7d916cd1a1cf9b0d98d6d38460b1a0c091a Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 24 Sep 2024 15:23:24 +0200 Subject: [PATCH 6/9] Added test for dynamic collection names Closes #2764 --- .../BasicMongoPersistentEntityUnitTests.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java index dcedba84ea..15dd600b01 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java @@ -34,12 +34,14 @@ import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.AliasFor; +import org.springframework.core.env.Environment; import org.springframework.core.env.StandardEnvironment; import org.springframework.data.mapping.MappingException; import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider; import org.springframework.data.spel.spi.EvaluationContextExtension; import org.springframework.data.util.TypeInformation; +import org.springframework.mock.env.MockEnvironment; /** * Unit tests for {@link BasicMongoPersistentEntity}. @@ -87,6 +89,19 @@ void collectionAllowsReferencingSpringBean() { assertThat(entity.getCollection()).isEqualTo("otherReference"); } + @Test // GH-2764 + void collectionAllowsReferencingProperties() { + + MockEnvironment environment = new MockEnvironment(); + environment.setProperty("collectionName", "reference"); + + BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity<>( + TypeInformation.of(DynamicallyMappedUsingPropertyPlaceholder.class)); + entity.setEnvironment(environment); + + assertThat(entity.getCollection()).isEqualTo("reference_cat"); + } + @Test // DATAMONGO-937 void shouldDetectLanguageCorrectly() { @@ -325,6 +340,9 @@ class Company {} @Document("#{@myBean.collectionName}") class DynamicallyMapped {} + @Document("${collectionName}_cat") + class DynamicallyMappedUsingPropertyPlaceholder {} + class CollectionProvider { String collectionName; From b3fc9cbbe1dd10b9237dd992621ac44c66a8114b Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 25 Sep 2024 17:11:50 +0200 Subject: [PATCH 7/9] Added feedback from the review --- .../repository/query/AbstractMongoQuery.java | 21 +++------- .../query/AbstractReactiveMongoQuery.java | 19 +++------ .../repository/query/PartTreeMongoQuery.java | 6 +-- .../query/ReactivePartTreeMongoQuery.java | 6 +-- .../query/ReactiveStringBasedAggregation.java | 6 +-- .../query/ReactiveStringBasedMongoQuery.java | 13 +++--- .../query/StringBasedAggregation.java | 6 +-- .../query/StringBasedMongoQuery.java | 6 +-- ...ssionDelegateValueExpressionEvaluator.java | 41 +++++++++++++++++++ .../util/json/ParameterBindingContext.java | 20 +++++++-- 10 files changed, 90 insertions(+), 54 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ValueExpressionDelegateValueExpressionEvaluator.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java index 618f158fe5..1c817b17f2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java @@ -22,7 +22,6 @@ 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; @@ -121,6 +120,7 @@ public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations, E * @param method must not be {@literal null}. * @param operations must not be {@literal null}. * @param delegate must not be {@literal null} + * @since 4.4.0 */ public AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations, ValueExpressionDelegate delegate) { @@ -415,23 +415,11 @@ protected SpELExpressionEvaluator getSpELExpressionEvaluatorFor(ExpressionDepend * * @param accessor must not be {@literal null}. * @return the {@link ValueExpressionEvaluator}. - * @since 4.3 + * @since 4.4.0 */ protected ValueExpressionEvaluator getExpressionEvaluatorFor(MongoParameterAccessor accessor) { - - return new ValueExpressionEvaluator() { - - @Override - public T evaluate(String expressionString) { - - ValueExpression expression = valueExpressionDelegate.parse(expressionString); - ValueEvaluationContext evaluationContext = - valueEvaluationContextProvider.getEvaluationContext(accessor.getValues(), expression.getExpressionDependencies()); - - return (T) expression.evaluate(evaluationContext); - - } - }; + return new ValueExpressionDelegateValueExpressionEvaluator(valueExpressionDelegate, (ValueExpression expression) -> + valueEvaluationContextProvider.getEvaluationContext(accessor.getValues(), expression.getExpressionDependencies())); } /** @@ -480,4 +468,5 @@ protected CodecRegistry getCodecRegistry() { * @since 2.0.4 */ protected abstract boolean isLimiting(); + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java index d37a46bb0b..15ff5e5e23 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java @@ -124,6 +124,7 @@ public AbstractReactiveMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongo 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}. @@ -131,6 +132,7 @@ public AbstractReactiveMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongo * @param method must not be {@literal null}. * @param operations must not be {@literal null}. * @param delegate must not be {@literal null}. + * @since 4.4.0 */ public AbstractReactiveMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations operations, ValueExpressionDelegate delegate) { @@ -463,10 +465,10 @@ protected Mono 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 + * @deprecated since 4.4.0, use * {@link #getValueExpressionEvaluatorLater(ExpressionDependencies, MongoParameterAccessor)} instead */ - @Deprecated(since = "4.3") + @Deprecated(since = "4.4.0") protected Mono getSpelEvaluatorFor(ExpressionDependencies dependencies, MongoParameterAccessor accessor) { return valueEvaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies) @@ -508,18 +510,7 @@ protected Mono getValueExpressionEvaluatorLater(Expres MongoParameterAccessor accessor) { return valueEvaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies) - .map(evaluationContext -> { - - return new ValueExpressionEvaluator() { - @Override - public T evaluate(String expressionString) { - - ValueExpression expression = valueExpressionDelegate.parse(expressionString); - - return (T) expression.evaluate(evaluationContext); - } - }; - }); + .map(evaluationContext -> new ValueExpressionDelegateValueExpressionEvaluator(valueExpressionDelegate, valueExpression -> evaluationContext)); } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java index a36c4a89d4..33bacb90ff 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java @@ -61,9 +61,9 @@ 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 QueryMethodValueEvaluationContextAccessor} instead. + * @deprecated since 4.4, use the constructors accepting {@link QueryMethodValueEvaluationContextAccessor} instead. */ - @Deprecated(since = "4.3") + @Deprecated(since = "4.4.0") public PartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations, ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { super(method, mongoOperations, expressionParser, evaluationContextProvider); @@ -80,7 +80,7 @@ public PartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperatio * @param method must not be {@literal null}. * @param mongoOperations must not be {@literal null}. * @param delegate must not be {@literal null}. - * @since 4.3 + * @since 4.4.0 */ public PartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations, ValueExpressionDelegate delegate) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java index abf7ea3060..894f8cdcb5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java @@ -59,9 +59,9 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery { * @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 QueryMethodValueEvaluationContextAccessor} instead. + * @deprecated since 4.4.0, use the constructors accepting {@link QueryMethodValueEvaluationContextAccessor} instead. */ - @Deprecated(since = "4.3") + @Deprecated(since = "4.4.0") public ReactivePartTreeMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations, ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { super(method, mongoOperations, expressionParser, evaluationContextProvider); @@ -78,7 +78,7 @@ public ReactivePartTreeMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongo * @param method must not be {@literal null}. * @param mongoOperations must not be {@literal null}. * @param delegate must not be {@literal null}. - * @since 4.3 + * @since 4.4.0 */ public ReactivePartTreeMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations, ValueExpressionDelegate delegate) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java index b1067015d2..a74694d968 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java @@ -58,9 +58,9 @@ public class ReactiveStringBasedAggregation extends AbstractReactiveMongoQuery { * @param reactiveMongoOperations 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 ValueExpressionDelegate} instead. + * @deprecated since 4.4.0, use the constructors accepting {@link ValueExpressionDelegate} instead. */ - @Deprecated(since = "4.3") + @Deprecated(since = "4.4.0") public ReactiveStringBasedAggregation(ReactiveMongoQueryMethod method, ReactiveMongoOperations reactiveMongoOperations, ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { @@ -75,7 +75,7 @@ public ReactiveStringBasedAggregation(ReactiveMongoQueryMethod method, * @param method must not be {@literal null}. * @param reactiveMongoOperations must not be {@literal null}. * @param delegate must not be {@literal null}. - * @since 4.3 + * @since 4.4.0 */ public ReactiveStringBasedAggregation(ReactiveMongoQueryMethod method, ReactiveMongoOperations reactiveMongoOperations, ValueExpressionDelegate delegate) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java index c1fe2409a5..565fc8157b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java @@ -34,6 +34,7 @@ import org.springframework.data.spel.ExpressionDependencies; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.lang.NonNull; import org.springframework.util.Assert; /** @@ -65,9 +66,9 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery { * @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 ValueExpressionDelegate} instead. + * @deprecated since 4.4.0, use the constructors accepting {@link ValueExpressionDelegate} instead. */ - @Deprecated(since = "4.3") + @Deprecated(since = "4.4.0") public ReactiveStringBasedMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations, ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { this(method.getAnnotatedQuery(), method, mongoOperations, expressionParser, evaluationContextProvider); @@ -82,9 +83,9 @@ public ReactiveStringBasedMongoQuery(ReactiveMongoQueryMethod method, ReactiveMo * @param method must not be {@literal null}. * @param mongoOperations must not be {@literal null}. * @param expressionParser must not be {@literal null}. - * @deprecated since 4.3, use the constructors accepting {@link ValueExpressionDelegate} instead. + * @deprecated since 4.4.0, use the constructors accepting {@link ValueExpressionDelegate} instead. */ - @Deprecated(since = "4.3") + @Deprecated(since = "4.4.0") public ReactiveStringBasedMongoQuery(String query, ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations, ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) { @@ -123,6 +124,7 @@ public ReactiveStringBasedMongoQuery(String query, ReactiveMongoQueryMethod meth * @param method must not be {@literal null}. * @param mongoOperations must not be {@literal null}. * @param delegate must not be {@literal null}. + * @since 4.4.0 */ public ReactiveStringBasedMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations, ValueExpressionDelegate delegate) { @@ -137,8 +139,9 @@ public ReactiveStringBasedMongoQuery(ReactiveMongoQueryMethod method, ReactiveMo * @param method must not be {@literal null}. * @param mongoOperations must not be {@literal null}. * @param delegate must not be {@literal null}. + * @since 4.4.0 */ - public ReactiveStringBasedMongoQuery(String query, ReactiveMongoQueryMethod method, + public ReactiveStringBasedMongoQuery(@NonNull String query, ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations, ValueExpressionDelegate delegate) { super(method, mongoOperations, delegate); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java index 27d7ed0795..1ffca4d85a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java @@ -64,9 +64,9 @@ public class StringBasedAggregation 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 QueryMethodValueEvaluationContextAccessor} instead. + * @deprecated since 4.4.0, use the constructors accepting {@link QueryMethodValueEvaluationContextAccessor} instead. */ - @Deprecated(since = "4.3") + @Deprecated(since = "4.4.0") public StringBasedAggregation(MongoQueryMethod method, MongoOperations mongoOperations, ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { super(method, mongoOperations, expressionParser, evaluationContextProvider); @@ -87,7 +87,7 @@ public StringBasedAggregation(MongoQueryMethod method, MongoOperations mongoOper * @param method must not be {@literal null}. * @param mongoOperations must not be {@literal null}. * @param delegate must not be {@literal null}. - * @since 4.3 + * @since 4.4.0 */ public StringBasedAggregation(MongoQueryMethod method, MongoOperations mongoOperations, ValueExpressionDelegate delegate) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java index 749d6a9cd8..a7e7cc38eb 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java @@ -57,9 +57,9 @@ public class StringBasedMongoQuery 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 ValueExpressionDelegate} instead. + * @deprecated since 4.4.0, use the constructors accepting {@link ValueExpressionDelegate} instead. */ - @Deprecated(since = "4.3") + @Deprecated(since = "4.4.0") public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations, ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) { super(method, mongoOperations, expressionParser, evaluationContextProvider); @@ -97,7 +97,7 @@ public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOpera * @param method must not be {@literal null}. * @param mongoOperations must not be {@literal null}. * @param expressionSupport must not be {@literal null}. - * @since 4.3 + * @since 4.4.0 */ public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations, ValueExpressionDelegate expressionSupport) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ValueExpressionDelegateValueExpressionEvaluator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ValueExpressionDelegateValueExpressionEvaluator.java new file mode 100644 index 0000000000..3cefe340f8 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ValueExpressionDelegateValueExpressionEvaluator.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.repository.query; + +import java.util.function.Function; + +import org.springframework.data.expression.ValueEvaluationContext; +import org.springframework.data.expression.ValueExpression; +import org.springframework.data.mapping.model.ValueExpressionEvaluator; +import org.springframework.data.repository.query.ValueExpressionDelegate; + +class ValueExpressionDelegateValueExpressionEvaluator implements ValueExpressionEvaluator { + + private final ValueExpressionDelegate delegate; + private final Function expressionToContext; + + ValueExpressionDelegateValueExpressionEvaluator(ValueExpressionDelegate delegate, Function expressionToContext) { + this.delegate = delegate; + this.expressionToContext = expressionToContext; + } + + @SuppressWarnings("unchecked") + @Override + public T evaluate(String expressionString) { + ValueExpression expression = delegate.parse(expressionString); + return (T) expression.evaluate(expressionToContext.apply(expression)); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java index 61034244fc..70eb840b7a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingContext.java @@ -19,6 +19,7 @@ import java.util.function.Function; import java.util.function.Supplier; +import org.springframework.data.mapping.model.SpELExpressionEvaluator; import org.springframework.data.mapping.model.ValueExpressionEvaluator; import org.springframework.data.spel.ExpressionDependencies; import org.springframework.data.util.Lazy; @@ -47,14 +48,25 @@ public class ParameterBindingContext { * @param valueProvider * @param expressionParser * @param evaluationContext - * @deprecated since 4.3, use {@link #ParameterBindingContext(ValueProvider, ExpressionParser, Supplier)} instead. + * @deprecated since 4.4.0, use {@link #ParameterBindingContext(ValueProvider, ExpressionParser, Supplier)} instead. */ - @Deprecated(since = "4.3") + @Deprecated(since = "4.4.0") public ParameterBindingContext(ValueProvider valueProvider, SpelExpressionParser expressionParser, EvaluationContext evaluationContext) { this(valueProvider, expressionParser, () -> evaluationContext); } + /** + * @param valueProvider + * @param expressionEvaluator + * @since 3.1 + * @deprecated since 4.4.0, use {@link #ParameterBindingContext(ValueProvider, ValueExpressionEvaluator)} instead. + */ + @Deprecated(since = "4.4.0") + public ParameterBindingContext(ValueProvider valueProvider, SpELExpressionEvaluator expressionEvaluator) { + this(valueProvider, (ValueExpressionEvaluator) expressionEvaluator); + } + /** * @param valueProvider * @param expressionParser @@ -94,7 +106,7 @@ private static String unwrap(String expressionString) { /** * @param valueProvider * @param expressionEvaluator - * @since 4.3 + * @since 4.4.0 */ public ParameterBindingContext(ValueProvider valueProvider, ValueExpressionEvaluator expressionEvaluator) { this.valueProvider = valueProvider; @@ -133,7 +145,7 @@ public EvaluationContext getEvaluationContext(String expressionString) { * @param valueProvider * @param expressionEvaluator * @return - * @since 4.3 + * @since 4.4.0 */ public static ParameterBindingContext forExpressions(ValueProvider valueProvider, ValueExpressionEvaluator expressionEvaluator) { From b0819f45631356a332b20fa8f4ec60a512e9bd42 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 27 Sep 2024 18:24:48 +0200 Subject: [PATCH 8/9] Added a test for the scenario where there's no default value for a property --- .../query/StringBasedMongoQueryUnitTests.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java index 154958a951..03f3b3e5ff 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java @@ -65,6 +65,7 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor; import org.springframework.data.repository.query.ValueExpressionDelegate; +import org.springframework.expression.EvaluationException; import org.springframework.expression.spel.standard.SpelExpressionParser; import com.mongodb.MongoClientSettings; @@ -329,6 +330,16 @@ public void shouldSupportPropertiesInCustomQueries() { assertThat(query.getQueryObject()).isEqualTo(reference.getQueryObject()); } + @Test // GH-3050 + public void shouldFailWhenPropertiesWithNoDefaultValueInCustomQueries() { + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter); + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithProperty"); + + assertThatThrownBy(() -> mongoQuery.createQuery(accessor)) + .isInstanceOf(EvaluationException.class) + .hasMessageContaining("Could not resolve placeholder 'foo' in value \"${foo}\""); + } + @Test // DATAMONGO-1244 public void shouldSupportExpressionsInCustomQueriesWithNestedObject() { From a1be384868aa87abc1e339da4da9b6a41f4adb05 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Fri, 27 Sep 2024 18:50:36 +0200 Subject: [PATCH 9/9] Ensures that new value expression API is being used --- .../ParameterBindingJsonReaderUnitTests.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java index 3f033db6dc..646f813481 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.Test; import org.springframework.data.expression.ValueExpressionParser; +import org.springframework.data.mapping.model.ValueExpressionEvaluator; import org.springframework.data.spel.EvaluationContextProvider; import org.springframework.data.spel.ExpressionDependencies; import org.springframework.expression.EvaluationContext; @@ -263,7 +264,7 @@ public TypedValue getRootObject() { }; ParameterBindingJsonReader reader = new ParameterBindingJsonReader("{ 'name':'?0' }", - new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext)); + new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), () -> evaluationContext)); Document target = new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build()); assertThat(target).isEqualTo(new Document("name", "value")); @@ -347,7 +348,7 @@ void shouldABindArgumentsViaIndexInSpelExpressions() { ParameterBindingJsonReader reader = new ParameterBindingJsonReader( "{ 'isBatman' : ?#{ T(" + this.getClass().getName() + ").isBatman() ? [0] : [1] }}", - new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext)); + new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), () -> evaluationContext)); Document target = new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build()); assertThat(target).isEqualTo(new Document("isBatman", "nooo")); @@ -362,7 +363,7 @@ void shouldABindArgumentsViaIndexInSpelExpressions() { ParameterBindingJsonReader reader = new ParameterBindingJsonReader( "{ 'isBatman' : ?#{ T(" + this.getClass().getName() + ").isBatman() ? '?0' : '?1' }}", - new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext)); + new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), () -> evaluationContext)); Document target = new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build()); assertThat(target).isEqualTo(new Document("isBatman", "nooo")); @@ -377,7 +378,7 @@ void shouldABindArgumentsViaIndexInSpelExpressions() { ParameterBindingJsonReader reader = new ParameterBindingJsonReader( "{ 'isBatman' : \"?#{ T(" + this.getClass().getName() + ").isBatman() ? '?0' : '?1' }\" }", - new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext)); + new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), () -> evaluationContext)); Document target = new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build()); assertThat(target).isEqualTo(new Document("isBatman", "nooo")); @@ -395,7 +396,7 @@ void evaluatesSpelExpressionDefiningEntireQuery() { + ").isBatman() ? {'_class': { '$eq' : 'region' }} : { '$and' : { {'_class': { '$eq' : 'region' } }, {'user.supervisor': principal.id } } } }"; ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json, - new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext)); + new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), () -> evaluationContext)); Document target = new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build()); assertThat(target) @@ -422,7 +423,7 @@ public void bindEntireQueryUsingSpelExpressionWhenEvaluationResultIsDocument() { .getEvaluationContext(args); ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json, - new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext)); + new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), () -> evaluationContext)); Document target = new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build()); assertThat(target).isEqualTo(new Document("name", "expected")); @@ -438,7 +439,7 @@ public void throwsExceptionWhenBindEntireQueryUsingSpelExpressionIsMalFormatted( assertThatExceptionOfType(ParseException.class).isThrownBy(() -> { ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json, - new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext)); + new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), () -> evaluationContext)); new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build()); }); @@ -453,7 +454,7 @@ public void bindEntireQueryUsingSpelExpressionWhenEvaluationResultIsJsonStringCo .getEvaluationContext(args); ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json, - new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext)); + new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), () -> evaluationContext)); Document target = new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build()); @@ -471,7 +472,7 @@ void bindEntireQueryUsingSpelExpression() { String json = "?#{ T(" + this.getClass().getName() + ").applyFilterByUser('?0' ,principal.id) }"; ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json, - new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext)); + new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), () -> evaluationContext)); Document target = new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build()); assertThat(target) @@ -489,7 +490,7 @@ void bindEntireQueryUsingParameter() { String json = "?0"; ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json, - new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), evaluationContext)); + new ParameterBindingContext((index) -> args[index], new SpelExpressionParser(), () -> evaluationContext)); Document target = new ParameterBindingDocumentCodec().decode(reader, DecoderContext.builder().build()); assertThat(target).isEqualTo(new Document("itWorks", true));