Add support for Value Expressions in Repository Query methods.
Closes #4677 Original pull request: #4683
This commit is contained in:
committed by
Mark Paluch
parent
db44512c13
commit
8b9182e5a8
@@ -21,7 +21,12 @@ import java.util.List;
|
||||
import org.bson.Document;
|
||||
import org.bson.codecs.configuration.CodecRegistry;
|
||||
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
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;
|
||||
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;
|
||||
@@ -43,12 +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.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.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;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -71,10 +79,10 @@ 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 Lazy<ParameterBindingDocumentCodec> codec = Lazy
|
||||
.of(() -> new ParameterBindingDocumentCodec(getCodecRegistry()));
|
||||
private final ValueExpressionDelegate valueExpressionDelegate;
|
||||
private final ValueEvaluationContextProvider valueEvaluationContextProvider;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AbstractMongoQuery} from the given {@link MongoQueryMethod} and {@link MongoOperations}.
|
||||
@@ -83,7 +91,9 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
* @param operations 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 AbstractMongoQuery(MongoQueryMethod method, MongoOperations operations, ExpressionParser expressionParser,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
@@ -100,10 +110,36 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
|
||||
this.executableFind = operations.query(type);
|
||||
this.executableUpdate = operations.update(type);
|
||||
this.expressionParser = expressionParser;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
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}
|
||||
* @since 4.4.0
|
||||
*/
|
||||
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
|
||||
public MongoQueryMethod getQueryMethod() {
|
||||
return method;
|
||||
}
|
||||
@@ -243,7 +279,7 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
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 +382,7 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
*/
|
||||
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 +407,19 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
protected SpELExpressionEvaluator getSpELExpressionEvaluatorFor(ExpressionDependencies dependencies,
|
||||
ConvertingParameterAccessor accessor) {
|
||||
|
||||
return new DefaultSpELExpressionEvaluator(expressionParser, evaluationContextProvider
|
||||
.getEvaluationContext(getQueryMethod().getParameters(), accessor.getValues(), dependencies));
|
||||
return new DefaultSpELExpressionEvaluator(new SpelExpressionParser(), valueEvaluationContextProvider.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.4.0
|
||||
*/
|
||||
protected ValueExpressionEvaluator getExpressionEvaluatorFor(MongoParameterAccessor accessor) {
|
||||
return new ValueExpressionDelegateValueExpressionEvaluator(valueExpressionDelegate, (ValueExpression expression) ->
|
||||
valueEvaluationContextProvider.getEvaluationContext(accessor.getValues(), expression.getExpressionDependencies()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -424,4 +468,5 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
||||
* @since 2.0.4
|
||||
*/
|
||||
protected abstract boolean isLimiting();
|
||||
|
||||
}
|
||||
|
||||
@@ -26,8 +26,15 @@ import org.bson.codecs.configuration.CodecRegistry;
|
||||
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;
|
||||
import org.springframework.data.mongodb.core.MongoOperations;
|
||||
import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithProjection;
|
||||
import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithQuery;
|
||||
@@ -48,18 +55,22 @@ import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecu
|
||||
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.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.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;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.MongoClientSettings;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
/**
|
||||
* Base class for reactive {@link RepositoryQuery} implementations for MongoDB.
|
||||
@@ -76,8 +87,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 ValueExpressionDelegate valueExpressionDelegate;
|
||||
private final ReactiveValueEvaluationContextProvider valueEvaluationContextProvider;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AbstractReactiveMongoQuery} from the given {@link MongoQueryMethod} and
|
||||
@@ -87,7 +98,9 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
* @param operations 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,
|
||||
ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
@@ -99,20 +112,57 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
this.method = method;
|
||||
this.operations = operations;
|
||||
this.instantiators = new EntityInstantiators();
|
||||
this.expressionParser = expressionParser;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
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}.
|
||||
* @since 4.4.0
|
||||
*/
|
||||
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
|
||||
public MongoQueryMethod getQueryMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Publisher<Object> execute(Object[] parameters) {
|
||||
|
||||
return method.hasReactiveWrapperParameter() ? executeDeferred(parameters)
|
||||
@@ -269,7 +319,7 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
Query applyAnnotatedCollationIfPresent(Query query, ConvertingParameterAccessor accessor) {
|
||||
|
||||
return QueryUtils.applyCollation(query, method.hasAnnotatedCollation() ? method.getAnnotatedCollation() : null,
|
||||
accessor, getQueryMethod().getParameters(), expressionParser, evaluationContextProvider);
|
||||
accessor, getValueExpressionEvaluator(accessor));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,19 +431,19 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
bsonString -> AbstractReactiveMongoQuery.this.decode(evaluator, bsonString, accessor, codec)));
|
||||
}
|
||||
|
||||
private Mono<SpELExpressionEvaluator> expressionEvaluator(String source, MongoParameterAccessor accessor,
|
||||
ParameterBindingDocumentCodec codec) {
|
||||
private Mono<Tuple2<ValueExpressionEvaluator, ParameterBindingDocumentCodec>> expressionEvaluator(String source,
|
||||
MongoParameterAccessor accessor, ParameterBindingDocumentCodec codec) {
|
||||
|
||||
ExpressionDependencies dependencies = codec.captureExpressionDependencies(source, accessor::getBindableValue,
|
||||
expressionParser);
|
||||
return getSpelEvaluatorFor(dependencies, accessor);
|
||||
valueExpressionDelegate.getValueExpressionParser());
|
||||
return getValueExpressionEvaluatorLater(dependencies, accessor).zipWith(Mono.just(codec));
|
||||
}
|
||||
|
||||
private Document decode(SpELExpressionEvaluator expressionEvaluator, String source, MongoParameterAccessor accessor,
|
||||
private Document decode(Tuple2<ValueExpressionEvaluator, ParameterBindingDocumentCodec> expressionEvaluator, String source, MongoParameterAccessor accessor,
|
||||
ParameterBindingDocumentCodec codec) {
|
||||
|
||||
ParameterBindingContext bindingContext = new ParameterBindingContext(accessor::getBindableValue,
|
||||
expressionEvaluator);
|
||||
expressionEvaluator.getT1());
|
||||
return codec.decode(source, bindingContext);
|
||||
}
|
||||
|
||||
@@ -415,17 +465,54 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
||||
* @param accessor must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the {@link SpELExpressionEvaluator} when ready.
|
||||
* @since 3.4
|
||||
* @deprecated since 4.4.0, use
|
||||
* {@link #getValueExpressionEvaluatorLater(ExpressionDependencies, MongoParameterAccessor)} instead
|
||||
*/
|
||||
@Deprecated(since = "4.4.0")
|
||||
protected Mono<SpELExpressionEvaluator> getSpelEvaluatorFor(ExpressionDependencies dependencies,
|
||||
MongoParameterAccessor accessor) {
|
||||
|
||||
return evaluationContextProvider
|
||||
.getEvaluationContextLater(getQueryMethod().getParameters(), accessor.getValues(), dependencies)
|
||||
.map(evaluationContext -> (SpELExpressionEvaluator) new DefaultSpELExpressionEvaluator(expressionParser,
|
||||
evaluationContext))
|
||||
return valueEvaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies)
|
||||
.map(evaluationContext -> (SpELExpressionEvaluator) new DefaultSpELExpressionEvaluator(
|
||||
new SpelExpressionParser(), evaluationContext.getEvaluationContext()))
|
||||
.defaultIfEmpty(DefaultSpELExpressionEvaluator.unsupported());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@link ValueExpressionEvaluator} suitable to evaluate expressions.
|
||||
*
|
||||
* @param accessor must not be {@literal null}.
|
||||
* @since 4.3
|
||||
*/
|
||||
ValueExpressionEvaluator getValueExpressionEvaluator(MongoParameterAccessor accessor) {
|
||||
|
||||
return new ValueExpressionEvaluator() {
|
||||
|
||||
@Override
|
||||
public <T> T evaluate(String expressionString) {
|
||||
ValueExpression expression = valueExpressionDelegate.parse(expressionString);
|
||||
ValueEvaluationContext evaluationContext = valueEvaluationContextProvider.getEvaluationContext(accessor.getValues(),
|
||||
expression.getExpressionDependencies());
|
||||
return (T) expression.evaluate(evaluationContext);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@link Mono publisher} emitting the {@link ValueExpressionEvaluator} suitable to evaluate expressions
|
||||
* backed by the given dependencies.
|
||||
*
|
||||
* @param dependencies must not be {@literal null}.
|
||||
* @param accessor must not be {@literal null}.
|
||||
* @return a {@link Mono} emitting the {@link ValueExpressionEvaluator} when ready.
|
||||
* @since 4.3
|
||||
*/
|
||||
protected Mono<ValueExpressionEvaluator> getValueExpressionEvaluatorLater(ExpressionDependencies dependencies,
|
||||
MongoParameterAccessor accessor) {
|
||||
|
||||
return valueEvaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies)
|
||||
.map(evaluationContext -> new ValueExpressionDelegateValueExpressionEvaluator(valueExpressionDelegate, valueExpression -> evaluationContext));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a {@link Mono} emitting the {@link CodecRegistry} when ready.
|
||||
* @since 2.4
|
||||
|
||||
@@ -21,8 +21,10 @@ import java.util.function.IntUnaryOperator;
|
||||
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.mapping.FieldName;
|
||||
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 @@ abstract class 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,11 @@ import java.util.regex.Matcher;
|
||||
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 @@ abstract class 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 @@ abstract class CollationUtils {
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.springframework.data.mongodb.repository.query;
|
||||
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.Query;
|
||||
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.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.ValueExpressionDelegate;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -57,11 +61,31 @@ public class PartTreeMongoQuery extends AbstractMongoQuery {
|
||||
* @param mongoOperations must not be {@literal null}.
|
||||
* @param expressionParser must not be {@literal null}.
|
||||
* @param evaluationContextProvider must not be {@literal null}.
|
||||
* @deprecated since 4.4, use the constructors accepting {@link QueryMethodValueEvaluationContextAccessor} instead.
|
||||
*/
|
||||
@Deprecated(since = "4.4.0")
|
||||
public PartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations, ExpressionParser expressionParser,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
super(method, mongoOperations, expressionParser, evaluationContextProvider);
|
||||
|
||||
this.processor = method.getResultProcessor();
|
||||
this.tree = new PartTree(method.getName(), processor.getReturnedType().getDomainType());
|
||||
this.isGeoNearQuery = method.isGeoNearQuery();
|
||||
this.context = mongoOperations.getConverter().getMappingContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link PartTreeMongoQuery} from the given {@link QueryMethod} and {@link MongoTemplate}.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param mongoOperations must not be {@literal null}.
|
||||
* @param delegate must not be {@literal null}.
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public PartTreeMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations,
|
||||
ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
ValueExpressionDelegate delegate) {
|
||||
|
||||
super(method, mongoOperations, expressionParser, evaluationContextProvider);
|
||||
super(method, mongoOperations, delegate);
|
||||
|
||||
this.processor = method.getResultProcessor();
|
||||
this.tree = new PartTree(method.getName(), processor.getReturnedType().getDomainType());
|
||||
|
||||
@@ -22,11 +22,11 @@ import org.apache.commons.logging.Log;
|
||||
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 @@ class QueryUtils {
|
||||
* @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);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.bson.json.JsonParseException;
|
||||
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
@@ -27,10 +28,12 @@ import org.springframework.data.mongodb.core.query.BasicQuery;
|
||||
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.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.ValueExpressionDelegate;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -56,10 +59,11 @@ 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.4.0, use the constructors accepting {@link QueryMethodValueEvaluationContextAccessor} instead.
|
||||
*/
|
||||
@Deprecated(since = "4.4.0")
|
||||
public ReactivePartTreeMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations,
|
||||
ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
super(method, mongoOperations, expressionParser, evaluationContextProvider);
|
||||
|
||||
this.processor = method.getResultProcessor();
|
||||
@@ -68,6 +72,25 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery {
|
||||
this.context = mongoOperations.getConverter().getMappingContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 delegate must not be {@literal null}.
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public ReactivePartTreeMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations,
|
||||
ValueExpressionDelegate delegate) {
|
||||
|
||||
super(method, mongoOperations, delegate);
|
||||
|
||||
this.processor = method.getResultProcessor();
|
||||
this.tree = new PartTree(method.getName(), processor.getReturnedType().getDomainType());
|
||||
this.isGeoNearQuery = method.isGeoNearQuery();
|
||||
this.context = mongoOperations.getConverter().getMappingContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link PartTree} backing the query.
|
||||
*
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.List;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
|
||||
@@ -33,6 +34,7 @@ import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.data.util.ReflectionUtils;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
@@ -48,8 +50,6 @@ import org.springframework.util.ClassUtils;
|
||||
*/
|
||||
public class ReactiveStringBasedAggregation extends AbstractReactiveMongoQuery {
|
||||
|
||||
private final ExpressionParser expressionParser;
|
||||
private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
private final ReactiveMongoOperations reactiveMongoOperations;
|
||||
private final MongoConverter mongoConverter;
|
||||
|
||||
@@ -58,7 +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.4.0, use the constructors accepting {@link ValueExpressionDelegate} instead.
|
||||
*/
|
||||
@Deprecated(since = "4.4.0")
|
||||
public ReactiveStringBasedAggregation(ReactiveMongoQueryMethod method,
|
||||
ReactiveMongoOperations reactiveMongoOperations, ExpressionParser expressionParser,
|
||||
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
@@ -67,8 +69,21 @@ public class ReactiveStringBasedAggregation extends AbstractReactiveMongoQuery {
|
||||
|
||||
this.reactiveMongoOperations = reactiveMongoOperations;
|
||||
this.mongoConverter = reactiveMongoOperations.getConverter();
|
||||
this.expressionParser = expressionParser;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param method must not be {@literal null}.
|
||||
* @param reactiveMongoOperations must not be {@literal null}.
|
||||
* @param delegate must not be {@literal null}.
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public ReactiveStringBasedAggregation(ReactiveMongoQueryMethod method,
|
||||
ReactiveMongoOperations reactiveMongoOperations, ValueExpressionDelegate delegate) {
|
||||
|
||||
super(method, reactiveMongoOperations, delegate);
|
||||
|
||||
this.reactiveMongoOperations = reactiveMongoOperations;
|
||||
this.mongoConverter = reactiveMongoOperations.getConverter();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -128,8 +143,8 @@ public class ReactiveStringBasedAggregation extends AbstractReactiveMongoQuery {
|
||||
|
||||
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);
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.bson.Document;
|
||||
|
||||
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;
|
||||
@@ -29,9 +30,11 @@ import org.springframework.data.mongodb.util.json.ParameterBindingContext;
|
||||
import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec;
|
||||
import org.springframework.data.repository.query.ReactiveExtensionAwareQueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
|
||||
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;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -49,7 +52,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;
|
||||
@@ -63,7 +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.4.0, use the constructors accepting {@link ValueExpressionDelegate} instead.
|
||||
*/
|
||||
@Deprecated(since = "4.4.0")
|
||||
public ReactiveStringBasedMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongoOperations mongoOperations,
|
||||
ExpressionParser expressionParser, ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
this(method.getAnnotatedQuery(), method, mongoOperations, expressionParser, evaluationContextProvider);
|
||||
@@ -78,18 +83,73 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
|
||||
* @param method must not be {@literal null}.
|
||||
* @param mongoOperations must not be {@literal null}.
|
||||
* @param expressionParser must not be {@literal null}.
|
||||
* @deprecated since 4.4.0, use the constructors accepting {@link ValueExpressionDelegate} instead.
|
||||
*/
|
||||
@Deprecated(since = "4.4.0")
|
||||
public ReactiveStringBasedMongoQuery(String query, ReactiveMongoQueryMethod method,
|
||||
ReactiveMongoOperations mongoOperations, ExpressionParser expressionParser,
|
||||
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
super(method, mongoOperations, expressionParser, evaluationContextProvider);
|
||||
|
||||
Assert.notNull(query, "Query must not be null");
|
||||
Assert.notNull(expressionParser, "SpelExpressionParser must not be null");
|
||||
|
||||
this.query = query;
|
||||
this.expressionParser = expressionParser;
|
||||
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},
|
||||
* {@link MongoOperations} and {@link ValueExpressionDelegate}.
|
||||
*
|
||||
* @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) {
|
||||
this(method.getAnnotatedQuery(), method, mongoOperations, delegate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReactiveStringBasedMongoQuery} for the given {@link String}, {@link MongoQueryMethod},
|
||||
* {@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 delegate must not be {@literal null}.
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public ReactiveStringBasedMongoQuery(@NonNull String query, ReactiveMongoQueryMethod method,
|
||||
ReactiveMongoOperations mongoOperations, ValueExpressionDelegate delegate) {
|
||||
|
||||
super(method, mongoOperations, delegate);
|
||||
|
||||
Assert.notNull(query, "Query must not be null");
|
||||
|
||||
this.query = query;
|
||||
this.expressionParser = delegate.getValueExpressionParser();
|
||||
this.fieldSpec = method.getFieldSpecification();
|
||||
|
||||
if (method.hasAnnotatedQuery()) {
|
||||
@@ -141,7 +201,7 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
|
||||
ExpressionDependencies dependencies = codec.captureExpressionDependencies(json, accessor::getBindableValue,
|
||||
expressionParser);
|
||||
|
||||
return getSpelEvaluatorFor(dependencies, accessor)
|
||||
return getValueExpressionEvaluatorLater(dependencies, accessor)
|
||||
.map(it -> new ParameterBindingContext(accessor::getBindableValue, it));
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.function.LongUnaryOperator;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.bson.Document;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.SliceImpl;
|
||||
import org.springframework.data.mongodb.InvalidMongoDbApiUsageException;
|
||||
@@ -34,7 +35,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.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.data.util.ReflectionUtils;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -53,8 +56,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}.
|
||||
@@ -63,7 +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.4.0, use the constructors accepting {@link QueryMethodValueEvaluationContextAccessor} instead.
|
||||
*/
|
||||
@Deprecated(since = "4.4.0")
|
||||
public StringBasedAggregation(MongoQueryMethod method, MongoOperations mongoOperations,
|
||||
ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
super(method, mongoOperations, expressionParser, evaluationContextProvider);
|
||||
@@ -76,8 +79,28 @@ public class StringBasedAggregation extends AbstractMongoQuery {
|
||||
|
||||
this.mongoOperations = mongoOperations;
|
||||
this.mongoConverter = mongoOperations.getConverter();
|
||||
this.expressionParser = expressionParser;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 delegate must not be {@literal null}.
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public StringBasedAggregation(MongoQueryMethod method, MongoOperations mongoOperations,
|
||||
ValueExpressionDelegate delegate) {
|
||||
super(method, mongoOperations, delegate);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -177,8 +200,8 @@ public class StringBasedAggregation extends AbstractMongoQuery {
|
||||
|
||||
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);
|
||||
|
||||
@@ -23,6 +23,8 @@ 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.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;
|
||||
@@ -55,28 +57,69 @@ 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.4.0, use the constructors accepting {@link ValueExpressionDelegate} instead.
|
||||
*/
|
||||
@Deprecated(since = "4.4.0")
|
||||
public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations,
|
||||
ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
this(method.getAnnotatedQuery(), method, mongoOperations, expressionParser, evaluationContextProvider);
|
||||
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 ValueExpressionDelegate}.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param mongoOperations must not be {@literal null}.
|
||||
* @param expressionSupport must not be {@literal null}.
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public StringBasedMongoQuery(MongoQueryMethod method, MongoOperations mongoOperations,
|
||||
ValueExpressionDelegate 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 ValueExpressionDelegate}, {@link QueryMethodValueEvaluationContextAccessor}.
|
||||
*
|
||||
* @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}.
|
||||
* @since 4.3
|
||||
*/
|
||||
public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperations mongoOperations,
|
||||
ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
ValueExpressionDelegate 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();
|
||||
|
||||
@@ -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<ValueExpression, ValueEvaluationContext> expressionToContext;
|
||||
|
||||
ValueExpressionDelegateValueExpressionEvaluator(ValueExpressionDelegate delegate, Function<ValueExpression, ValueEvaluationContext> expressionToContext) {
|
||||
this.delegate = delegate;
|
||||
this.expressionToContext = expressionToContext;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T evaluate(String expressionString) {
|
||||
ValueExpression expression = delegate.parse(expressionString);
|
||||
return (T) expression.evaluate(expressionToContext.apply(expression));
|
||||
}
|
||||
}
|
||||
@@ -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<String, Expression> 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");
|
||||
}
|
||||
}
|
||||
@@ -42,10 +42,9 @@ import org.springframework.data.repository.core.support.RepositoryComposition.Re
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
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.QueryMethodValueEvaluationContextAccessor;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
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 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
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<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
@Nullable private QueryMethodValueEvaluationContextAccessor accessor;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MongoRepositoryFactory} with the given {@link MongoOperations}.
|
||||
@@ -148,8 +146,8 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
@Override
|
||||
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
return Optional.of(new MongoQueryLookupStrategy(operations, evaluationContextProvider, mappingContext));
|
||||
ValueExpressionDelegate valueExpressionDelegate) {
|
||||
return Optional.of(new MongoQueryLookupStrategy(operations, mappingContext, valueExpressionDelegate));
|
||||
}
|
||||
|
||||
public <T, ID> MongoEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
|
||||
@@ -170,21 +168,9 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport {
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
private static class MongoQueryLookupStrategy implements QueryLookupStrategy {
|
||||
|
||||
private final MongoOperations operations;
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
private final ExpressionParser expressionParser = new CachingExpressionParser(EXPRESSION_PARSER);
|
||||
|
||||
public MongoQueryLookupStrategy(MongoOperations operations,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
|
||||
|
||||
this.operations = operations;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
private record MongoQueryLookupStrategy(MongoOperations operations,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext,
|
||||
ValueExpressionDelegate expressionSupport) implements QueryLookupStrategy {
|
||||
|
||||
@Override
|
||||
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
|
||||
@@ -197,14 +183,13 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.io.Serializable;
|
||||
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;
|
||||
@@ -42,11 +43,10 @@ import org.springframework.data.repository.core.support.RepositoryComposition.Re
|
||||
import org.springframework.data.repository.core.support.RepositoryFragment;
|
||||
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.QueryMethodValueEvaluationContextAccessor;
|
||||
import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
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 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
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<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
@Nullable private QueryMethodValueEvaluationContextAccessor accessor;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReactiveMongoRepositoryFactory} with the given {@link ReactiveMongoOperations}.
|
||||
@@ -133,13 +132,12 @@ public class ReactiveMongoRepositoryFactory extends ReactiveRepositoryFactorySup
|
||||
return targetRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
return Optional.of(new MongoQueryLookupStrategy(operations,
|
||||
(ReactiveQueryMethodEvaluationContextProvider) evaluationContextProvider, mappingContext));
|
||||
@Override protected Optional<QueryLookupStrategy> getQueryLookupStrategy(Key key,
|
||||
ValueExpressionDelegate valueExpressionDelegate) {
|
||||
return Optional.of(new MongoQueryLookupStrategy(operations, mappingContext, valueExpressionDelegate));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, ID> MongoEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
|
||||
return getEntityInformation(domainClass, null);
|
||||
}
|
||||
@@ -160,21 +158,9 @@ public class ReactiveMongoRepositoryFactory extends ReactiveRepositoryFactorySup
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
private static class MongoQueryLookupStrategy implements QueryLookupStrategy {
|
||||
|
||||
private final ReactiveMongoOperations operations;
|
||||
private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
|
||||
private final ExpressionParser expressionParser = new CachingExpressionParser(EXPRESSION_PARSER);
|
||||
|
||||
MongoQueryLookupStrategy(ReactiveMongoOperations operations,
|
||||
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
|
||||
|
||||
this.operations = operations;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
private record MongoQueryLookupStrategy(ReactiveMongoOperations operations,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext,
|
||||
ValueExpressionDelegate delegate) implements QueryLookupStrategy {
|
||||
|
||||
@Override
|
||||
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
|
||||
@@ -187,14 +173,13 @@ public class ReactiveMongoRepositoryFactory extends ReactiveRepositoryFactorySup
|
||||
|
||||
if (namedQueries.hasQuery(namedQueryName)) {
|
||||
String namedQuery = namedQueries.getQuery(namedQueryName);
|
||||
return new ReactiveStringBasedMongoQuery(namedQuery, queryMethod, operations, expressionParser,
|
||||
evaluationContextProvider);
|
||||
return new ReactiveStringBasedMongoQuery(namedQuery, queryMethod, operations, delegate);
|
||||
} else if (queryMethod.hasAnnotatedAggregation()) {
|
||||
return new ReactiveStringBasedAggregation(queryMethod, operations, expressionParser, evaluationContextProvider);
|
||||
return new ReactiveStringBasedAggregation(queryMethod, operations, delegate);
|
||||
} else if (queryMethod.hasAnnotatedQuery()) {
|
||||
return new ReactiveStringBasedMongoQuery(queryMethod, operations, expressionParser, evaluationContextProvider);
|
||||
return new ReactiveStringBasedMongoQuery(queryMethod, operations, delegate);
|
||||
} else {
|
||||
return new ReactivePartTreeMongoQuery(queryMethod, operations, expressionParser, evaluationContextProvider);
|
||||
return new ReactivePartTreeMongoQuery(queryMethod, operations, delegate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ class JsonScanner {
|
||||
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 == '{') {
|
||||
|
||||
@@ -26,6 +26,8 @@ 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;
|
||||
|
||||
@@ -46,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
|
||||
@@ -62,7 +75,7 @@ public class ParameterBindingContext {
|
||||
*/
|
||||
public ParameterBindingContext(ValueProvider valueProvider, ExpressionParser expressionParser,
|
||||
Supplier<EvaluationContext> evaluationContext) {
|
||||
this(valueProvider, new EvaluationContextExpressionEvaluator(valueProvider, expressionParser) {
|
||||
this(valueProvider, new EvaluationContextExpressionEvaluator(valueProvider, unwrap(expressionParser)) {
|
||||
@Override
|
||||
public EvaluationContext getEvaluationContext(String expressionString) {
|
||||
return evaluationContext.get();
|
||||
@@ -70,21 +83,30 @@ public class ParameterBindingContext {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param valueProvider
|
||||
* @param expressionEvaluator
|
||||
* @since 4.3
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public ParameterBindingContext(ValueProvider valueProvider, ValueExpressionEvaluator expressionEvaluator) {
|
||||
this.valueProvider = valueProvider;
|
||||
@@ -117,6 +139,20 @@ public class ParameterBindingContext {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ParameterBindingContext} that is capable of expression parsing.
|
||||
*
|
||||
* @param valueProvider
|
||||
* @param expressionEvaluator
|
||||
* @return
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public static ParameterBindingContext forExpressions(ValueProvider valueProvider,
|
||||
ValueExpressionEvaluator expressionEvaluator) {
|
||||
|
||||
return new ParameterBindingContext(valueProvider, expressionEvaluator);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Object bindableValueForIndex(int index) {
|
||||
return valueProvider.getBindableValue(index);
|
||||
|
||||
@@ -41,11 +41,11 @@ import org.bson.codecs.*;
|
||||
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 class ParameterBindingDocumentCodec implements CollectibleCodec<Document>
|
||||
* @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 @@ public class ParameterBindingDocumentCodec implements CollectibleCodec<Document>
|
||||
|
||||
private static final Object PLACEHOLDER = new Object();
|
||||
|
||||
private final ExpressionParser expressionParser;
|
||||
private final ValueExpressionParser expressionParser;
|
||||
private final List<ExpressionDependencies> dependencies = new ArrayList<>();
|
||||
|
||||
DependencyCapturingExpressionEvaluator(ExpressionParser expressionParser) {
|
||||
DependencyCapturingExpressionEvaluator(ValueExpressionParser expressionParser) {
|
||||
this.expressionParser = expressionParser;
|
||||
}
|
||||
|
||||
@@ -400,7 +400,7 @@ public class ParameterBindingDocumentCodec implements CollectibleCodec<Document>
|
||||
@Override
|
||||
public <T> T evaluate(String expression) {
|
||||
|
||||
dependencies.add(ExpressionDependencies.discover(expressionParser.parseExpression(expression)));
|
||||
dependencies.add(expressionParser.parse(expression).getExpressionDependencies());
|
||||
return (T) PLACEHOLDER;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,9 +64,9 @@ import org.springframework.util.ObjectUtils;
|
||||
*/
|
||||
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 @@ public class ParameterBindingJsonReader extends AbstractBsonReader {
|
||||
|
||||
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<String, Object> innerSpelVariables = new HashMap<>();
|
||||
@@ -385,6 +386,7 @@ public class ParameterBindingJsonReader extends AbstractBsonReader {
|
||||
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 @@ public class ParameterBindingJsonReader extends AbstractBsonReader {
|
||||
}
|
||||
}
|
||||
|
||||
Object value = evaluateExpression(expression, innerSpelVariables);
|
||||
Object value = evaluateExpression(expressionString, innerSpelVariables);
|
||||
bindableValue.setValue(value);
|
||||
bindableValue.setType(bsonTypeForValue(value));
|
||||
return bindableValue;
|
||||
@@ -421,6 +423,7 @@ public class ParameterBindingJsonReader extends AbstractBsonReader {
|
||||
|
||||
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<String, Object> innerSpelVariables = new HashMap<>();
|
||||
@@ -432,6 +435,7 @@ public class ParameterBindingJsonReader extends AbstractBsonReader {
|
||||
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 @@ public class ParameterBindingJsonReader extends AbstractBsonReader {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ public class ParameterBindingJsonReader extends AbstractBsonReader {
|
||||
}
|
||||
|
||||
verifyToken(JsonTokenType.RIGHT_PAREN);
|
||||
|
||||
|
||||
String dateTimeString = token.getValue(String.class);
|
||||
|
||||
try {
|
||||
|
||||
@@ -34,12 +34,14 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
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 @@ public class BasicMongoPersistentEntityUnitTests {
|
||||
assertThat(entity.getCollection()).isEqualTo("otherReference");
|
||||
}
|
||||
|
||||
@Test // GH-2764
|
||||
void collectionAllowsReferencingProperties() {
|
||||
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
environment.setProperty("collectionName", "reference");
|
||||
|
||||
BasicMongoPersistentEntity<DynamicallyMappedUsingPropertyPlaceholder> 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 @@ public class BasicMongoPersistentEntityUnitTests {
|
||||
@Document("#{@myBean.collectionName}")
|
||||
class DynamicallyMapped {}
|
||||
|
||||
@Document("${collectionName}_cat")
|
||||
class DynamicallyMappedUsingPropertyPlaceholder {}
|
||||
|
||||
class CollectionProvider {
|
||||
String collectionName;
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import static org.mockito.ArgumentMatchers.*;
|
||||
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;
|
||||
@@ -36,6 +37,9 @@ import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
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;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
@@ -43,6 +47,7 @@ import org.springframework.data.domain.Pageable;
|
||||
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;
|
||||
@@ -71,7 +76,8 @@ import org.springframework.data.projection.ProjectionFactory;
|
||||
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.QueryMethodValueEvaluationContextAccessor;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
|
||||
import com.mongodb.MongoClientSettings;
|
||||
@@ -566,7 +572,11 @@ class AbstractMongoQueryUnitTests {
|
||||
private boolean isLimitingQuery;
|
||||
|
||||
MongoQueryFake(MongoQueryMethod method, MongoOperations operations) {
|
||||
super(method, operations, new SpelExpressionParser(), QueryMethodEvaluationContextProvider.DEFAULT);
|
||||
super(method, operations,
|
||||
new ValueExpressionDelegate(
|
||||
new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(),
|
||||
Collections.emptySet()),
|
||||
ValueExpressionParser.create(SpelExpressionParser::new)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,6 +22,7 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -36,6 +37,10 @@ import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
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;
|
||||
import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithQuery;
|
||||
import org.springframework.data.mongodb.core.ReactiveFindOperation.ReactiveFind;
|
||||
@@ -58,7 +63,8 @@ import org.springframework.data.mongodb.repository.Update;
|
||||
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.ReactiveExtensionAwareQueryMethodEvaluationContextProvider;
|
||||
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;
|
||||
@@ -197,7 +203,7 @@ class AbstractReactiveMongoQueryUnitTests {
|
||||
|
||||
createQueryForMethod("findWithCollationUsingPlaceholdersInDocumentByFirstName", String.class, String.class,
|
||||
int.class) //
|
||||
.executeBlocking(new Object[] { "dalinar", "en_US", 2 });
|
||||
.executeBlocking(new Object[] { "dalinar", "en_US", 2 });
|
||||
|
||||
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
|
||||
verify(withQueryMock).matching(captor.capture());
|
||||
@@ -299,8 +305,12 @@ class AbstractReactiveMongoQueryUnitTests {
|
||||
private boolean isLimitingQuery;
|
||||
|
||||
ReactiveMongoQueryFake(ReactiveMongoQueryMethod method, ReactiveMongoOperations operations) {
|
||||
super(method, operations, new SpelExpressionParser(),
|
||||
ReactiveExtensionAwareQueryMethodEvaluationContextProvider.DEFAULT);
|
||||
super(method, operations,
|
||||
new ValueExpressionDelegate(
|
||||
new QueryMethodValueEvaluationContextAccessor(
|
||||
new StandardEnvironment(),
|
||||
Collections.emptySet()),
|
||||
ValueExpressionParser.create(SpelExpressionParser::new)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,14 +19,13 @@ import static org.assertj.core.api.Assertions.*;
|
||||
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.Test;
|
||||
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,10 @@ import org.springframework.data.projection.ProjectionFactory;
|
||||
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.QueryMethodValueEvaluationContextAccessor;
|
||||
import org.springframework.data.repository.query.ReactiveExtensionAwareQueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
|
||||
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;
|
||||
@@ -67,7 +73,8 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
@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 +82,18 @@ public class ReactiveStringBasedMongoQueryUnitTests {
|
||||
|
||||
MongoConverter converter;
|
||||
|
||||
Map<String, Object> 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,10 +200,23 @@ public class ReactiveStringBasedMongoQueryUnitTests {
|
||||
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 {
|
||||
|
||||
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2");
|
||||
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, true, "param1");
|
||||
ReactiveStringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpressionAndNestedObject",
|
||||
boolean.class, String.class);
|
||||
|
||||
@@ -232,11 +257,8 @@ public class ReactiveStringBasedMongoQueryUnitTests {
|
||||
@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}");
|
||||
@@ -244,21 +266,17 @@ public class ReactiveStringBasedMongoQueryUnitTests {
|
||||
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, PARSER,
|
||||
contextProvider);
|
||||
QueryMethodValueEvaluationContextAccessor accessor = new QueryMethodValueEvaluationContextAccessor(
|
||||
environment, Collections.singletonList(ReactiveSpelExtension.INSTANCE));
|
||||
return new ReactiveStringBasedMongoQuery(queryMethod, operations, new ValueExpressionDelegate(accessor, PARSER));
|
||||
}
|
||||
|
||||
private interface SampleRepository extends Repository<Person, Long> {
|
||||
@@ -290,6 +308,9 @@ public class ReactiveStringBasedMongoQueryUnitTests {
|
||||
@Query("{'lastname': ?#{[0]} }")
|
||||
Flux<Person> findByQueryWithExpression(String param0);
|
||||
|
||||
@Query("{'lastname': ?${foo} }")
|
||||
Flux<Person> findByQueryWithProperty();
|
||||
|
||||
@Query("{'id':?#{ [0] ? { $exists :true} : [1] }}")
|
||||
Flux<Person> findByQueryWithExpressionAndNestedObject(boolean param0, String param1);
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.ArrayList;
|
||||
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.Test;
|
||||
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;
|
||||
@@ -59,7 +63,9 @@ import org.springframework.data.projection.ProjectionFactory;
|
||||
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.QueryMethodValueEvaluationContextAccessor;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
|
||||
import com.mongodb.MongoClientSettings;
|
||||
@@ -77,7 +83,8 @@ import com.mongodb.reactivestreams.client.MongoClients;
|
||||
@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<Object> findOperation;
|
||||
@@ -85,10 +92,14 @@ public class StringBasedMongoQueryUnitTests {
|
||||
|
||||
MongoConverter converter;
|
||||
|
||||
Map<String, Object> 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,10 +304,46 @@ public class StringBasedMongoQueryUnitTests {
|
||||
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 // 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() {
|
||||
|
||||
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2");
|
||||
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, true, "param1");
|
||||
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpressionAndNestedObject", boolean.class,
|
||||
String.class);
|
||||
|
||||
@@ -707,7 +754,9 @@ public class StringBasedMongoQueryUnitTests {
|
||||
ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
|
||||
MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class),
|
||||
factory, converter.getMappingContext());
|
||||
return new StringBasedMongoQuery(queryMethod, operations, PARSER, QueryMethodEvaluationContextProvider.DEFAULT);
|
||||
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);
|
||||
@@ -776,6 +825,12 @@ public class StringBasedMongoQueryUnitTests {
|
||||
@Query("{'lastname': ?#{[0]} }")
|
||||
List<Person> findByQueryWithExpression(String param0);
|
||||
|
||||
@Query("{'lastname': ?#{[0]}, 'firstname': ?${absent-property:some-default} }")
|
||||
List<Person> findByQueryWithExpressionAndProperty(String param0);
|
||||
|
||||
@Query("{'lastname': ?${foo} }")
|
||||
List<Person> findByQueryWithProperty();
|
||||
|
||||
@Query("{'id':?#{ [0] ? { $exists :true} : [1] }}")
|
||||
List<Person> findByQueryWithExpressionAndNestedObject(boolean param0, String param1);
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@ import org.bson.BsonBinarySubType;
|
||||
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.mapping.model.ValueExpressionEvaluator;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.data.spel.ExpressionDependencies;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
@@ -261,7 +264,7 @@ class ParameterBindingJsonReaderUnitTests {
|
||||
};
|
||||
|
||||
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"));
|
||||
@@ -309,7 +312,8 @@ class ParameterBindingJsonReaderUnitTests {
|
||||
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 +324,8 @@ class ParameterBindingJsonReaderUnitTests {
|
||||
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);
|
||||
@@ -343,7 +348,7 @@ class ParameterBindingJsonReaderUnitTests {
|
||||
|
||||
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"));
|
||||
@@ -358,7 +363,7 @@ class ParameterBindingJsonReaderUnitTests {
|
||||
|
||||
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"));
|
||||
@@ -373,7 +378,7 @@ class ParameterBindingJsonReaderUnitTests {
|
||||
|
||||
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"));
|
||||
@@ -391,7 +396,7 @@ class ParameterBindingJsonReaderUnitTests {
|
||||
+ ").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)
|
||||
@@ -406,7 +411,7 @@ class ParameterBindingJsonReaderUnitTests {
|
||||
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
|
||||
@@ -418,7 +423,7 @@ class ParameterBindingJsonReaderUnitTests {
|
||||
.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"));
|
||||
@@ -434,7 +439,7 @@ class ParameterBindingJsonReaderUnitTests {
|
||||
|
||||
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());
|
||||
});
|
||||
@@ -449,7 +454,7 @@ class ParameterBindingJsonReaderUnitTests {
|
||||
.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());
|
||||
|
||||
@@ -467,7 +472,7 @@ class ParameterBindingJsonReaderUnitTests {
|
||||
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)
|
||||
@@ -485,7 +490,7 @@ class ParameterBindingJsonReaderUnitTests {
|
||||
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));
|
||||
|
||||
Reference in New Issue
Block a user