DATACASS-788 - Add support for reactive SpEL context extensions.
We now support context extensions that contribute contextual details using a reactive programming model for evaluation of SpEL expressions in @Query methods.
This commit is contained in:
@@ -38,7 +38,6 @@ import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
|
||||
import com.datastax.oss.driver.api.core.cql.Statement;
|
||||
|
||||
/**
|
||||
* Base class for reactive {@link RepositoryQuery} implementations for Cassandra.
|
||||
@@ -81,18 +80,10 @@ public abstract class AbstractReactiveCassandraQuery extends CassandraRepository
|
||||
*/
|
||||
@Override
|
||||
public Object execute(Object[] parameters) {
|
||||
|
||||
return getQueryMethod().hasReactiveWrapperParameter() ? executeDeferred(parameters) : executeNow(parameters);
|
||||
return Flux.defer(() -> executeLater(parameters));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object executeDeferred(Object[] parameters) {
|
||||
|
||||
return getQueryMethod().isCollectionQuery() ? Flux.defer(() -> (Publisher<Object>) execute(parameters))
|
||||
: Mono.defer(() -> (Mono<Object>) execute(parameters));
|
||||
}
|
||||
|
||||
private Object executeNow(Object[] parameters) {
|
||||
private Publisher<Object> executeLater(Object[] parameters) {
|
||||
|
||||
ReactiveCassandraParameterAccessor parameterAccessor = new ReactiveCassandraParameterAccessor(getQueryMethod(),
|
||||
parameters);
|
||||
@@ -100,7 +91,7 @@ public abstract class AbstractReactiveCassandraQuery extends CassandraRepository
|
||||
CassandraParameterAccessor convertingParameterAccessor = new ConvertingParameterAccessor(
|
||||
getRequiredConverter(getReactiveCassandraOperations()), parameterAccessor);
|
||||
|
||||
Statement<?> statement = createQuery(convertingParameterAccessor);
|
||||
Mono<SimpleStatement> statement = createQuery(convertingParameterAccessor);
|
||||
|
||||
ResultProcessor resultProcessor = getQueryMethod().getResultProcessor()
|
||||
.withDynamicProjection(convertingParameterAccessor);
|
||||
@@ -110,7 +101,7 @@ public abstract class AbstractReactiveCassandraQuery extends CassandraRepository
|
||||
|
||||
Class<?> resultType = resolveResultType(resultProcessor);
|
||||
|
||||
return queryExecution.execute(statement, resultType);
|
||||
return statement.flatMapMany(it -> queryExecution.execute(it, resultType));
|
||||
}
|
||||
|
||||
private Class<?> resolveResultType(ResultProcessor resultProcessor) {
|
||||
@@ -126,7 +117,7 @@ public abstract class AbstractReactiveCassandraQuery extends CassandraRepository
|
||||
*
|
||||
* @param accessor must not be {@literal null}.
|
||||
*/
|
||||
protected abstract SimpleStatement createQuery(CassandraParameterAccessor accessor);
|
||||
protected abstract Mono<SimpleStatement> createQuery(CassandraParameterAccessor accessor);
|
||||
|
||||
protected ReactiveCassandraOperations getReactiveCassandraOperations() {
|
||||
return this.operations;
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright 2020 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.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Value object capturing the binding context to provide {@link #getBindingValues() binding values} for queries.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.5
|
||||
*/
|
||||
class BindingContext {
|
||||
|
||||
private final CassandraParameters parameters;
|
||||
|
||||
private final ParameterAccessor parameterAccessor;
|
||||
|
||||
private final List<ParameterBinding> bindings;
|
||||
|
||||
private final SpELExpressionEvaluator evaluator;
|
||||
|
||||
/**
|
||||
* Create new {@link BindingContext}.
|
||||
*/
|
||||
public BindingContext(CassandraParameters parameters, ParameterAccessor parameterAccessor,
|
||||
List<ParameterBinding> bindings, SpELExpressionEvaluator evaluator) {
|
||||
|
||||
this.parameters = parameters;
|
||||
this.parameterAccessor = parameterAccessor;
|
||||
this.bindings = bindings;
|
||||
this.evaluator = evaluator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} when list of bindings is not empty.
|
||||
*/
|
||||
private boolean hasBindings() {
|
||||
return !bindings.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind values provided by {@link CassandraParameterAccessor} to placeholders in {@link BindingContext} while
|
||||
* considering potential conversions and parameter types.
|
||||
*
|
||||
* @return {@literal null} if given {@code raw} value is empty.
|
||||
*/
|
||||
public List<Object> getBindingValues() {
|
||||
|
||||
if (!hasBindings()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Object> parameters = new ArrayList<>(bindings.size());
|
||||
|
||||
for (ParameterBinding binding : bindings) {
|
||||
Object parameterValueForBinding = getParameterValueForBinding(binding);
|
||||
parameters.add(parameterValueForBinding);
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value to be used for the given {@link ParameterBinding}.
|
||||
*
|
||||
* @param binding must not be {@literal null}.
|
||||
* @return the value used for the given {@link ParameterBinding}.
|
||||
*/
|
||||
@Nullable
|
||||
private Object getParameterValueForBinding(ParameterBinding binding) {
|
||||
|
||||
if (binding.isExpression()) {
|
||||
return evaluator.evaluate(binding.getRequiredExpression());
|
||||
}
|
||||
|
||||
return binding.isNamed()
|
||||
? parameterAccessor.getBindableValue(getParameterIndex(parameters, binding.getRequiredParameterName()))
|
||||
: parameterAccessor.getBindableValue(binding.getParameterIndex());
|
||||
}
|
||||
|
||||
private int getParameterIndex(CassandraParameters parameters, String parameterName) {
|
||||
|
||||
return parameters.stream() //
|
||||
.filter(cassandraParameter -> cassandraParameter //
|
||||
.getName().filter(s -> s.equals(parameterName)) //
|
||||
.isPresent()) //
|
||||
.mapToInt(Parameter::getIndex) //
|
||||
.findFirst() //
|
||||
.orElseThrow(() -> new IllegalArgumentException(
|
||||
String.format("Invalid parameter name; Cannot resolve parameter [%s]", parameterName)));
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic parameter binding with name or position information.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
static class ParameterBinding {
|
||||
|
||||
private final int parameterIndex;
|
||||
private final @Nullable String expression;
|
||||
private final @Nullable String parameterName;
|
||||
|
||||
private ParameterBinding(int parameterIndex, @Nullable String expression, @Nullable String parameterName) {
|
||||
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.expression = expression;
|
||||
this.parameterName = parameterName;
|
||||
}
|
||||
|
||||
static ParameterBinding expression(String expression, boolean quoted) {
|
||||
return new ParameterBinding(-1, expression, null);
|
||||
}
|
||||
|
||||
static ParameterBinding indexed(int parameterIndex) {
|
||||
return new ParameterBinding(parameterIndex, null, null);
|
||||
}
|
||||
|
||||
static ParameterBinding named(String name) {
|
||||
return new ParameterBinding(-1, null, name);
|
||||
}
|
||||
|
||||
boolean isNamed() {
|
||||
return (parameterName != null);
|
||||
}
|
||||
|
||||
int getParameterIndex() {
|
||||
return parameterIndex;
|
||||
}
|
||||
|
||||
String getParameter() {
|
||||
return ("?" + (isExpression() ? "expr" : "") + parameterIndex);
|
||||
}
|
||||
|
||||
String getRequiredExpression() {
|
||||
|
||||
Assert.state(expression != null, "ParameterBinding is not an expression");
|
||||
return expression;
|
||||
}
|
||||
|
||||
boolean isExpression() {
|
||||
return (this.expression != null);
|
||||
}
|
||||
|
||||
String getRequiredParameterName() {
|
||||
|
||||
Assert.state(parameterName != null, "ParameterBinding is not named");
|
||||
|
||||
return parameterName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2020 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 org.springframework.data.mapping.model.SpELExpressionEvaluator;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
|
||||
/**
|
||||
* Simple {@link SpELExpressionEvaluator} implementation using {@link ExpressionParser} and {@link EvaluationContext}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.1
|
||||
*/
|
||||
class DefaultSpELExpressionEvaluator implements SpELExpressionEvaluator {
|
||||
|
||||
private final ExpressionParser parser;
|
||||
|
||||
private final EvaluationContext context;
|
||||
|
||||
DefaultSpELExpressionEvaluator(ExpressionParser parser, EvaluationContext context) {
|
||||
this.parser = parser;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link SpELExpressionEvaluator} that does not support expression evaluation.
|
||||
*
|
||||
* @return a {@link SpELExpressionEvaluator} that does not support expression evaluation.
|
||||
*/
|
||||
public static SpELExpressionEvaluator unsupported() {
|
||||
return NoOpExpressionEvaluator.INSTANCE;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.model.SpELExpressionEvaluator#evaluate(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T evaluate(String expression) {
|
||||
return (T) parser.parseExpression(expression).getValue(context, Object.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SpELExpressionEvaluator} that does not support SpEL evaluation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
enum NoOpExpressionEvaluator implements SpELExpressionEvaluator {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public <T> T evaluate(String expression) {
|
||||
throw new UnsupportedOperationException("Expression evaluation not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2020 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.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* {@link ExpressionEvaluatingParameterBinder} allows to evaluate, convert and bind parameters to placeholders within a
|
||||
* {@link String}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.5
|
||||
*/
|
||||
class ExpressionEvaluatingParameterBinder {
|
||||
|
||||
private final SpelExpressionParser expressionParser;
|
||||
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
|
||||
/**
|
||||
* Creates new {@link ExpressionEvaluatingParameterBinder}
|
||||
*
|
||||
* @param expressionParser must not be {@literal null}.
|
||||
* @param evaluationContextProvider must not be {@literal null}.
|
||||
*/
|
||||
ExpressionEvaluatingParameterBinder(SpelExpressionParser expressionParser,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
Assert.notNull(expressionParser, "ExpressionParser must not be null");
|
||||
Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null");
|
||||
|
||||
this.expressionParser = expressionParser;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind values provided by {@link CassandraParameterAccessor} to placeholders in {@link BindingContext} while
|
||||
* considering potential conversions and parameter types.
|
||||
*
|
||||
* @param parameterAccessor must not be {@literal null}.
|
||||
* @param bindingContext must not be {@literal null}.
|
||||
* @return {@literal null} if given {@code raw} value is empty.
|
||||
*/
|
||||
public List<Object> bind(CassandraParameterAccessor parameterAccessor, BindingContext bindingContext) {
|
||||
|
||||
if (!bindingContext.hasBindings()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Object> parameters = new ArrayList<>(bindingContext.getBindings().size());
|
||||
|
||||
bindingContext.getBindings() //
|
||||
.stream() //
|
||||
.map(binding -> getParameterValueForBinding(parameterAccessor, bindingContext.getParameters(), binding)) //
|
||||
.forEach(parameters::add);
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value to be used for the given {@link ParameterBinding}.
|
||||
*
|
||||
* @param parameterAccessor must not be {@literal null}.
|
||||
* @param parameters must not be {@literal null}.
|
||||
* @param binding must not be {@literal null}.
|
||||
* @return the value used for the given {@link ParameterBinding}.
|
||||
*/
|
||||
@Nullable
|
||||
private Object getParameterValueForBinding(CassandraParameterAccessor parameterAccessor,
|
||||
CassandraParameters parameters, ParameterBinding binding) {
|
||||
|
||||
if (binding.isExpression()) {
|
||||
return evaluateExpression(binding.getExpression(), parameters, parameterAccessor.getValues());
|
||||
}
|
||||
|
||||
return binding.isNamed()
|
||||
? parameterAccessor.getBindableValue(getParameterIndex(parameters, binding.getParameterName()))
|
||||
: parameterAccessor.getBindableValue(binding.getParameterIndex());
|
||||
}
|
||||
|
||||
private int getParameterIndex(CassandraParameters parameters, String parameterName) {
|
||||
|
||||
return parameters.stream() //
|
||||
.filter(cassandraParameter -> cassandraParameter //
|
||||
.getName().filter(s -> s.equals(parameterName)) //
|
||||
.isPresent()) //
|
||||
.mapToInt(Parameter::getIndex) //
|
||||
.findFirst() //
|
||||
.orElseThrow(() -> new IllegalArgumentException(
|
||||
String.format("Invalid parameter name; Cannot resolve parameter [%s]", parameterName)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the given {@code expressionString}.
|
||||
*
|
||||
* @param expressionString must not be {@literal null} or empty.
|
||||
* @param parameters must not be {@literal null}.
|
||||
* @param parameterValues must not be {@literal null}.
|
||||
* @return the value of the {@code expressionString} evaluation.
|
||||
*/
|
||||
@Nullable
|
||||
private Object evaluateExpression(String expressionString, CassandraParameters parameters, Object[] parameterValues) {
|
||||
|
||||
EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(parameters, parameterValues);
|
||||
Expression expression = expressionParser.parseExpression(expressionString);
|
||||
|
||||
return expression.getValue(evaluationContext, Object.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
* @since 1.5
|
||||
*/
|
||||
static class BindingContext {
|
||||
|
||||
final CassandraQueryMethod queryMethod;
|
||||
final List<ParameterBinding> bindings;
|
||||
|
||||
/**
|
||||
* Creates new {@link BindingContext}.
|
||||
*
|
||||
* @param queryMethod {@link CassandraQueryMethod} on which the parameters are evaluated.
|
||||
* @param bindings {@link List} of {@link ParameterBinding} containing name or position (index) information
|
||||
* pertaining to the parameter in the referenced {@code queryMethod}.
|
||||
*/
|
||||
public BindingContext(CassandraQueryMethod queryMethod, List<ParameterBinding> bindings) {
|
||||
|
||||
this.queryMethod = queryMethod;
|
||||
this.bindings = bindings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} when list of bindings is not empty.
|
||||
*/
|
||||
boolean hasBindings() {
|
||||
return !CollectionUtils.isEmpty(bindings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unmodifiable list of {@link ParameterBinding}s.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
List<ParameterBinding> getBindings() {
|
||||
return Collections.unmodifiableList(bindings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the associated {@link CassandraParameters}.
|
||||
*
|
||||
* @return the {@link CassandraParameters} associated with the {@link CassandraQueryMethod}.
|
||||
*/
|
||||
CassandraParameters getParameters() {
|
||||
return queryMethod.getParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link CassandraQueryMethod}.
|
||||
*
|
||||
* @return the {@link CassandraQueryMethod} used in the expression evaluation context.
|
||||
*/
|
||||
CassandraQueryMethod getQueryMethod() {
|
||||
return queryMethod;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic parameter binding with name or position information.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
static class ParameterBinding {
|
||||
|
||||
private final boolean quoted;
|
||||
private final int parameterIndex;
|
||||
private final @Nullable String expression;
|
||||
private final @Nullable String parameterName;
|
||||
|
||||
private ParameterBinding(int parameterIndex, boolean quoted, @Nullable String expression,
|
||||
@Nullable String parameterName) {
|
||||
|
||||
this.parameterIndex = parameterIndex;
|
||||
this.quoted = quoted;
|
||||
this.expression = expression;
|
||||
this.parameterName = parameterName;
|
||||
}
|
||||
|
||||
static ParameterBinding expression(String expression, boolean quoted) {
|
||||
return new ParameterBinding(-1, quoted, expression, null);
|
||||
}
|
||||
|
||||
static ParameterBinding indexed(int parameterIndex) {
|
||||
return new ParameterBinding(parameterIndex, false, null, null);
|
||||
}
|
||||
|
||||
static ParameterBinding named(String name) {
|
||||
return new ParameterBinding(-1, false, null, name);
|
||||
}
|
||||
|
||||
boolean isNamed() {
|
||||
return (parameterName != null);
|
||||
}
|
||||
|
||||
int getParameterIndex() {
|
||||
return parameterIndex;
|
||||
}
|
||||
|
||||
String getParameter() {
|
||||
return ("?" + (isExpression() ? "expr" : "") + parameterIndex);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
boolean isExpression() {
|
||||
return (this.expression != null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getParameterName() {
|
||||
return parameterName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import org.springframework.data.cassandra.core.query.Columns;
|
||||
import org.springframework.data.cassandra.core.query.Query;
|
||||
import org.springframework.data.cassandra.repository.Query.Idempotency;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
|
||||
import org.springframework.data.repository.query.QueryCreationException;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
@@ -229,11 +230,12 @@ class QueryStatementCreator {
|
||||
* @param parameterAccessor must not be {@literal null}.
|
||||
* @return the {@link Statement}.
|
||||
*/
|
||||
SimpleStatement select(StringBasedQuery stringBasedQuery, CassandraParameterAccessor parameterAccessor) {
|
||||
SimpleStatement select(StringBasedQuery stringBasedQuery, CassandraParameterAccessor parameterAccessor,
|
||||
SpELExpressionEvaluator evaluator) {
|
||||
|
||||
try {
|
||||
|
||||
SimpleStatement boundQuery = stringBasedQuery.bindQuery(parameterAccessor, this.queryMethod);
|
||||
SimpleStatement boundQuery = stringBasedQuery.bindQuery(parameterAccessor, evaluator);
|
||||
|
||||
Optional<QueryOptions> queryOptions = Optional.ofNullable(parameterAccessor.getQueryOptions());
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ import com.datastax.oss.driver.api.core.cql.Statement;
|
||||
@FunctionalInterface
|
||||
interface ReactiveCassandraQueryExecution {
|
||||
|
||||
Object execute(Statement<?> statement, Class<?> type);
|
||||
Publisher<? extends Object> execute(Statement<?> statement, Class<?> type);
|
||||
|
||||
/**
|
||||
* {@link ReactiveCassandraQueryExecution} for a {@link org.springframework.data.domain.Slice}.
|
||||
@@ -72,7 +72,7 @@ interface ReactiveCassandraQueryExecution {
|
||||
* @see org.springframework.data.cassandra.repository.query.CassandraQueryExecution#execute(java.lang.String, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public Object execute(Statement<?> statement, Class<?> type) {
|
||||
public Publisher<? extends Object> execute(Statement<?> statement, Class<?> type) {
|
||||
|
||||
CassandraPageRequest.validatePageable(pageable);
|
||||
|
||||
@@ -114,7 +114,7 @@ interface ReactiveCassandraQueryExecution {
|
||||
* @see org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryExecution#execute(java.lang.String, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public Object execute(Statement<?> statement, Class<?> type) {
|
||||
public Publisher<? extends Object> execute(Statement<?> statement, Class<?> type) {
|
||||
return operations.select(statement, type);
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ interface ReactiveCassandraQueryExecution {
|
||||
* @see org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryExecution#execute(java.lang.String, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public Object execute(Statement<?> statement, Class<?> type) {
|
||||
public Publisher<? extends Object> execute(Statement<?> statement, Class<?> type) {
|
||||
|
||||
return operations.select(statement, type).buffer(2).map(objects -> {
|
||||
|
||||
@@ -175,7 +175,7 @@ interface ReactiveCassandraQueryExecution {
|
||||
* @see org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryExecution#execute(com.datastax.oss.driver.api.core.cql.Statement, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public Object execute(Statement<?> statement, Class<?> type) {
|
||||
public Publisher<? extends Object> execute(Statement<?> statement, Class<?> type) {
|
||||
|
||||
Mono<List<Row>> rows = this.operations.getReactiveCqlOperations().queryForRows(statement).buffer(2).next();
|
||||
|
||||
@@ -223,8 +223,8 @@ interface ReactiveCassandraQueryExecution {
|
||||
* @see org.springframework.data.cassandra.repository.query.ReactiveCassandraQueryExecution#execute(java.lang.String, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public Object execute(Statement<?> statement, Class<?> type) {
|
||||
return converter.convert(delegate.execute(statement, type));
|
||||
public Publisher<? extends Object> execute(Statement<?> statement, Class<?> type) {
|
||||
return (Publisher) converter.convert(delegate.execute(statement, type));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.cassandra.repository.query;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
|
||||
import org.springframework.data.cassandra.core.StatementFactory;
|
||||
import org.springframework.data.cassandra.core.convert.UpdateMapper;
|
||||
@@ -94,22 +96,25 @@ public class ReactivePartTreeCassandraQuery extends AbstractReactiveCassandraQue
|
||||
* @see org.springframework.data.cassandra.repository.query.AbstractCassandraQuery#createQuery(org.springframework.data.cassandra.repository.query.CassandraParameterAccessor, boolean)
|
||||
*/
|
||||
@Override
|
||||
protected SimpleStatement createQuery(CassandraParameterAccessor parameterAccessor) {
|
||||
protected Mono<SimpleStatement> createQuery(CassandraParameterAccessor parameterAccessor) {
|
||||
|
||||
if (isCountQuery()) {
|
||||
return getQueryStatementCreator().count(getStatementFactory(), getTree(), parameterAccessor);
|
||||
}
|
||||
return Mono.fromSupplier(() -> {
|
||||
|
||||
if (isExistsQuery()) {
|
||||
return getQueryStatementCreator().exists(getStatementFactory(), getTree(), parameterAccessor);
|
||||
}
|
||||
if (isCountQuery()) {
|
||||
return getQueryStatementCreator().count(getStatementFactory(), getTree(), parameterAccessor);
|
||||
}
|
||||
|
||||
if (getTree().isDelete()) {
|
||||
return getQueryStatementCreator().delete(getStatementFactory(), getTree(), parameterAccessor);
|
||||
}
|
||||
if (isExistsQuery()) {
|
||||
return getQueryStatementCreator().exists(getStatementFactory(), getTree(), parameterAccessor);
|
||||
}
|
||||
|
||||
return getQueryStatementCreator().select(getStatementFactory(), getTree(), parameterAccessor,
|
||||
getQueryMethod().getResultProcessor());
|
||||
if (getTree().isDelete()) {
|
||||
return getQueryStatementCreator().delete(getStatementFactory(), getTree(), parameterAccessor);
|
||||
}
|
||||
|
||||
return getQueryStatementCreator().select(getStatementFactory(), getTree(), parameterAccessor,
|
||||
getQueryMethod().getResultProcessor());
|
||||
});
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
|
||||
@@ -15,9 +15,15 @@
|
||||
*/
|
||||
package org.springframework.data.cassandra.repository.query;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
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.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.spel.ExpressionDependencies;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -45,6 +51,9 @@ public class ReactiveStringBasedCassandraQuery extends AbstractReactiveCassandra
|
||||
|
||||
private final boolean isExistsQuery;
|
||||
|
||||
private final ExpressionParser expressionParser;
|
||||
private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
|
||||
/**
|
||||
* Create a new {@link ReactiveStringBasedCassandraQuery} for the given {@link CassandraQueryMethod},
|
||||
* {@link ReactiveCassandraOperations}, {@link SpelExpressionParser}, and
|
||||
@@ -59,8 +68,8 @@ public class ReactiveStringBasedCassandraQuery extends AbstractReactiveCassandra
|
||||
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations
|
||||
*/
|
||||
public ReactiveStringBasedCassandraQuery(ReactiveCassandraQueryMethod queryMethod,
|
||||
ReactiveCassandraOperations operations, SpelExpressionParser expressionParser,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
ReactiveCassandraOperations operations, ExpressionParser expressionParser,
|
||||
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
this(queryMethod.getRequiredAnnotatedQuery(), queryMethod, operations, expressionParser, evaluationContextProvider);
|
||||
}
|
||||
@@ -79,15 +88,17 @@ public class ReactiveStringBasedCassandraQuery extends AbstractReactiveCassandra
|
||||
* @see org.springframework.data.cassandra.core.ReactiveCassandraOperations
|
||||
*/
|
||||
public ReactiveStringBasedCassandraQuery(String query, ReactiveCassandraQueryMethod method,
|
||||
ReactiveCassandraOperations operations, SpelExpressionParser expressionParser,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
ReactiveCassandraOperations operations, ExpressionParser expressionParser,
|
||||
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
super(method, operations);
|
||||
|
||||
Assert.hasText(query, "Query must not be empty");
|
||||
|
||||
this.stringBasedQuery = new StringBasedQuery(query,
|
||||
new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider));
|
||||
this.expressionParser = expressionParser;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
|
||||
this.stringBasedQuery = new StringBasedQuery(query, method.getParameters(), expressionParser);
|
||||
|
||||
if (method.hasAnnotatedQuery()) {
|
||||
|
||||
@@ -113,8 +124,14 @@ public class ReactiveStringBasedCassandraQuery extends AbstractReactiveCassandra
|
||||
* @see org.springframework.data.cassandra.repository.query.AbstractCassandraQuery#createQuery(org.springframework.data.cassandra.repository.query.CassandraParameterAccessor)
|
||||
*/
|
||||
@Override
|
||||
public SimpleStatement createQuery(CassandraParameterAccessor parameterAccessor) {
|
||||
return getQueryStatementCreator().select(getStringBasedQuery(), parameterAccessor);
|
||||
public Mono<SimpleStatement> createQuery(CassandraParameterAccessor parameterAccessor) {
|
||||
|
||||
StringBasedQuery query = getStringBasedQuery();
|
||||
|
||||
Mono<SpELExpressionEvaluator> spelEvaluator = getSpelEvaluatorFor(query.getExpressionDependencies(),
|
||||
parameterAccessor);
|
||||
|
||||
return spelEvaluator.map(it -> getQueryStatementCreator().select(query, parameterAccessor, it));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -148,4 +165,22 @@ public class ReactiveStringBasedCassandraQuery extends AbstractReactiveCassandra
|
||||
protected boolean isModifyingQuery() {
|
||||
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,
|
||||
CassandraParameterAccessor accessor) {
|
||||
|
||||
return evaluationContextProvider
|
||||
.getEvaluationContextLater(getQueryMethod().getParameters(), accessor.getValues(), dependencies)
|
||||
.map(evaluationContext -> (SpELExpressionEvaluator) new DefaultSpELExpressionEvaluator(expressionParser,
|
||||
evaluationContext))
|
||||
.defaultIfEmpty(DefaultSpELExpressionEvaluator.unsupported());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.springframework.data.cassandra.repository.query;
|
||||
import org.springframework.data.cassandra.core.CassandraOperations;
|
||||
import org.springframework.data.cassandra.repository.Query;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
|
||||
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
|
||||
@@ -44,6 +46,9 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
|
||||
|
||||
private final boolean isExistsQuery;
|
||||
|
||||
private final ExpressionParser expressionParser;
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
|
||||
/**
|
||||
* Create a new {@link StringBasedCassandraQuery} for the given {@link CassandraQueryMethod},
|
||||
* {@link CassandraOperations}, {@link SpelExpressionParser}, and {@link QueryMethodEvaluationContextProvider}.
|
||||
@@ -57,7 +62,7 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
|
||||
* @see org.springframework.data.cassandra.core.CassandraOperations
|
||||
*/
|
||||
public StringBasedCassandraQuery(CassandraQueryMethod queryMethod, CassandraOperations operations,
|
||||
SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
this(queryMethod.getRequiredAnnotatedQuery(), queryMethod, operations, expressionParser, evaluationContextProvider);
|
||||
}
|
||||
@@ -76,12 +81,15 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
|
||||
* @see org.springframework.data.cassandra.core.CassandraOperations
|
||||
*/
|
||||
public StringBasedCassandraQuery(String query, CassandraQueryMethod method, CassandraOperations operations,
|
||||
SpelExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
ExpressionParser expressionParser, QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
super(method, operations);
|
||||
|
||||
this.expressionParser = expressionParser;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
|
||||
this.stringBasedQuery = new StringBasedQuery(query,
|
||||
new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider));
|
||||
method.getParameters(), expressionParser);
|
||||
|
||||
if (method.hasAnnotatedQuery()) {
|
||||
|
||||
@@ -108,7 +116,14 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
|
||||
*/
|
||||
@Override
|
||||
public SimpleStatement createQuery(CassandraParameterAccessor parameterAccessor) {
|
||||
return getQueryStatementCreator().select(getStringBasedQuery(), parameterAccessor);
|
||||
|
||||
StringBasedQuery query = getStringBasedQuery();
|
||||
|
||||
EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(
|
||||
getQueryMethod().getParameters(), parameterAccessor.getValues(), query.getExpressionDependencies());
|
||||
|
||||
return getQueryStatementCreator().select(query, parameterAccessor,
|
||||
new DefaultSpELExpressionEvaluator(expressionParser, evaluationContext));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
|
||||
@@ -21,8 +21,10 @@ import java.util.TreeMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.cassandra.repository.query.ExpressionEvaluatingParameterBinder.BindingContext;
|
||||
import org.springframework.data.cassandra.repository.query.ExpressionEvaluatingParameterBinder.ParameterBinding;
|
||||
import org.springframework.data.cassandra.repository.query.BindingContext.ParameterBinding;
|
||||
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
|
||||
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;
|
||||
@@ -39,54 +41,76 @@ class StringBasedQuery {
|
||||
|
||||
private final String query;
|
||||
|
||||
private final ExpressionEvaluatingParameterBinder parameterBinder;
|
||||
private final CassandraParameters parameters;
|
||||
|
||||
private final ExpressionParser expressionParser;
|
||||
|
||||
private final List<ParameterBinding> queryParameterBindings = new ArrayList<>();
|
||||
|
||||
private final ExpressionDependencies expressionDependencies;
|
||||
|
||||
/**
|
||||
* Create a new {@link StringBasedQuery} given {@code query}, {@link ExpressionEvaluatingParameterBinder} and
|
||||
* {@link CodecRegistry}.
|
||||
* Create a new {@link StringBasedQuery} given {@code query}, {@link CassandraParameters} and
|
||||
* {@link ExpressionParser}.
|
||||
*
|
||||
* @param query must not be empty.
|
||||
* @param parameterBinder must not be {@literal null}.
|
||||
* @param parameters must not be {@literal null}.
|
||||
* @param expressionParser must not be {@literal null}.
|
||||
*/
|
||||
StringBasedQuery(String query, ExpressionEvaluatingParameterBinder parameterBinder) {
|
||||
|
||||
Assert.hasText(query, "Query must not be empty");
|
||||
Assert.notNull(parameterBinder, "ExpressionEvaluatingParameterBinder must not be null");
|
||||
|
||||
this.parameterBinder = parameterBinder;
|
||||
StringBasedQuery(String query, CassandraParameters parameters, ExpressionParser expressionParser) {
|
||||
|
||||
this.query = ParameterBindingParser.INSTANCE.parseAndCollectParameterBindingsFromQueryIntoBindings(query,
|
||||
this.queryParameterBindings);
|
||||
|
||||
this.parameters = parameters;
|
||||
this.expressionParser = expressionParser;
|
||||
this.expressionDependencies = createExpressionDependencies();
|
||||
}
|
||||
|
||||
private ExpressionEvaluatingParameterBinder getParameterBinder() {
|
||||
return this.parameterBinder;
|
||||
private ExpressionDependencies createExpressionDependencies() {
|
||||
|
||||
if (queryParameterBindings.isEmpty()) {
|
||||
return ExpressionDependencies.none();
|
||||
}
|
||||
|
||||
List<ExpressionDependencies> dependencies = new ArrayList<>();
|
||||
|
||||
for (ParameterBinding binding : queryParameterBindings) {
|
||||
if (binding.isExpression()) {
|
||||
dependencies
|
||||
.add(ExpressionDependencies.discover(expressionParser.parseExpression(binding.getRequiredExpression())));
|
||||
}
|
||||
}
|
||||
|
||||
return ExpressionDependencies.merged(dependencies);
|
||||
}
|
||||
|
||||
/* (non-Javadoc) */
|
||||
protected String getQuery() {
|
||||
return this.query;
|
||||
/**
|
||||
* Obtain {@link ExpressionDependencies} from the parsed query.
|
||||
*
|
||||
* @return the {@link ExpressionDependencies} from the parsed query.
|
||||
*/
|
||||
public ExpressionDependencies getExpressionDependencies() {
|
||||
return expressionDependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the query to actual parameters using {@link CassandraParameterAccessor},
|
||||
*
|
||||
* @param parameterAccessor must not be {@literal null}.
|
||||
* @param queryMethod must not be {@literal null}.
|
||||
* @param evaluator must not be {@literal null}.
|
||||
* @return the bound String query containing formatted parameters.
|
||||
*/
|
||||
SimpleStatement bindQuery(CassandraParameterAccessor parameterAccessor, CassandraQueryMethod queryMethod) {
|
||||
public SimpleStatement bindQuery(CassandraParameterAccessor parameterAccessor, SpELExpressionEvaluator evaluator) {
|
||||
|
||||
Assert.notNull(parameterAccessor, "CassandraParameterAccessor must not be null");
|
||||
Assert.notNull(queryMethod, "CassandraQueryMethod must not be null");
|
||||
Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null");
|
||||
|
||||
List<Object> arguments = getParameterBinder().bind(parameterAccessor,
|
||||
new BindingContext(queryMethod, this.queryParameterBindings));
|
||||
BindingContext bindingContext = new BindingContext(this.parameters, parameterAccessor, this.queryParameterBindings,
|
||||
evaluator);
|
||||
|
||||
return ParameterBinder.INSTANCE.bind(getQuery(), arguments);
|
||||
List<Object> arguments = bindingContext.getBindingValues();
|
||||
|
||||
return ParameterBinder.INSTANCE.bind(this.query, arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,14 +242,15 @@ class StringBasedQuery {
|
||||
result.append(ARGUMENT_PLACEHOLDER);
|
||||
|
||||
if (matcher.pattern() == NAME_BASED_EXPRESSION_PATTERN || matcher.pattern() == INDEX_BASED_EXPRESSION_PATTERN) {
|
||||
bindings.add(ExpressionEvaluatingParameterBinder.ParameterBinding
|
||||
bindings.add(
|
||||
BindingContext.ParameterBinding
|
||||
.expression(input.substring(exprStart + 3, currentPosition - 1), true));
|
||||
} else {
|
||||
if (matcher.pattern() == INDEX_PARAMETER_BINDING_PATTERN) {
|
||||
bindings
|
||||
.add(ExpressionEvaluatingParameterBinder.ParameterBinding.indexed(Integer.parseInt(matcher.group(1))));
|
||||
.add(BindingContext.ParameterBinding.indexed(Integer.parseInt(matcher.group(1))));
|
||||
} else {
|
||||
bindings.add(ExpressionEvaluatingParameterBinder.ParameterBinding.named(matcher.group(1)));
|
||||
bindings.add(BindingContext.ParameterBinding.named(matcher.group(1)));
|
||||
}
|
||||
|
||||
currentPosition = matcher.end();
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2020 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.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;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.expression.ExpressionParser#parseExpression(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public Expression parseExpression(String expressionString) throws ParseException {
|
||||
return cache.computeIfAbsent(expressionString, delegate::parseExpression);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.expression.ExpressionParser#parseExpression(java.lang.String, org.springframework.expression.ParserContext)
|
||||
*/
|
||||
@Override
|
||||
public Expression parseExpression(String expressionString, ParserContext context) throws ParseException {
|
||||
throw new UnsupportedOperationException("Parsing using ParserContext is not supported");
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -112,7 +113,7 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
|
||||
return Optional.of(new CassandraQueryLookupStrategy(operations, evaluationContextProvider, mappingContext));
|
||||
}
|
||||
|
||||
private class CassandraQueryLookupStrategy implements QueryLookupStrategy {
|
||||
private static class CassandraQueryLookupStrategy implements QueryLookupStrategy {
|
||||
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
|
||||
@@ -120,6 +121,8 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
private final CassandraOperations operations;
|
||||
|
||||
private final ExpressionParser expressionParser = new CachingExpressionParser(EXPRESSION_PARSER);
|
||||
|
||||
CassandraQueryLookupStrategy(CassandraOperations operations,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider,
|
||||
MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext) {
|
||||
@@ -141,13 +144,14 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
if (namedQueries.hasQuery(namedQueryName)) {
|
||||
String namedQuery = namedQueries.getQuery(namedQueryName);
|
||||
return new StringBasedCassandraQuery(namedQuery, queryMethod, operations, EXPRESSION_PARSER,
|
||||
return new StringBasedCassandraQuery(namedQuery, queryMethod, operations, expressionParser,
|
||||
evaluationContextProvider);
|
||||
} else if (queryMethod.hasAnnotatedQuery()) {
|
||||
return new StringBasedCassandraQuery(queryMethod, operations, EXPRESSION_PARSER, evaluationContextProvider);
|
||||
return new StringBasedCassandraQuery(queryMethod, operations, expressionParser, evaluationContextProvider);
|
||||
} else {
|
||||
return new PartTreeCassandraQuery(queryMethod, operations);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,9 @@ 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.ReactiveQueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -64,6 +66,8 @@ public class ReactiveCassandraRepositoryFactory extends ReactiveRepositoryFactor
|
||||
|
||||
this.operations = cassandraOperations;
|
||||
this.mappingContext = cassandraOperations.getConverter().getMappingContext();
|
||||
|
||||
setEvaluationContextProvider(ReactiveQueryMethodEvaluationContextProvider.DEFAULT);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -91,7 +95,8 @@ public class ReactiveCassandraRepositoryFactory extends ReactiveRepositoryFactor
|
||||
@Override
|
||||
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
return Optional.of(new CassandraQueryLookupStrategy(operations, evaluationContextProvider, mappingContext));
|
||||
return Optional.of(new CassandraQueryLookupStrategy(operations,
|
||||
(ReactiveQueryMethodEvaluationContextProvider) evaluationContextProvider, mappingContext));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -113,12 +118,16 @@ public class ReactiveCassandraRepositoryFactory extends ReactiveRepositoryFactor
|
||||
*/
|
||||
private static class CassandraQueryLookupStrategy implements QueryLookupStrategy {
|
||||
|
||||
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
|
||||
private final ReactiveCassandraOperations operations;
|
||||
|
||||
private final MappingContext<? extends CassandraPersistentEntity<?>, ? extends CassandraPersistentProperty> mappingContext;
|
||||
|
||||
private final ExpressionParser expressionParser = new CachingExpressionParser(EXPRESSION_PARSER);
|
||||
|
||||
CassandraQueryLookupStrategy(ReactiveCassandraOperations operations,
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider,
|
||||
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider,
|
||||
MappingContext<? extends CassandraPersistentEntity<?>, ? extends CassandraPersistentProperty> mappingContext) {
|
||||
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
@@ -141,10 +150,10 @@ public class ReactiveCassandraRepositoryFactory extends ReactiveRepositoryFactor
|
||||
if (namedQueries.hasQuery(namedQueryName)) {
|
||||
String namedQuery = namedQueries.getQuery(namedQueryName);
|
||||
|
||||
return new ReactiveStringBasedCassandraQuery(namedQuery, queryMethod, operations, EXPRESSION_PARSER,
|
||||
return new ReactiveStringBasedCassandraQuery(namedQuery, queryMethod, operations, expressionParser,
|
||||
evaluationContextProvider);
|
||||
} else if (queryMethod.hasAnnotatedQuery()) {
|
||||
return new ReactiveStringBasedCassandraQuery(queryMethod, operations, EXPRESSION_PARSER,
|
||||
return new ReactiveStringBasedCassandraQuery(queryMethod, operations, expressionParser,
|
||||
evaluationContextProvider);
|
||||
} else {
|
||||
return new ReactivePartTreeCassandraQuery(queryMethod, operations);
|
||||
|
||||
@@ -15,11 +15,16 @@
|
||||
*/
|
||||
package org.springframework.data.cassandra.repository.support;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.data.cassandra.core.ReactiveCassandraOperations;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.ReactiveExtensionAwareQueryMethodEvaluationContextProvider;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -80,6 +85,16 @@ public class ReactiveCassandraRepositoryFactoryBean<T extends Repository<S, ID>,
|
||||
return getFactoryInstance(operations);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport#createDefaultQueryMethodEvaluationContextProvider(ListableBeanFactory)
|
||||
*/
|
||||
@Override
|
||||
protected Optional<QueryMethodEvaluationContextProvider> createDefaultQueryMethodEvaluationContextProvider(
|
||||
ListableBeanFactory beanFactory) {
|
||||
return Optional.of(new ReactiveExtensionAwareQueryMethodEvaluationContextProvider(beanFactory));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and initializes a {@link RepositoryFactorySupport} instance.
|
||||
*
|
||||
|
||||
@@ -111,7 +111,6 @@ public class ReactiveCassandraRepositoryIntegrationTests extends AbstractSpringD
|
||||
factory.setRepositoryBaseClass(SimpleReactiveCassandraRepository.class);
|
||||
factory.setBeanClassLoader(classLoader);
|
||||
factory.setBeanFactory(beanFactory);
|
||||
factory.setEvaluationContextProvider(QueryMethodEvaluationContextProvider.DEFAULT);
|
||||
|
||||
repository = factory.getRepository(UserRepository.class);
|
||||
groupRepostitory = factory.getRepository(GroupRepository.class);
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.cassandra.repository.query.ExpressionEvaluatingParameterBinder.ParameterBinding;
|
||||
import org.springframework.data.cassandra.repository.query.BindingContext.ParameterBinding;
|
||||
import org.springframework.data.cassandra.repository.query.StringBasedQuery.ParameterBindingParser;
|
||||
|
||||
/**
|
||||
|
||||
@@ -176,7 +176,8 @@ public class ReactivePartTreeCassandraQueryUnitTests {
|
||||
CassandraParameterAccessor accessor = new CassandraParametersParameterAccessor(partTreeQuery.getQueryMethod(),
|
||||
args);
|
||||
|
||||
return partTreeQuery.createQuery(new ConvertingParameterAccessor(mockCassandraOperations.getConverter(), accessor));
|
||||
return partTreeQuery.createQuery(new ConvertingParameterAccessor(mockCassandraOperations.getConverter(), accessor))
|
||||
.block();
|
||||
}
|
||||
|
||||
private ReactivePartTreeCassandraQuery createQueryForMethod(Class<?> repositoryInterface, String methodName,
|
||||
|
||||
@@ -18,7 +18,10 @@ package org.springframework.data.cassandra.repository.query;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -40,8 +43,11 @@ 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.ReactiveExtensionAwareQueryMethodEvaluationContextProvider;
|
||||
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.util.ReflectionUtils;
|
||||
|
||||
import com.datastax.oss.driver.api.core.DefaultConsistencyLevel;
|
||||
@@ -79,13 +85,13 @@ public class ReactiveStringBasedCassandraQueryUnitTests {
|
||||
}
|
||||
|
||||
@Test // DATACASS-335
|
||||
public void bindsSimplePropertyCorrectly() throws Exception {
|
||||
public void bindsSimplePropertyCorrectly() {
|
||||
|
||||
ReactiveStringBasedCassandraQuery cassandraQuery = getQueryMethod("findByLastname", String.class);
|
||||
CassandraParametersParameterAccessor accessor = new CassandraParametersParameterAccessor(
|
||||
cassandraQuery.getQueryMethod(), "White");
|
||||
|
||||
SimpleStatement actual = cassandraQuery.createQuery(accessor);
|
||||
SimpleStatement actual = cassandraQuery.createQuery(accessor).block();
|
||||
|
||||
assertThat(actual.getQuery()).isEqualTo("SELECT * FROM person WHERE lastname=?;");
|
||||
assertThat(actual.getPositionalValues().get(0)).isEqualTo("White");
|
||||
@@ -102,7 +108,7 @@ public class ReactiveStringBasedCassandraQueryUnitTests {
|
||||
CassandraParametersParameterAccessor parameterAccessor = new CassandraParametersParameterAccessor(
|
||||
cassandraQuery.getQueryMethod(), queryOptions, "White");
|
||||
|
||||
SimpleStatement actual = cassandraQuery.createQuery(parameterAccessor);
|
||||
SimpleStatement actual = cassandraQuery.createQuery(parameterAccessor).block();
|
||||
|
||||
assertThat(actual.getQuery()).isEqualTo("SELECT * FROM person WHERE lastname=?;");
|
||||
assertThat(actual.getPositionalValues().get(0)).isEqualTo("White");
|
||||
@@ -117,13 +123,27 @@ public class ReactiveStringBasedCassandraQueryUnitTests {
|
||||
CassandraParametersParameterAccessor parameterAccessor = new CassandraParametersParameterAccessor(
|
||||
cassandraQuery.getQueryMethod(), "Matthews");
|
||||
|
||||
SimpleStatement actual = cassandraQuery.createQuery(parameterAccessor);
|
||||
SimpleStatement actual = cassandraQuery.createQuery(parameterAccessor).block();
|
||||
|
||||
assertThat(actual.getQuery()).isEqualTo("SELECT * FROM person WHERE lastname=?;");
|
||||
assertThat(actual.getPositionalValues().get(0)).isEqualTo("Matthews");
|
||||
assertThat(actual.getConsistencyLevel()).isEqualTo(DefaultConsistencyLevel.LOCAL_ONE);
|
||||
}
|
||||
|
||||
@Test // DATACASS-788
|
||||
public void shouldUseSpelExtension() {
|
||||
|
||||
ReactiveStringBasedCassandraQuery cassandraQuery = getQueryMethod("findBySpel");
|
||||
|
||||
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);
|
||||
@@ -131,8 +151,11 @@ public class ReactiveStringBasedCassandraQueryUnitTests {
|
||||
ReactiveCassandraQueryMethod queryMethod = new ReactiveCassandraQueryMethod(method, metadata, factory,
|
||||
converter.getMappingContext());
|
||||
|
||||
ReactiveExtensionAwareQueryMethodEvaluationContextProvider provider = new ReactiveExtensionAwareQueryMethodEvaluationContextProvider(
|
||||
Arrays.asList(MyReactiveExtension.INSTANCE, MyDefunctExtension.INSTANCE));
|
||||
|
||||
return new ReactiveStringBasedCassandraQuery(queryMethod, operations, PARSER,
|
||||
ExtensionAwareQueryMethodEvaluationContextProvider.DEFAULT);
|
||||
provider);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@@ -145,5 +168,74 @@ public class ReactiveStringBasedCassandraQueryUnitTests {
|
||||
@Query("SELECT * FROM person WHERE lastname=?0;")
|
||||
Person findByLastname(QueryOptions queryOptions, String lastname);
|
||||
|
||||
@Query("SELECT * FROM person WHERE lastname=:#{getName()};")
|
||||
Person findBySpel();
|
||||
|
||||
}
|
||||
|
||||
public static class MyReactiveExtensionObject implements EvaluationContextExtension {
|
||||
|
||||
public String getName() {
|
||||
return "Walter";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExtensionId() {
|
||||
return "ext-1";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public MyReactiveExtensionObject getRootObject() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DefunctExtensionObject implements EvaluationContextExtension {
|
||||
|
||||
public String getPrincipal() {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExtensionId() {
|
||||
return "ext-1";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DefunctExtensionObject getRootObject() {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
enum MyReactiveExtension implements ReactiveEvaluationContextExtension {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Mono<MyReactiveExtensionObject> getExtension() {
|
||||
return Mono.just(new MyReactiveExtensionObject());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExtensionId() {
|
||||
return "ext-1";
|
||||
}
|
||||
}
|
||||
|
||||
enum MyDefunctExtension implements ReactiveEvaluationContextExtension {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Mono<DefunctExtensionObject> getExtension() {
|
||||
return Mono.error(new IllegalStateException());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExtensionId() {
|
||||
return "ext-2";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,6 @@ public class SimpleReactiveCassandraRepositoryIntegrationTests extends AbstractE
|
||||
factory.setRepositoryBaseClass(SimpleReactiveCassandraRepository.class);
|
||||
factory.setBeanClassLoader(classLoader);
|
||||
factory.setBeanFactory(beanFactory);
|
||||
factory.setEvaluationContextProvider(ExtensionAwareQueryMethodEvaluationContextProvider.DEFAULT);
|
||||
|
||||
repository = factory.getRepository(UserRepostitory.class);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ This chapter summarizes changes and new features for each release.
|
||||
== What's new in Spring Data for Apache Cassandra 3.1
|
||||
|
||||
* <<cassandra.auditing,Reactive auditing>> enabled through `@EnableReactiveCassandraAuditing`. `@EnableCassandraAuditing` no longer registers `ReactiveAuditingEntityCallback`.
|
||||
* Reactive SpEL support in `@Query` query methods.
|
||||
|
||||
[[new-features.3-0-0]]
|
||||
== What's new in Spring Data for Apache Cassandra 3.0
|
||||
|
||||
Reference in New Issue
Block a user