Added support for Repository Query Method ValueExpressions.

Closes #1522
Original pull request: #1523
This commit is contained in:
Marcin Grzejszczak
2024-09-30 15:22:39 +02:00
committed by Mark Paluch
parent 77453ae9a0
commit 9ebd3db34f
10 changed files with 270 additions and 91 deletions

View File

@@ -19,7 +19,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mapping.model.ValueExpressionEvaluator;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.lang.Nullable;
@@ -39,13 +39,13 @@ class BindingContext {
private final List<ParameterBinding> bindings;
private final SpELExpressionEvaluator evaluator;
private final ValueExpressionEvaluator evaluator;
/**
* Create new {@link BindingContext}.
*/
public BindingContext(CassandraParameters parameters, ParameterAccessor parameterAccessor,
List<ParameterBinding> bindings, SpELExpressionEvaluator evaluator) {
BindingContext(CassandraParameters parameters, ParameterAccessor parameterAccessor,
List<ParameterBinding> bindings, ValueExpressionEvaluator evaluator) {
this.parameters = parameters;
this.parameterAccessor = parameterAccessor;

View File

@@ -32,7 +32,7 @@ import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.cassandra.repository.Query.Idempotency;
import org.springframework.data.domain.Limit;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mapping.model.ValueExpressionEvaluator;
import org.springframework.data.repository.query.QueryCreationException;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
@@ -249,7 +249,7 @@ class QueryStatementCreator {
* @return the {@link Statement}.
*/
SimpleStatement select(StringBasedQuery stringBasedQuery, CassandraParameterAccessor parameterAccessor,
SpELExpressionEvaluator evaluator) {
ValueExpressionEvaluator evaluator) {
try {

View File

@@ -17,11 +17,17 @@ package org.springframework.data.cassandra.repository.query;
import reactor.core.publisher.Mono;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.expression.ReactiveValueEvaluationContextProvider;
import org.springframework.data.expression.ValueEvaluationContextProvider;
import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.mapping.model.ValueExpressionEvaluator;
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.ValueExpressionDelegate;
import org.springframework.data.spel.ExpressionDependencies;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -51,8 +57,9 @@ public class ReactiveStringBasedCassandraQuery extends AbstractReactiveCassandra
private final boolean isExistsQuery;
private final ExpressionParser expressionParser;
private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider;
private final ValueExpressionDelegate delegate;
private final ReactiveValueEvaluationContextProvider valueEvaluationContextProvider;
/**
* Create a new {@link ReactiveStringBasedCassandraQuery} for the given {@link CassandraQueryMethod},
@@ -66,7 +73,9 @@ public class ReactiveStringBasedCassandraQuery extends AbstractReactiveCassandra
* {@link org.springframework.expression.spel.support.StandardEvaluationContext}.
* @see org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryMethod
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations
* @deprecated since 4.4, use the constructors accepting {@link ValueExpressionDelegate} instead.
*/
@Deprecated(since = "4.4")
public ReactiveStringBasedCassandraQuery(ReactiveCassandraQueryMethod queryMethod,
ReactiveCassandraOperations operations, ExpressionParser expressionParser,
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
@@ -86,19 +95,59 @@ public class ReactiveStringBasedCassandraQuery extends AbstractReactiveCassandra
* {@link org.springframework.expression.spel.support.StandardEvaluationContext}.
* @see org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryMethod
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations
* @deprecated since 4.4, use the constructors accepting {@link ValueExpressionDelegate} instead.
*/
@Deprecated(since = "4.4")
public ReactiveStringBasedCassandraQuery(String query, ReactiveCassandraQueryMethod method,
ReactiveCassandraOperations operations, ExpressionParser expressionParser,
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
this(query, method, operations, new ValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), evaluationContextProvider.getEvaluationContextProvider()), ValueExpressionParser.create(() -> expressionParser)));
}
/**
* Create a new {@link ReactiveStringBasedCassandraQuery} for the given {@link CassandraQueryMethod},
* {@link ReactiveCassandraOperations}, {@link ValueExpressionDelegate}
*
* @param queryMethod {@link ReactiveCassandraQueryMethod} on which this query is based.
* @param operations {@link ReactiveCassandraOperations} used to perform data access in Cassandra.
* @param delegate {@link ValueExpressionDelegate} used to parse expressions in the query.
* @see org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryMethod
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations
* @since 4.4
*/
public ReactiveStringBasedCassandraQuery(ReactiveCassandraQueryMethod queryMethod,
ReactiveCassandraOperations operations, ValueExpressionDelegate delegate) {
this(queryMethod.getRequiredAnnotatedQuery(), queryMethod, operations, delegate);
}
/**
* Create a new {@link ReactiveStringBasedCassandraQuery} for the given {@code query}, {@link CassandraQueryMethod},
* {@link ReactiveCassandraOperations}, {@link ValueExpressionDelegate}
*
* @param method {@link ReactiveCassandraQueryMethod} on which this query is based.
* @param operations {@link ReactiveCassandraOperations} used to perform data access in Cassandra.
* @param delegate {@link SpelExpressionParser} used to parse expressions in the query.
* @see org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryMethod
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations
* @since 4.4
*/
public ReactiveStringBasedCassandraQuery(String query, ReactiveCassandraQueryMethod method,
ReactiveCassandraOperations operations, ValueExpressionDelegate delegate) {
super(method, operations);
Assert.hasText(query, "Query must not be empty");
this.expressionParser = expressionParser;
this.evaluationContextProvider = evaluationContextProvider;
this.delegate = delegate;
this.stringBasedQuery = new StringBasedQuery(query, method.getParameters(), expressionParser);
this.stringBasedQuery = new StringBasedQuery(query, method.getParameters(), delegate);
ValueEvaluationContextProvider valueContextProvider = delegate.createValueContextProvider(
method.getParameters());
Assert.isInstanceOf(ReactiveValueEvaluationContextProvider.class, valueContextProvider, "ValueEvaluationContextProvider must be reactive");
this.valueEvaluationContextProvider = (ReactiveValueEvaluationContextProvider) valueContextProvider;
if (method.hasAnnotatedQuery()) {
@@ -126,10 +175,9 @@ public class ReactiveStringBasedCassandraQuery extends AbstractReactiveCassandra
StringBasedQuery query = getStringBasedQuery();
ConvertingParameterAccessor parameterAccessorToUse = new ConvertingParameterAccessor(
getReactiveCassandraOperations().getConverter(), parameterAccessor);
Mono<SpELExpressionEvaluator> spelEvaluator = getSpelEvaluatorFor(query.getExpressionDependencies(),
parameterAccessorToUse);
return spelEvaluator.map(it -> getQueryStatementCreator().select(query, parameterAccessorToUse, it));
return getValueExpressionEvaluatorLater(query.getExpressionDependencies(), parameterAccessor)
.map(it -> getQueryStatementCreator().select(query, parameterAccessorToUse, it));
}
@Override
@@ -152,21 +200,9 @@ public class ReactiveStringBasedCassandraQuery extends AbstractReactiveCassandra
return false;
}
/**
* Obtain a {@link Mono publisher} emitting the {@link SpELExpressionEvaluator} 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 SpELExpressionEvaluator} when ready.
*/
private Mono<SpELExpressionEvaluator> getSpelEvaluatorFor(ExpressionDependencies dependencies,
private Mono<ValueExpressionEvaluator> getValueExpressionEvaluatorLater(ExpressionDependencies dependencies,
CassandraParameterAccessor accessor) {
return evaluationContextProvider
.getEvaluationContextLater(getQueryMethod().getParameters(), accessor.getValues(), dependencies)
.map(evaluationContext -> (SpELExpressionEvaluator) new DefaultSpELExpressionEvaluator(expressionParser,
evaluationContext))
.defaultIfEmpty(DefaultSpELExpressionEvaluator.unsupported());
return valueEvaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies)
.map(evaluationContext -> new ValueExpressionDelegateValueExpressionEvaluator(delegate, valueExpression -> evaluationContext));
}
}

View File

@@ -15,10 +15,14 @@
*/
package org.springframework.data.cassandra.repository.query;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.expression.ValueEvaluationContext;
import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.expression.EvaluationContext;
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;
@@ -46,8 +50,7 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
private final boolean isExistsQuery;
private final ExpressionParser expressionParser;
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
private final ValueExpressionDelegate valueExpressionDelegate;
/**
* Create a new {@link StringBasedCassandraQuery} for the given {@link CassandraQueryMethod},
@@ -60,35 +63,52 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
* {@link org.springframework.expression.spel.support.StandardEvaluationContext}.
* @see org.springframework.data.cassandra.repository.query.CassandraQueryMethod
* @see org.springframework.data.cassandra.core.CassandraOperations
* @deprecated use the constructor version with {@link ValueExpressionDelegate}
*/
@Deprecated(since = "4.4")
public StringBasedCassandraQuery(CassandraQueryMethod queryMethod, CassandraOperations operations,
ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
this(queryMethod.getRequiredAnnotatedQuery(), queryMethod, operations, expressionParser, evaluationContextProvider);
}
/**
* Create a new {@link StringBasedCassandraQuery} for the given {@link CassandraQueryMethod},
* {@link CassandraOperations}, {@link ValueExpressionDelegate}.
*
* @param queryMethod {@link CassandraQueryMethod} on which this query is based.
* @param operations {@link CassandraOperations} used to perform data access in Cassandra.
* @param valueExpressionDelegate {@link ValueExpressionDelegate} used to parse expressions in the query.
* @see org.springframework.data.cassandra.repository.query.CassandraQueryMethod
* @see org.springframework.data.cassandra.core.CassandraOperations
* @since 4.4
*/
public StringBasedCassandraQuery(CassandraQueryMethod queryMethod, CassandraOperations operations,
ValueExpressionDelegate valueExpressionDelegate) {
this(queryMethod.getRequiredAnnotatedQuery(), queryMethod, operations, valueExpressionDelegate);
}
/**
* Create a new {@link StringBasedCassandraQuery} for the given {@code query}, {@link CassandraQueryMethod},
* {@link CassandraOperations}, {@link SpelExpressionParser}, and {@link QueryMethodEvaluationContextProvider}.
* {@link CassandraOperations}, {@link ValueExpressionDelegate}.
*
* @param query {@link String} containing the Apache Cassandra CQL query to execute.
* @param method {@link CassandraQueryMethod} on which this query is based.
* @param operations {@link CassandraOperations} used to perform data access in Cassandra.
* @param expressionParser {@link SpelExpressionParser} used to parse expressions in the query.
* @param evaluationContextProvider {@link QueryMethodEvaluationContextProvider} used to access the potentially shared
* {@link org.springframework.expression.spel.support.StandardEvaluationContext}.
* @param valueExpressionDelegate {@link ValueExpressionDelegate} used to parse expressions in the query.
* @see org.springframework.data.cassandra.repository.query.CassandraQueryMethod
* @see org.springframework.data.cassandra.core.CassandraOperations
* @since 4.4
*/
public StringBasedCassandraQuery(String query, CassandraQueryMethod method, CassandraOperations operations,
ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
ValueExpressionDelegate valueExpressionDelegate) {
super(method, operations);
this.expressionParser = expressionParser;
this.evaluationContextProvider = evaluationContextProvider;
this.valueExpressionDelegate = valueExpressionDelegate;
this.stringBasedQuery = new StringBasedQuery(query, method.getParameters(), expressionParser);
this.stringBasedQuery = new StringBasedQuery(query, method.getParameters(), valueExpressionDelegate);
if (method.hasAnnotatedQuery()) {
@@ -106,6 +126,26 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
}
}
/**
* Create a new {@link StringBasedCassandraQuery} for the given {@code query}, {@link CassandraQueryMethod},
* {@link CassandraOperations}, {@link SpelExpressionParser}, and {@link QueryMethodEvaluationContextProvider}.
*
* @param query {@link String} containing the Apache Cassandra CQL query to execute.
* @param method {@link CassandraQueryMethod} on which this query is based.
* @param operations {@link CassandraOperations} used to perform data access in Cassandra.
* @param expressionParser {@link SpelExpressionParser} used to parse expressions in the query.
* @param evaluationContextProvider {@link QueryMethodEvaluationContextProvider} used to access the potentially shared
* {@link org.springframework.expression.spel.support.StandardEvaluationContext}.
* @see org.springframework.data.cassandra.repository.query.CassandraQueryMethod
* @see org.springframework.data.cassandra.core.CassandraOperations
* @deprecated use the constructor version with {@link ValueExpressionDelegate}
*/
@Deprecated(since = "4.4")
public StringBasedCassandraQuery(String query, CassandraQueryMethod method, CassandraOperations operations,
ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
this(query, method, operations, new ValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), evaluationContextProvider.getEvaluationContextProvider()), ValueExpressionParser.create(() -> expressionParser)));
}
protected StringBasedQuery getStringBasedQuery() {
return this.stringBasedQuery;
}
@@ -116,11 +156,10 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
StringBasedQuery query = getStringBasedQuery();
ConvertingParameterAccessor parameterAccessorToUse = new ConvertingParameterAccessor(getOperations().getConverter(),
parameterAccessor);
EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(
getQueryMethod().getParameters(), parameterAccessorToUse.getValues(), query.getExpressionDependencies());
ValueEvaluationContext evaluationContext = valueExpressionDelegate.createValueContextProvider(
getQueryMethod().getParameters()).getEvaluationContext(parameterAccessorToUse.getValues(), query.getExpressionDependencies());
return getQueryStatementCreator().select(query, parameterAccessorToUse,
new DefaultSpELExpressionEvaluator(expressionParser, evaluationContext));
return getQueryStatementCreator().select(query, parameterAccessorToUse, new ValueExpressionDelegateValueExpressionEvaluator(valueExpressionDelegate, valueExpression -> evaluationContext));
}
@Override

View File

@@ -17,14 +17,15 @@ package org.springframework.data.cassandra.repository.query;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.data.cassandra.repository.query.BindingContext.ParameterBinding;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mapping.model.ValueExpressionEvaluator;
import org.springframework.data.repository.query.ValueExpressionDelegate;
import org.springframework.data.spel.ExpressionDependencies;
import org.springframework.expression.ExpressionParser;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -43,21 +44,20 @@ class StringBasedQuery {
private final CassandraParameters parameters;
private final ExpressionParser expressionParser;
private final ValueExpressionDelegate expressionParser;
private final List<ParameterBinding> queryParameterBindings = new ArrayList<>();
private final ExpressionDependencies expressionDependencies;
/**
* Create a new {@link StringBasedQuery} given {@code query}, {@link CassandraParameters} and
* {@link ExpressionParser}.
* Create a new {@link StringBasedQuery} given {@code query}, {@link CassandraParameters} and {@link ValueExpressionDelegate}.
*
* @param query must not be empty.
* @param parameters must not be {@literal null}.
* @param expressionParser must not be {@literal null}.
*/
StringBasedQuery(String query, CassandraParameters parameters, ExpressionParser expressionParser) {
StringBasedQuery(String query, CassandraParameters parameters, ValueExpressionDelegate expressionParser) {
this.query = ParameterBindingParser.INSTANCE.parseAndCollectParameterBindingsFromQueryIntoBindings(query,
this.queryParameterBindings);
@@ -77,7 +77,7 @@ class StringBasedQuery {
for (ParameterBinding binding : queryParameterBindings) {
if (binding.isExpression()) {
dependencies
.add(ExpressionDependencies.discover(expressionParser.parseExpression(binding.getRequiredExpression())));
.add(expressionParser.parse(binding.getRequiredExpression()).getExpressionDependencies());
}
}
@@ -100,7 +100,7 @@ class StringBasedQuery {
* @param evaluator must not be {@literal null}.
* @return the bound String query containing formatted parameters.
*/
public SimpleStatement bindQuery(CassandraParameterAccessor parameterAccessor, SpELExpressionEvaluator evaluator) {
SimpleStatement bindQuery(CassandraParameterAccessor parameterAccessor, ValueExpressionEvaluator evaluator) {
Assert.notNull(parameterAccessor, "CassandraParameterAccessor must not be null");
Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null");
@@ -176,6 +176,10 @@ class StringBasedQuery {
private static final Pattern NAMED_PARAMETER_BINDING_PATTERN = Pattern.compile("\\:(\\w+)");
private static final Pattern INDEX_BASED_EXPRESSION_PATTERN = Pattern.compile("\\?\\#\\{");
private static final Pattern NAME_BASED_EXPRESSION_PATTERN = Pattern.compile("\\:\\#\\{");
private static final Pattern INDEX_BASED_PROPERTY_PLACEHOLDER_PATTERN = Pattern.compile("\\?\\$\\{");
private static final Pattern NAME_BASED_PROPERTY_PLACEHOLDER_PATTERN = Pattern.compile("\\:\\$\\{");
private static final Set<Pattern> VALUE_EXPRESSION_PATTERNS = Set.of(INDEX_BASED_EXPRESSION_PATTERN, NAME_BASED_EXPRESSION_PATTERN, INDEX_BASED_PROPERTY_PLACEHOLDER_PATTERN, NAME_BASED_PROPERTY_PLACEHOLDER_PATTERN);
private static final String ARGUMENT_PLACEHOLDER = "?_param_?";
@@ -217,7 +221,7 @@ class StringBasedQuery {
int exprStart = matcher.start();
currentPosition = exprStart;
if (matcher.pattern() == NAME_BASED_EXPRESSION_PATTERN || matcher.pattern() == INDEX_BASED_EXPRESSION_PATTERN) {
if (isValueExpression(matcher)) {
// eat parameter expression
int curlyBraceOpenCount = 1;
currentPosition += 3;
@@ -233,7 +237,6 @@ class StringBasedQuery {
default:
}
}
result.append(input.subSequence(startIndex, exprStart));
} else {
result.append(input.subSequence(startIndex, exprStart));
@@ -241,10 +244,10 @@ class StringBasedQuery {
result.append(ARGUMENT_PLACEHOLDER);
if (matcher.pattern() == NAME_BASED_EXPRESSION_PATTERN || matcher.pattern() == INDEX_BASED_EXPRESSION_PATTERN) {
if (isValueExpression(matcher)) {
bindings.add(
BindingContext.ParameterBinding
.expression(input.substring(exprStart + 3, currentPosition - 1), true));
.expression(input.substring(exprStart + 1, currentPosition), true));
} else {
if (matcher.pattern() == INDEX_PARAMETER_BINDING_PATTERN) {
bindings
@@ -262,6 +265,10 @@ class StringBasedQuery {
return result.append(input.subSequence(currentPosition, input.length())).toString();
}
private static boolean isValueExpression(Matcher matcher) {
return VALUE_EXPRESSION_PATTERNS.contains(matcher.pattern());
}
@Nullable
private static Matcher findNextBindingOrExpression(String input, int position) {
@@ -271,6 +278,8 @@ class StringBasedQuery {
matchers.add(NAMED_PARAMETER_BINDING_PATTERN.matcher(input));
matchers.add(INDEX_BASED_EXPRESSION_PATTERN.matcher(input));
matchers.add(NAME_BASED_EXPRESSION_PATTERN.matcher(input));
matchers.add(INDEX_BASED_PROPERTY_PLACEHOLDER_PATTERN.matcher(input));
matchers.add(NAME_BASED_PROPERTY_PLACEHOLDER_PATTERN.matcher(input));
TreeMap<Integer, Matcher> matcherMap = new TreeMap<>();

View File

@@ -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.cassandra.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));
}
}

View File

@@ -19,6 +19,7 @@ import java.lang.reflect.Method;
import java.util.Optional;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
@@ -27,6 +28,7 @@ import org.springframework.data.cassandra.repository.query.CassandraEntityInform
import org.springframework.data.cassandra.repository.query.CassandraQueryMethod;
import org.springframework.data.cassandra.repository.query.PartTreeCassandraQuery;
import org.springframework.data.cassandra.repository.query.StringBasedCassandraQuery;
import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.NamedQueries;
@@ -36,8 +38,9 @@ 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.data.repository.query.ValueExpressionDelegate;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -53,7 +56,7 @@ import org.springframework.util.Assert;
*/
public class CassandraRepositoryFactory extends RepositoryFactorySupport {
private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
private static final ValueExpressionParser EXPRESSION_PARSER = ValueExpressionParser.create(SpelExpressionParser::new);
private final MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext;
@@ -99,29 +102,31 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
return new MappingCassandraEntityInformation<>((CassandraPersistentEntity<T>) entity, operations.getConverter());
}
@Override protected Optional<QueryLookupStrategy> getQueryLookupStrategy(Key key,
ValueExpressionDelegate valueExpressionDelegate) {
return Optional.of(new CassandraQueryLookupStrategy(operations, valueExpressionDelegate, mappingContext));
}
@Override
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
return Optional.of(new CassandraQueryLookupStrategy(operations, evaluationContextProvider, mappingContext));
return Optional.of(new CassandraQueryLookupStrategy(operations, new ValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), evaluationContextProvider.getEvaluationContextProvider()), EXPRESSION_PARSER), mappingContext));
}
private static class CassandraQueryLookupStrategy implements QueryLookupStrategy {
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
private final ValueExpressionDelegate valueExpressionDelegate;
private final MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext;
private final CassandraOperations operations;
private final ExpressionParser expressionParser = new CachingExpressionParser(EXPRESSION_PARSER);
CassandraQueryLookupStrategy(CassandraOperations operations,
QueryMethodEvaluationContextProvider evaluationContextProvider,
ValueExpressionDelegate valueExpressionDelegate,
MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext) {
this.operations = operations;
this.evaluationContextProvider = evaluationContextProvider;
this.valueExpressionDelegate = valueExpressionDelegate;
this.mappingContext = mappingContext;
}
@@ -134,10 +139,9 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
if (namedQueries.hasQuery(namedQueryName)) {
String namedQuery = namedQueries.getQuery(namedQueryName);
return new StringBasedCassandraQuery(namedQuery, queryMethod, operations, expressionParser,
evaluationContextProvider);
return new StringBasedCassandraQuery(namedQuery, queryMethod, operations, valueExpressionDelegate);
} else if (queryMethod.hasAnnotatedQuery()) {
return new StringBasedCassandraQuery(queryMethod, operations, expressionParser, evaluationContextProvider);
return new StringBasedCassandraQuery(queryMethod, operations, valueExpressionDelegate);
} else {
return new PartTreeCassandraQuery(queryMethod, operations);
}

View File

@@ -19,6 +19,7 @@ import java.lang.reflect.Method;
import java.util.Optional;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
@@ -26,6 +27,7 @@ import org.springframework.data.cassandra.repository.query.CassandraEntityInform
import org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryMethod;
import org.springframework.data.cassandra.repository.query.ReactivePartTreeCassandraQuery;
import org.springframework.data.cassandra.repository.query.ReactiveStringBasedCassandraQuery;
import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.NamedQueries;
@@ -35,9 +37,10 @@ import org.springframework.data.repository.core.support.ReactiveRepositoryFactor
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.data.repository.query.ValueExpressionDelegate;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -50,7 +53,7 @@ import org.springframework.util.Assert;
*/
public class ReactiveCassandraRepositoryFactory extends ReactiveRepositoryFactorySupport {
private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
private static final ValueExpressionParser EXPRESSION_PARSER = ValueExpressionParser.create(SpelExpressionParser::new);
private final ReactiveCassandraOperations operations;
@@ -89,11 +92,15 @@ public class ReactiveCassandraRepositoryFactory extends ReactiveRepositoryFactor
return getTargetRepositoryViaReflection(information, entityInformation, operations);
}
@Override protected Optional<QueryLookupStrategy> getQueryLookupStrategy(Key key,
ValueExpressionDelegate valueExpressionDelegate) {
return Optional.of(new CassandraQueryLookupStrategy(operations, valueExpressionDelegate, mappingContext));
}
@Override
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
return Optional.of(new CassandraQueryLookupStrategy(operations,
(ReactiveQueryMethodEvaluationContextProvider) evaluationContextProvider, mappingContext));
return Optional.of(new CassandraQueryLookupStrategy(operations, new ValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), evaluationContextProvider.getEvaluationContextProvider()), EXPRESSION_PARSER), mappingContext));
}
@SuppressWarnings("unchecked")
@@ -112,19 +119,17 @@ public class ReactiveCassandraRepositoryFactory extends ReactiveRepositoryFactor
*/
private static class CassandraQueryLookupStrategy implements QueryLookupStrategy {
private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider;
private final ValueExpressionDelegate delegate;
private final ReactiveCassandraOperations operations;
private final MappingContext<? extends CassandraPersistentEntity<?>, ? extends CassandraPersistentProperty> mappingContext;
private final ExpressionParser expressionParser = new CachingExpressionParser(EXPRESSION_PARSER);
CassandraQueryLookupStrategy(ReactiveCassandraOperations operations,
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider,
ValueExpressionDelegate delegate,
MappingContext<? extends CassandraPersistentEntity<?>, ? extends CassandraPersistentProperty> mappingContext) {
this.evaluationContextProvider = evaluationContextProvider;
this.delegate = delegate;
this.operations = operations;
this.mappingContext = mappingContext;
}
@@ -141,11 +146,9 @@ public class ReactiveCassandraRepositoryFactory extends ReactiveRepositoryFactor
if (namedQueries.hasQuery(namedQueryName)) {
String namedQuery = namedQueries.getQuery(namedQueryName);
return new ReactiveStringBasedCassandraQuery(namedQuery, queryMethod, operations, expressionParser,
evaluationContextProvider);
return new ReactiveStringBasedCassandraQuery(namedQuery, queryMethod, operations, delegate);
} else if (queryMethod.hasAnnotatedQuery()) {
return new ReactiveStringBasedCassandraQuery(queryMethod, operations, expressionParser,
evaluationContextProvider);
return new ReactiveStringBasedCassandraQuery(queryMethod, operations, delegate);
} else {
return new ReactivePartTreeCassandraQuery(queryMethod, operations);
}

View File

@@ -28,6 +28,7 @@ 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.springframework.data.cassandra.ReactiveSession;
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
@@ -37,16 +38,20 @@ import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.domain.Person;
import org.springframework.data.cassandra.repository.Consistency;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
import org.springframework.data.repository.query.ReactiveExtensionAwareQueryMethodEvaluationContextProvider;
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;
import org.springframework.lang.Nullable;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.util.ReflectionUtils;
import com.datastax.oss.driver.api.core.DefaultConsistencyLevel;
@@ -60,7 +65,7 @@ import com.datastax.oss.driver.api.core.cql.SimpleStatement;
@ExtendWith(MockitoExtension.class)
class ReactiveStringBasedCassandraQueryUnitTests {
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
private static final ValueExpressionParser PARSER = ValueExpressionParser.create(SpelExpressionParser::new);
@Mock private ReactiveCassandraOperations operations;
@Mock private ReactiveCqlOperations cqlOperations;
@@ -69,6 +74,7 @@ class ReactiveStringBasedCassandraQueryUnitTests {
private MappingCassandraConverter converter;
private ProjectionFactory factory;
private RepositoryMetadata metadata;
private MockEnvironment mockEnvironment = new MockEnvironment();
@BeforeEach
@SuppressWarnings("unchecked")
@@ -143,6 +149,20 @@ class ReactiveStringBasedCassandraQueryUnitTests {
assertThat(actual.getPositionalValues().get(0)).isEqualTo("Walter");
}
@Test // GH-1522
void shouldUsePropertyPlaceholder() {
mockEnvironment.withProperty("someProp", "Walter");
ReactiveStringBasedCassandraQuery cassandraQuery = getQueryMethod("findByPropertyPlaceholder");
CassandraParametersParameterAccessor parameterAccessor = new CassandraParametersParameterAccessor(
cassandraQuery.getQueryMethod());
SimpleStatement actual = cassandraQuery.createQuery(parameterAccessor).block();
assertThat(actual.getQuery()).isEqualTo("SELECT * FROM person WHERE lastname=?;");
assertThat(actual.getPositionalValues().get(0)).isEqualTo("Walter");
}
private ReactiveStringBasedCassandraQuery getQueryMethod(String name, Class<?>... args) {
Method method = ReflectionUtils.findMethod(SampleRepository.class, name, args);
@@ -150,11 +170,10 @@ class ReactiveStringBasedCassandraQueryUnitTests {
ReactiveCassandraQueryMethod queryMethod = new ReactiveCassandraQueryMethod(method, metadata, factory,
converter.getMappingContext());
ReactiveExtensionAwareQueryMethodEvaluationContextProvider provider = new ReactiveExtensionAwareQueryMethodEvaluationContextProvider(
Arrays.asList(MyReactiveExtension.INSTANCE, MyDefunctExtension.INSTANCE));
QueryMethodValueEvaluationContextAccessor accessor = new QueryMethodValueEvaluationContextAccessor(
mockEnvironment, Arrays.asList(MyReactiveExtension.INSTANCE, MyDefunctExtension.INSTANCE));
return new ReactiveStringBasedCassandraQuery(queryMethod, operations, PARSER,
provider);
return new ReactiveStringBasedCassandraQuery(queryMethod, operations, new ValueExpressionDelegate(accessor, PARSER));
}
@SuppressWarnings("unused")
@@ -170,6 +189,9 @@ class ReactiveStringBasedCassandraQueryUnitTests {
@Query("SELECT * FROM person WHERE lastname=:#{getName()};")
Person findBySpel();
@Query("SELECT * FROM person WHERE lastname=:${someProp};")
Person findByPropertyPlaceholder();
}
public static class MyReactiveExtensionObject implements EvaluationContextExtension {

View File

@@ -25,6 +25,7 @@ import java.nio.ByteBuffer;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -33,6 +34,7 @@ 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.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.cql.QueryOptions;
@@ -43,15 +45,18 @@ import org.springframework.data.cassandra.domain.Person;
import org.springframework.data.cassandra.repository.Consistency;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.cassandra.support.UserDefinedTypeBuilder;
import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
import org.springframework.data.repository.query.ExtensionAwareQueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.Param;
import org.springframework.data.repository.query.QueryCreationException;
import org.springframework.data.repository.query.QueryMethodValueEvaluationContextAccessor;
import org.springframework.data.repository.query.ValueExpressionDelegate;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.util.ReflectionUtils;
import com.datastax.oss.driver.api.core.CqlIdentifier;
@@ -71,7 +76,7 @@ import com.datastax.oss.driver.api.core.type.UserDefinedType;
@ExtendWith(MockitoExtension.class)
class StringBasedCassandraQueryUnitTests {
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
private static final ValueExpressionParser PARSER = ValueExpressionParser.create(SpelExpressionParser::new);
@Mock private CassandraOperations operations;
@Mock private UdtValue udtValue;
@@ -80,6 +85,7 @@ class StringBasedCassandraQueryUnitTests {
private RepositoryMetadata metadata;
private MappingCassandraConverter converter;
private ProjectionFactory factory;
private MockEnvironment environment = new MockEnvironment();
@BeforeEach
void setUp() {
@@ -293,6 +299,20 @@ class StringBasedCassandraQueryUnitTests {
assertThat(actual.getPositionalValues().get(0)).isEqualTo("Walter");
}
@Test // GH-1522
void bindsPropertyPlaceholderParameterCorrectly() {
environment.withProperty("someParam", "Walter");
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByPropertyPlaceholder");
CassandraParametersParameterAccessor accessor = new CassandraParametersParameterAccessor(
cassandraQuery.getQueryMethod());
SimpleStatement actual = cassandraQuery.createQuery(accessor);
assertThat(actual.getQuery()).isEqualTo("SELECT * FROM person WHERE lastname = ?;");
assertThat(actual.getPositionalValues().get(0)).isEqualTo("Walter");
}
@Test // DATACASS-117
void bindsReusedParametersCorrectly() {
@@ -431,8 +451,10 @@ class StringBasedCassandraQueryUnitTests {
CassandraQueryMethod queryMethod = new CassandraQueryMethod(method, metadata, factory,
converter.getMappingContext());
return new StringBasedCassandraQuery(queryMethod, operations, PARSER,
ExtensionAwareQueryMethodEvaluationContextProvider.DEFAULT);
QueryMethodValueEvaluationContextAccessor accessor = new QueryMethodValueEvaluationContextAccessor(
environment, Collections.emptySet());
return new StringBasedCassandraQuery(queryMethod, operations, new ValueExpressionDelegate(accessor, PARSER));
}
@SuppressWarnings("unused")
@@ -480,6 +502,9 @@ class StringBasedCassandraQueryUnitTests {
@Query("SELECT * FROM person WHERE lastname = :#{#lastname == 'Matthews' ? 'Woohoo' : #lastname};")
Person findByConditionalExpressionParameter(@Param("lastname") String lastname);
@Query("SELECT * FROM person WHERE lastname = :${someParam};")
Person findByPropertyPlaceholder();
@Query("SELECT * FROM person WHERE lastname=?0 AND firstname=?1;")
Person findByLastnameAndFirstname(String lastname, String firstname);