diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ValueExpressionDelegateValueExpressionEvaluator.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ContextualValueExpressionEvaluator.java similarity index 58% rename from spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ValueExpressionDelegateValueExpressionEvaluator.java rename to spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ContextualValueExpressionEvaluator.java index 1ee1073d8..258a21bd8 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ValueExpressionDelegateValueExpressionEvaluator.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ContextualValueExpressionEvaluator.java @@ -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 expressionToContext; + private final ValueExpressionParser parser; - ValueExpressionDelegateValueExpressionEvaluator(ValueExpressionDelegate delegate, Function 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 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); } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/DefaultSpELExpressionEvaluator.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/DefaultSpELExpressionEvaluator.java deleted file mode 100644 index e3a25b719..000000000 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/DefaultSpELExpressionEvaluator.java +++ /dev/null @@ -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 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 evaluate(String expression) { - throw new UnsupportedOperationException("Expression evaluation not supported"); - } - } -} diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ReactiveStringBasedCassandraQuery.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ReactiveStringBasedCassandraQuery.java index 2669f0fbc..b112e567c 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ReactiveStringBasedCassandraQuery.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/ReactiveStringBasedCassandraQuery.java @@ -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 getValueExpressionEvaluatorLater(ExpressionDependencies dependencies, CassandraParameterAccessor accessor) { return valueEvaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies) - .map(evaluationContext -> new ValueExpressionDelegateValueExpressionEvaluator(delegate, valueExpression -> evaluationContext)); + .map(evaluationContext -> new ContextualValueExpressionEvaluator(delegate, evaluationContext)); } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQuery.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQuery.java index e04682a9a..d7309c2cd 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQuery.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQuery.java @@ -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 diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedQuery.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedQuery.java index 47973467a..c34f348aa 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedQuery.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/query/StringBasedQuery.java @@ -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 matchers = new ArrayList<>(); + List 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 matcherMap = new TreeMap<>(); + Map matcherMap = new TreeMap<>(); for (Matcher matcher : matchers) { - if (matcher.find(position)) { + if (matcher.find(startPosition)) { matcherMap.put(matcher.start(), matcher); } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactory.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactory.java index f45642160..49ce905aa 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactory.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/CassandraRepositoryFactory.java @@ -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, CassandraPersistentProperty> mappingContext; private final CassandraOperations operations; @@ -102,33 +95,19 @@ public class CassandraRepositoryFactory extends RepositoryFactorySupport { return new MappingCassandraEntityInformation<>((CassandraPersistentEntity) entity, operations.getConverter()); } - @Override protected Optional getQueryLookupStrategy(Key key, - ValueExpressionDelegate valueExpressionDelegate) { - return Optional.of(new CassandraQueryLookupStrategy(operations, valueExpressionDelegate, mappingContext)); - } - @Override - protected Optional 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 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, CassandraPersistentProperty> mappingContext; - - private final CassandraOperations operations; - - CassandraQueryLookupStrategy(CassandraOperations operations, - ValueExpressionDelegate valueExpressionDelegate, - MappingContext, CassandraPersistentProperty> mappingContext) { - - this.operations = operations; - this.valueExpressionDelegate = valueExpressionDelegate; - this.mappingContext = mappingContext; - } + private record CassandraQueryLookupStrategy(CassandraOperations operations, + ValueExpressionDelegate valueExpressionDelegate, + MappingContext, CassandraPersistentProperty> mappingContext) + implements + QueryLookupStrategy { @Override public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/ReactiveCassandraRepositoryFactory.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/ReactiveCassandraRepositoryFactory.java index 71813ae63..25d7a5868 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/ReactiveCassandraRepositoryFactory.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/repository/support/ReactiveCassandraRepositoryFactory.java @@ -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 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 getQueryLookupStrategy(Key key, - ValueExpressionDelegate valueExpressionDelegate) { - return Optional.of(new CassandraQueryLookupStrategy(operations, valueExpressionDelegate, mappingContext)); - } - @Override - protected Optional 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 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 CassandraPersistentProperty> mappingContext; - - CassandraQueryLookupStrategy(ReactiveCassandraOperations operations, - ValueExpressionDelegate delegate, - MappingContext, ? extends CassandraPersistentProperty> mappingContext) { - - this.delegate = delegate; - this.operations = operations; - this.mappingContext = mappingContext; - } + private record CassandraQueryLookupStrategy(ReactiveCassandraOperations operations, ValueExpressionDelegate delegate, + MappingContext, ? extends CassandraPersistentProperty> mappingContext) + implements + QueryLookupStrategy { @Override public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ReactiveStringBasedCassandraQueryUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ReactiveStringBasedCassandraQueryUnitTests.java index 00a6db7e4..41c9fa139 100644 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ReactiveStringBasedCassandraQueryUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/ReactiveStringBasedCassandraQueryUnitTests.java @@ -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( diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQueryUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQueryUnitTests.java index 3179afaa1..abfb8ef6f 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQueryUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/repository/query/StringBasedCassandraQueryUnitTests.java @@ -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");