Polishing.

Remove unused DefaultSpELExpressionEvaluator. Use caching variant of ValueExpressionDelegate.

Remove unused RepositoryFactory methods. Simplify ValueExpressionDelegateValueExpressionEvaluator to ContextualValueExpressionEvaluator.

See #1522
Original pull request: #1523
This commit is contained in:
Mark Paluch
2024-10-01 09:55:30 +02:00
parent 9ebd3db34f
commit 34da375738
9 changed files with 50 additions and 156 deletions

View File

@@ -15,27 +15,30 @@
*/
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.expression.ValueExpressionParser;
import org.springframework.data.mapping.model.ValueExpressionEvaluator;
import org.springframework.data.repository.query.ValueExpressionDelegate;
class ValueExpressionDelegateValueExpressionEvaluator implements ValueExpressionEvaluator {
/**
* @author Marcin Grzejszczak
* @author Mark Paluch
*/
class ContextualValueExpressionEvaluator implements ValueExpressionEvaluator {
private final ValueExpressionDelegate delegate;
private final Function<ValueExpression, ValueEvaluationContext> expressionToContext;
private final ValueExpressionParser parser;
ValueExpressionDelegateValueExpressionEvaluator(ValueExpressionDelegate delegate, Function<ValueExpression, ValueEvaluationContext> expressionToContext) {
this.delegate = delegate;
this.expressionToContext = expressionToContext;
public ContextualValueExpressionEvaluator(ValueExpressionParser parser, ValueEvaluationContext evaluationContext) {
this.parser = parser;
this.evaluationContext = evaluationContext;
}
private final ValueEvaluationContext evaluationContext;
@SuppressWarnings("unchecked")
@Override
public <T> T evaluate(String expressionString) {
ValueExpression expression = delegate.parse(expressionString);
return (T) expression.evaluate(expressionToContext.apply(expression));
ValueExpression expression = parser.parse(expressionString);
return (T) expression.evaluate(evaluationContext);
}
}

View File

@@ -1,68 +0,0 @@
/*
* Copyright 2020-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.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;
}
@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");
}
}
}

View File

@@ -43,6 +43,7 @@ import com.datastax.oss.driver.api.core.cql.SimpleStatement;
* index-based and expression parameters that are resolved during query execution.
*
* @author Mark Paluch
* @author Marcin Grzejszczak
* @see org.springframework.data.cassandra.repository.Query
* @see org.springframework.data.cassandra.repository.query.AbstractReactiveCassandraQuery
* @since 2.0
@@ -203,6 +204,6 @@ public class ReactiveStringBasedCassandraQuery extends AbstractReactiveCassandra
private Mono<ValueExpressionEvaluator> getValueExpressionEvaluatorLater(ExpressionDependencies dependencies,
CassandraParameterAccessor accessor) {
return valueEvaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies)
.map(evaluationContext -> new ValueExpressionDelegateValueExpressionEvaluator(delegate, valueExpression -> evaluationContext));
.map(evaluationContext -> new ContextualValueExpressionEvaluator(delegate, evaluationContext));
}
}

View File

@@ -37,6 +37,7 @@ import com.datastax.oss.driver.api.core.cql.SimpleStatement;
*
* @author Matthew Adams
* @author Mark Paluch
* @author Marcin Grzejszczak
* @see org.springframework.data.cassandra.repository.Query
* @see org.springframework.data.cassandra.repository.query.AbstractCassandraQuery
*/
@@ -159,7 +160,8 @@ public class StringBasedCassandraQuery extends AbstractCassandraQuery {
ValueEvaluationContext evaluationContext = valueExpressionDelegate.createValueContextProvider(
getQueryMethod().getParameters()).getEvaluationContext(parameterAccessorToUse.getValues(), query.getExpressionDependencies());
return getQueryStatementCreator().select(query, parameterAccessorToUse, new ValueExpressionDelegateValueExpressionEvaluator(valueExpressionDelegate, valueExpression -> evaluationContext));
return getQueryStatementCreator().select(query, parameterAccessorToUse,
new ContextualValueExpressionEvaluator(valueExpressionDelegate, evaluationContext));
}
@Override

View File

@@ -17,6 +17,7 @@ package org.springframework.data.cassandra.repository.query;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
@@ -36,6 +37,7 @@ import com.datastax.oss.driver.api.core.cql.SimpleStatement;
* String-based Query abstracting a CQL query with parameter bindings.
*
* @author Mark Paluch
* @author Marcin Grzejszczak
* @since 2.0
*/
class StringBasedQuery {
@@ -270,9 +272,9 @@ class StringBasedQuery {
}
@Nullable
private static Matcher findNextBindingOrExpression(String input, int position) {
private static Matcher findNextBindingOrExpression(String input, int startPosition) {
List<Matcher> matchers = new ArrayList<>();
List<Matcher> matchers = new ArrayList<>(6);
matchers.add(INDEX_PARAMETER_BINDING_PATTERN.matcher(input));
matchers.add(NAMED_PARAMETER_BINDING_PATTERN.matcher(input));
@@ -281,10 +283,10 @@ class StringBasedQuery {
matchers.add(INDEX_BASED_PROPERTY_PLACEHOLDER_PATTERN.matcher(input));
matchers.add(NAME_BASED_PROPERTY_PLACEHOLDER_PATTERN.matcher(input));
TreeMap<Integer, Matcher> matcherMap = new TreeMap<>();
Map<Integer, Matcher> matcherMap = new TreeMap<>();
for (Matcher matcher : matchers) {
if (matcher.find(position)) {
if (matcher.find(startPosition)) {
matcherMap.put(matcher.start(), matcher);
}
}

View File

@@ -19,7 +19,6 @@ 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;
@@ -28,21 +27,17 @@ 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;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.query.CachingValueExpressionDelegate;
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.data.repository.query.ValueExpressionDelegate;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -56,8 +51,6 @@ import org.springframework.util.Assert;
*/
public class CassandraRepositoryFactory extends RepositoryFactorySupport {
private static final ValueExpressionParser EXPRESSION_PARSER = ValueExpressionParser.create(SpelExpressionParser::new);
private final MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext;
private final CassandraOperations operations;
@@ -102,33 +95,19 @@ 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, new ValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), evaluationContextProvider.getEvaluationContextProvider()), EXPRESSION_PARSER), mappingContext));
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(Key key,
ValueExpressionDelegate valueExpressionDelegate) {
return Optional.of(new CassandraQueryLookupStrategy(operations,
new CachingValueExpressionDelegate(valueExpressionDelegate), mappingContext));
}
private static class CassandraQueryLookupStrategy implements QueryLookupStrategy {
private final ValueExpressionDelegate valueExpressionDelegate;
private final MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext;
private final CassandraOperations operations;
CassandraQueryLookupStrategy(CassandraOperations operations,
ValueExpressionDelegate valueExpressionDelegate,
MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext) {
this.operations = operations;
this.valueExpressionDelegate = valueExpressionDelegate;
this.mappingContext = mappingContext;
}
private record CassandraQueryLookupStrategy(CassandraOperations operations,
ValueExpressionDelegate valueExpressionDelegate,
MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext)
implements
QueryLookupStrategy {
@Override
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,

View File

@@ -19,7 +19,6 @@ 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;
@@ -27,34 +26,28 @@ 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;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport;
import org.springframework.data.repository.query.CachingValueExpressionDelegate;
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.data.repository.query.ValueExpressionDelegate;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Factory to create {@link org.springframework.data.cassandra.repository.ReactiveCassandraRepository} instances.
*
* @author Mark Paluch
* @author Marcin Grzejszczak
* @since 2.0
*/
public class ReactiveCassandraRepositoryFactory extends ReactiveRepositoryFactorySupport {
private static final ValueExpressionParser EXPRESSION_PARSER = ValueExpressionParser.create(SpelExpressionParser::new);
private final ReactiveCassandraOperations operations;
private final MappingContext<? extends CassandraPersistentEntity<?>, ? extends CassandraPersistentProperty> mappingContext;
@@ -70,8 +63,6 @@ public class ReactiveCassandraRepositoryFactory extends ReactiveRepositoryFactor
this.operations = cassandraOperations;
this.mappingContext = cassandraOperations.getConverter().getMappingContext();
setEvaluationContextProvider(ReactiveQueryMethodEvaluationContextProvider.DEFAULT);
}
@Override
@@ -92,15 +83,11 @@ 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, new ValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), evaluationContextProvider.getEvaluationContextProvider()), EXPRESSION_PARSER), mappingContext));
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(Key key,
ValueExpressionDelegate valueExpressionDelegate) {
return Optional.of(new CassandraQueryLookupStrategy(operations,
new CachingValueExpressionDelegate(valueExpressionDelegate), mappingContext));
}
@SuppressWarnings("unchecked")
@@ -117,22 +104,10 @@ public class ReactiveCassandraRepositoryFactory extends ReactiveRepositoryFactor
*
* @author Mark Paluch
*/
private static class CassandraQueryLookupStrategy implements QueryLookupStrategy {
private final ValueExpressionDelegate delegate;
private final ReactiveCassandraOperations operations;
private final MappingContext<? extends CassandraPersistentEntity<?>, ? extends CassandraPersistentProperty> mappingContext;
CassandraQueryLookupStrategy(ReactiveCassandraOperations operations,
ValueExpressionDelegate delegate,
MappingContext<? extends CassandraPersistentEntity<?>, ? extends CassandraPersistentProperty> mappingContext) {
this.delegate = delegate;
this.operations = operations;
this.mappingContext = mappingContext;
}
private record CassandraQueryLookupStrategy(ReactiveCassandraOperations operations, ValueExpressionDelegate delegate,
MappingContext<? extends CassandraPersistentEntity<?>, ? extends CassandraPersistentProperty> mappingContext)
implements
QueryLookupStrategy {
@Override
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,

View File

@@ -29,11 +29,9 @@ 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;
import org.springframework.data.cassandra.core.cql.QueryOptions;
import org.springframework.data.cassandra.core.cql.ReactiveCqlOperations;
import org.springframework.data.cassandra.core.mapping.CassandraMappingContext;
import org.springframework.data.cassandra.domain.Person;
import org.springframework.data.cassandra.repository.Consistency;
@@ -45,7 +43,6 @@ 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;
@@ -61,6 +58,7 @@ import com.datastax.oss.driver.api.core.cql.SimpleStatement;
* Unit tests for {@link StringBasedCassandraQuery}.
*
* @author Mark Paluch
* @author Marcin Grzejszczak
*/
@ExtendWith(MockitoExtension.class)
class ReactiveStringBasedCassandraQueryUnitTests {
@@ -68,8 +66,6 @@ class ReactiveStringBasedCassandraQueryUnitTests {
private static final ValueExpressionParser PARSER = ValueExpressionParser.create(SpelExpressionParser::new);
@Mock private ReactiveCassandraOperations operations;
@Mock private ReactiveCqlOperations cqlOperations;
@Mock private ReactiveSession reactiveSession;
private MappingCassandraConverter converter;
private ProjectionFactory factory;
@@ -151,7 +147,9 @@ class ReactiveStringBasedCassandraQueryUnitTests {
@Test // GH-1522
void shouldUsePropertyPlaceholder() {
mockEnvironment.withProperty("someProp", "Walter");
ReactiveStringBasedCassandraQuery cassandraQuery = getQueryMethod("findByPropertyPlaceholder");
CassandraParametersParameterAccessor parameterAccessor = new CassandraParametersParameterAccessor(

View File

@@ -72,6 +72,7 @@ import com.datastax.oss.driver.api.core.type.UserDefinedType;
* @author Matthew T. Adams
* @author Oliver Gierke
* @author Mark Paluch
* @author Marcin Grzejszczak
*/
@ExtendWith(MockitoExtension.class)
class StringBasedCassandraQueryUnitTests {
@@ -301,6 +302,7 @@ class StringBasedCassandraQueryUnitTests {
@Test // GH-1522
void bindsPropertyPlaceholderParameterCorrectly() {
environment.withProperty("someParam", "Walter");
StringBasedCassandraQuery cassandraQuery = getQueryMethod("findByPropertyPlaceholder");