diff --git a/src/main/java/org/springframework/data/repository/core/support/ReactiveRepositoryFactorySupport.java b/src/main/java/org/springframework/data/repository/core/support/ReactiveRepositoryFactorySupport.java index 75db8708f..b9658343a 100644 --- a/src/main/java/org/springframework/data/repository/core/support/ReactiveRepositoryFactorySupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/ReactiveRepositoryFactorySupport.java @@ -19,8 +19,11 @@ import java.lang.reflect.Method; import java.util.Arrays; import org.reactivestreams.Publisher; + import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; +import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider; import org.springframework.data.repository.util.ReactiveWrapperConverters; import org.springframework.data.repository.util.ReactiveWrappers; import org.springframework.util.ClassUtils; @@ -56,6 +59,20 @@ public abstract class ReactiveRepositoryFactorySupport extends RepositoryFactory } } + /** + * Sets the {@link QueryMethodEvaluationContextProvider} to be used to evaluate SpEL expressions in manually defined + * queries. + * + * @param evaluationContextProvider can be {@literal null}, defaults to + * {@link ReactiveQueryMethodEvaluationContextProvider#INSTANCE}. + */ + @Override + public void setEvaluationContextProvider(QueryMethodEvaluationContextProvider evaluationContextProvider) { + super.setEvaluationContextProvider( + evaluationContextProvider == null ? ReactiveQueryMethodEvaluationContextProvider.DEFAULT + : evaluationContextProvider); + } + /** * We need to make sure that the necessary conversion libraries are in place if the repository interface uses RxJava 1 * types. diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java index 45fed211f..7eb3be1f4 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java @@ -182,11 +182,23 @@ public abstract class RepositoryFactoryBeanSupport, this.beanFactory = beanFactory; if (!this.evaluationContextProvider.isPresent() && ListableBeanFactory.class.isInstance(beanFactory)) { - this.evaluationContextProvider = Optional - .of(new ExtensionAwareQueryMethodEvaluationContextProvider((ListableBeanFactory) beanFactory)); + this.evaluationContextProvider = createDefaultQueryMethodEvaluationContextProvider( + (ListableBeanFactory) beanFactory); } } + /** + * Create a default {@link QueryMethodEvaluationContextProvider} (or subclass) from {@link ListableBeanFactory}. + * + * @param beanFactory the bean factory to use. + * @return the default instance. May be {@link Optional#empty()}. + * @since 2.4 + */ + protected Optional createDefaultQueryMethodEvaluationContextProvider( + ListableBeanFactory beanFactory) { + return Optional.of(new ExtensionAwareQueryMethodEvaluationContextProvider(beanFactory)); + } + /* * (non-Javadoc) * @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher) diff --git a/src/main/java/org/springframework/data/repository/query/QueryMethodEvaluationContextProvider.java b/src/main/java/org/springframework/data/repository/query/QueryMethodEvaluationContextProvider.java index 8abd56834..9d8db59f9 100644 --- a/src/main/java/org/springframework/data/repository/query/QueryMethodEvaluationContextProvider.java +++ b/src/main/java/org/springframework/data/repository/query/QueryMethodEvaluationContextProvider.java @@ -30,7 +30,7 @@ import org.springframework.expression.EvaluationContext; */ public interface QueryMethodEvaluationContextProvider { - static QueryMethodEvaluationContextProvider DEFAULT = new ExtensionAwareQueryMethodEvaluationContextProvider( + QueryMethodEvaluationContextProvider DEFAULT = new ExtensionAwareQueryMethodEvaluationContextProvider( Collections.emptyList()); /** diff --git a/src/main/java/org/springframework/data/repository/query/ReactiveExtensionAwareQueryMethodEvaluationContextProvider.java b/src/main/java/org/springframework/data/repository/query/ReactiveExtensionAwareQueryMethodEvaluationContextProvider.java index 9e793a90e..e93eeba19 100644 --- a/src/main/java/org/springframework/data/repository/query/ReactiveExtensionAwareQueryMethodEvaluationContextProvider.java +++ b/src/main/java/org/springframework/data/repository/query/ReactiveExtensionAwareQueryMethodEvaluationContextProvider.java @@ -68,15 +68,50 @@ public class ReactiveExtensionAwareQueryMethodEvaluationContextProvider this.delegate = new ReactiveExtensionAwareEvaluationContextProvider(extensions); } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.QueryMethodEvaluationContextProvider#getEvaluationContext(org.springframework.data.repository.query.Parameters, java.lang.Object[]) + */ + @Override + public > EvaluationContext getEvaluationContext(T parameters, Object[] parameterValues) { + + EvaluationContext evaluationContext = delegate.getEvaluationContext(parameterValues); + + if (evaluationContext instanceof StandardEvaluationContext) { + ((StandardEvaluationContext) evaluationContext).setVariables( + ExtensionAwareQueryMethodEvaluationContextProvider.collectVariables(parameters, parameterValues)); + } + + return evaluationContext; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.query.QueryMethodEvaluationContextProvider#getEvaluationContext(org.springframework.data.repository.query.Parameters, java.lang.Object[], org.springframework.data.spel.ExpressionDependencies) + */ + @Override + public > EvaluationContext getEvaluationContext(T parameters, Object[] parameterValues, + ExpressionDependencies dependencies) { + + EvaluationContext evaluationContext = delegate.getEvaluationContext(parameterValues, dependencies); + + if (evaluationContext instanceof StandardEvaluationContext) { + ((StandardEvaluationContext) evaluationContext).setVariables( + ExtensionAwareQueryMethodEvaluationContextProvider.collectVariables(parameters, parameterValues)); + } + + return evaluationContext; + } + /* * (non-Javadoc) * @see org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider#getEvaluationContext(org.springframework.data.repository.query.Parameters, java.lang.Object[]) */ @Override - public > Mono getEvaluationContext(T parameters, + public > Mono getEvaluationContextLater(T parameters, Object[] parameterValues) { - Mono evaluationContext = delegate.getEvaluationContext(parameterValues); + Mono evaluationContext = delegate.getEvaluationContextLater(parameterValues); return evaluationContext .doOnNext(it -> it.setVariables( @@ -84,15 +119,16 @@ public class ReactiveExtensionAwareQueryMethodEvaluationContextProvider .cast(EvaluationContext.class); } - /* + /* * (non-Javadoc) - * @see org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider#getEvaluationContext(org.springframework.data.repository.query.Parameters, java.lang.Object[], ExpressionDependencies) + * @see org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider#getEvaluationContextLater(org.springframework.data.repository.query.Parameters, java.lang.Object[], org.springframework.data.spel.ExpressionDependencies) */ @Override - public > Mono getEvaluationContext(T parameters, + public > Mono getEvaluationContextLater(T parameters, Object[] parameterValues, ExpressionDependencies dependencies) { - Mono evaluationContext = delegate.getEvaluationContext(parameterValues, dependencies); + Mono evaluationContext = delegate.getEvaluationContextLater(parameterValues, + dependencies); return evaluationContext .doOnNext(it -> it.setVariables( diff --git a/src/main/java/org/springframework/data/repository/query/ReactiveQueryMethodEvaluationContextProvider.java b/src/main/java/org/springframework/data/repository/query/ReactiveQueryMethodEvaluationContextProvider.java index 6316ce7cd..7dd25f0ad 100644 --- a/src/main/java/org/springframework/data/repository/query/ReactiveQueryMethodEvaluationContextProvider.java +++ b/src/main/java/org/springframework/data/repository/query/ReactiveQueryMethodEvaluationContextProvider.java @@ -17,33 +17,40 @@ package org.springframework.data.repository.query; import reactor.core.publisher.Mono; +import java.util.Collections; + import org.springframework.data.spel.ExpressionDependencies; import org.springframework.expression.EvaluationContext; /** - * Provides a way to access a centrally defined potentially shared {@link EvaluationContext}. + * Provides a way to access a centrally defined potentially shared {@link EvaluationContext} by considering + * {@link org.springframework.data.spel.spi.ReactiveEvaluationContextExtension}. * * @author Mark Paluch * @since 2.4 */ -public interface ReactiveQueryMethodEvaluationContextProvider { +public interface ReactiveQueryMethodEvaluationContextProvider extends QueryMethodEvaluationContextProvider { + + ReactiveQueryMethodEvaluationContextProvider DEFAULT = new ReactiveExtensionAwareQueryMethodEvaluationContextProvider( + Collections.emptyList()); /** * Returns an {@link EvaluationContext} built using the given {@link Parameters} and parameter values. * * @param parameters the {@link Parameters} instance obtained from the query method the context is built for. * @param parameterValues the values for the parameters. - * @return + * @return a mono that emits exactly one {@link EvaluationContext}. */ - > Mono getEvaluationContext(T parameters, Object[] parameterValues); + > Mono getEvaluationContextLater(T parameters, + Object[] parameterValues); /** * Returns an {@link EvaluationContext} built using the given {@link Parameters} and parameter values. * * @param parameters the {@link Parameters} instance obtained from the query method the context is built for. * @param parameterValues the values for the parameters. - * @return + * @return a mono that emits exactly one {@link EvaluationContext}. */ - > Mono getEvaluationContext(T parameters, Object[] parameterValues, + > Mono getEvaluationContextLater(T parameters, Object[] parameterValues, ExpressionDependencies dependencies); } diff --git a/src/main/java/org/springframework/data/spel/ExpressionDependencies.java b/src/main/java/org/springframework/data/spel/ExpressionDependencies.java index 8f4b5dcba..d7c114994 100644 --- a/src/main/java/org/springframework/data/spel/ExpressionDependencies.java +++ b/src/main/java/org/springframework/data/spel/ExpressionDependencies.java @@ -18,7 +18,9 @@ package org.springframework.data.spel; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import java.util.function.Consumer; import org.springframework.data.util.Streamable; @@ -28,6 +30,7 @@ import org.springframework.expression.spel.ast.CompoundExpression; import org.springframework.expression.spel.ast.MethodReference; import org.springframework.expression.spel.ast.PropertyOrFieldReference; import org.springframework.expression.spel.standard.SpelExpression; +import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** @@ -46,6 +49,15 @@ public class ExpressionDependencies implements Streamable dependencySet = new LinkedHashSet<>(this.dependencies.size() + other.dependencies.size()); + dependencySet.addAll(this.dependencies); + dependencySet.addAll(other.dependencies); + + return new ExpressionDependencies(new ArrayList<>(dependencySet)); + } + + /* * (non-Javadoc) * @see java.lang.Iterable#iterator() */ @@ -103,7 +133,7 @@ public class ExpressionDependencies implements Streamable true)); } - /* (non-Javadoc) - * @see org.springframework.data.spel.EvaluationContextProvider#getEvaluationContext(Object, Collection, ExpressionDependencies) + /* + * (non-Javadoc) + * @see org.springframework.data.spel.EvaluationContextProvider#getEvaluationContext(Object, ExpressionDependencies) */ @Override public StandardEvaluationContext getEvaluationContext(Object rootObject, ExpressionDependencies dependencies) { diff --git a/src/main/java/org/springframework/data/spel/ReactiveEvaluationContextProvider.java b/src/main/java/org/springframework/data/spel/ReactiveEvaluationContextProvider.java index 25566e433..80eb5d863 100644 --- a/src/main/java/org/springframework/data/spel/ReactiveEvaluationContextProvider.java +++ b/src/main/java/org/springframework/data/spel/ReactiveEvaluationContextProvider.java @@ -25,16 +25,15 @@ import org.springframework.expression.EvaluationContext; * @author Mark Paluch * @since 2.4 */ -@FunctionalInterface -public interface ReactiveEvaluationContextProvider { +public interface ReactiveEvaluationContextProvider extends EvaluationContextProvider { /** * Returns an {@link EvaluationContext} built using the given parameter values. * * @param rootObject the root object to set in the {@link EvaluationContext}. - * @return mono emitting the {@link EvaluationContext}. + * @return a mono that emits exactly one {@link EvaluationContext}. */ - Mono getEvaluationContext(Object rootObject); + Mono getEvaluationContextLater(Object rootObject); /** * Returns a tailored {@link EvaluationContext} built using the given parameter values and considering @@ -44,11 +43,11 @@ public interface ReactiveEvaluationContextProvider { * * @param rootObject the root object to set in the {@link EvaluationContext}. * @param dependencies the requested expression dependencies to be available. - * @return mono emitting the {@link EvaluationContext}. + * @return a mono that emits exactly one {@link EvaluationContext}. * @since 2.4 */ - default Mono getEvaluationContext(Object rootObject, + default Mono getEvaluationContextLater(Object rootObject, ExpressionDependencies dependencies) { - return getEvaluationContext(rootObject); + return getEvaluationContextLater(rootObject); } } diff --git a/src/main/java/org/springframework/data/spel/ReactiveExtensionAwareEvaluationContextProvider.java b/src/main/java/org/springframework/data/spel/ReactiveExtensionAwareEvaluationContextProvider.java index 719fceab8..e0f027514 100644 --- a/src/main/java/org/springframework/data/spel/ReactiveExtensionAwareEvaluationContextProvider.java +++ b/src/main/java/org/springframework/data/spel/ReactiveExtensionAwareEvaluationContextProvider.java @@ -27,7 +27,6 @@ import org.springframework.core.ResolvableType; import org.springframework.data.spel.spi.EvaluationContextExtension; import org.springframework.data.spel.spi.ExtensionIdAware; import org.springframework.data.spel.spi.ReactiveEvaluationContextExtension; -import org.springframework.data.util.Lazy; import org.springframework.data.util.ReflectionUtils; import org.springframework.expression.EvaluationContext; import org.springframework.expression.spel.support.StandardEvaluationContext; @@ -37,6 +36,7 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; * {@link ReactiveEvaluationContextExtension} and {@link EvaluationContextExtension} instances. * * @author Mark Paluch + * @since 2.4 */ public class ReactiveExtensionAwareEvaluationContextProvider implements ReactiveEvaluationContextProvider { @@ -49,32 +49,67 @@ public class ReactiveExtensionAwareEvaluationContextProvider implements Reactive evaluationContextProvider = new ExtensionAwareEvaluationContextProvider(); } + /** + * Create a new {@link ReactiveExtensionAwareEvaluationContextProvider} with extensions looked up lazily from the + * given {@link ListableBeanFactory}. + * + * @param beanFactory the {@link ListableBeanFactory} to lookup extensions from. + */ public ReactiveExtensionAwareEvaluationContextProvider(ListableBeanFactory beanFactory) { evaluationContextProvider = new ExtensionAwareEvaluationContextProvider(beanFactory); } + /** + * Creates a new {@link ReactiveExtensionAwareEvaluationContextProvider} for the given + * {@link EvaluationContextExtension}s. + * + * @param extensions must not be {@literal null}. + */ public ReactiveExtensionAwareEvaluationContextProvider(Collection extensions) { evaluationContextProvider = new ExtensionAwareEvaluationContextProvider(extensions); } - public ReactiveExtensionAwareEvaluationContextProvider( - Lazy> extensions) { - evaluationContextProvider = new ExtensionAwareEvaluationContextProvider(extensions); + /* + * (non-Javadoc) + * @see org.springframework.data.spel.EvaluationContextProvider#getEvaluationContext(Object) + */ + @Override + public EvaluationContext getEvaluationContext(Object rootObject) { + return evaluationContextProvider.getEvaluationContext(rootObject); } + /* + * (non-Javadoc) + * @see org.springframework.data.spel.EvaluationContextProvider#getEvaluationContext(java.lang.Object, org.springframework.data.spel.ExpressionDependencies) + */ @Override - public Mono getEvaluationContext(Object rootObject) { + public EvaluationContext getEvaluationContext(Object rootObject, ExpressionDependencies dependencies) { + return evaluationContextProvider.getEvaluationContext(rootObject, dependencies); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.spel.ReactiveEvaluationContextProvider#getEvaluationContextLater(java.lang.Object) + */ + @Override + public Mono getEvaluationContextLater(Object rootObject) { return getExtensions(it -> true) // .map(it -> evaluationContextProvider.doGetEvaluationContext(rootObject, it)); } + /* + * (non-Javadoc) + * @see org.springframework.data.spel.ReactiveEvaluationContextProvider#getEvaluationContextLater(java.lang.Object, org.springframework.data.spel.ExpressionDependencies) + */ @Override - public Mono getEvaluationContext(Object rootObject, ExpressionDependencies dependencies) { + public Mono getEvaluationContextLater(Object rootObject, + ExpressionDependencies dependencies) { return getExtensions(it -> dependencies.stream().anyMatch(it::provides)) // .map(it -> evaluationContextProvider.doGetEvaluationContext(rootObject, it)); } + @SuppressWarnings({ "unchecked", "rawtypes" }) private Mono> getExtensions( Predicate extensionFilter) { @@ -83,6 +118,7 @@ public class ReactiveExtensionAwareEvaluationContextProvider implements Reactive return Flux.fromIterable(extensions).concatMap(it -> { if (it instanceof EvaluationContextExtension) { + EvaluationContextExtension extension = (EvaluationContextExtension) it; EvaluationContextExtensionInformation information = evaluationContextProvider.getOrCreateInformation(extension); @@ -114,13 +150,10 @@ public class ReactiveExtensionAwareEvaluationContextProvider implements Reactive }).collectList(); } - private ResolvableType getExtensionType(ExtensionIdAware extensionCandidate) { + private static ResolvableType getExtensionType(ExtensionIdAware extensionCandidate) { - ResolvableType extensionType = ResolvableType + return ResolvableType .forMethodReturnType(ReflectionUtils.findRequiredMethod(extensionCandidate.getClass(), "getExtension")) .getGeneric(0); - - return extensionType; } - } diff --git a/src/test/java/org/springframework/data/spel/ExpressionDependenciesUnitTests.java b/src/test/java/org/springframework/data/spel/ExpressionDependenciesUnitTests.java index 162050cac..59e3f1c40 100644 --- a/src/test/java/org/springframework/data/spel/ExpressionDependenciesUnitTests.java +++ b/src/test/java/org/springframework/data/spel/ExpressionDependenciesUnitTests.java @@ -30,9 +30,9 @@ import org.springframework.expression.spel.standard.SpelExpressionParser; */ class ExpressionDependenciesUnitTests { - SpelExpressionParser PARSER = new SpelExpressionParser(); + static final SpelExpressionParser PARSER = new SpelExpressionParser(); - @Test + @Test // DATACMNS-1108 void shouldExtractDependencies() { String expression = "hasRole('ROLE_ADMIN') ? '%' : principal.emailAddress"; @@ -46,7 +46,7 @@ class ExpressionDependenciesUnitTests { "principal"); } - @Test + @Test // DATACMNS-1108 void shouldExtractDependenciesFromMethodCallArgs() { String expression = "hasRole(principal.emailAddress)"; @@ -60,7 +60,7 @@ class ExpressionDependenciesUnitTests { "principal"); } - @Test + @Test // DATACMNS-1108 void shouldExtractFirstMethodAsDependency() { String expression = "hello().hasRole(principal.emailAddress, principal.somethingElse).somethingElse()"; @@ -74,4 +74,13 @@ class ExpressionDependenciesUnitTests { "principal"); } + @Test // DATACMNS-1108 + void shouldMergeDependencies() { + + ExpressionDependencies left = ExpressionDependencies.discover(PARSER.parseExpression("hasRole('ROLE_ADMIN')")); + ExpressionDependencies right = ExpressionDependencies.discover(PARSER.parseExpression("principal.somethingElse")); + + assertThat(left.mergeWith(right)).extracting(ExpressionDependencies.ExpressionDependency::getSymbol) + .containsOnly("hasRole", "principal"); + } } diff --git a/src/test/java/org/springframework/data/spel/ReactiveExtensionAwareEvaluationContextProviderUnitTests.java b/src/test/java/org/springframework/data/spel/ReactiveExtensionAwareEvaluationContextProviderUnitTests.java index 670f0badf..aef72b19b 100644 --- a/src/test/java/org/springframework/data/spel/ReactiveExtensionAwareEvaluationContextProviderUnitTests.java +++ b/src/test/java/org/springframework/data/spel/ReactiveExtensionAwareEvaluationContextProviderUnitTests.java @@ -57,7 +57,7 @@ class ReactiveExtensionAwareEvaluationContextProviderUnitTests { ReactiveExtensionAwareEvaluationContextProvider provider = new ReactiveExtensionAwareEvaluationContextProvider( Arrays.asList(SampleReactiveExtension.INSTANCE, GenericExtension.INSTANCE)); - provider.getEvaluationContext(new Object[0], dependencies).map(expression::getValue) // + provider.getEvaluationContextLater(new Object[0], dependencies).map(expression::getValue) // .as(StepVerifier::create) // .expectNext("Walter") // .verifyComplete(); @@ -75,7 +75,7 @@ class ReactiveExtensionAwareEvaluationContextProviderUnitTests { ReactiveExtensionAwareEvaluationContextProvider provider = new ReactiveExtensionAwareEvaluationContextProvider( Arrays.asList(SampleReactiveExtension.INSTANCE, GenericExtension.INSTANCE)); - provider.getEvaluationContext(new Object[0], dependencies).map(expression::getValue) // + provider.getEvaluationContextLater(new Object[0], dependencies).map(expression::getValue) // .as(StepVerifier::create) // .expectNext(true) // .verifyComplete(); @@ -93,7 +93,7 @@ class ReactiveExtensionAwareEvaluationContextProviderUnitTests { ReactiveExtensionAwareEvaluationContextProvider provider = new ReactiveExtensionAwareEvaluationContextProvider( Arrays.asList(SampleReactiveExtension.INSTANCE, GenericExtension.INSTANCE)); - provider.getEvaluationContext(new Object[0], dependencies).map(expression::getValue) // + provider.getEvaluationContextLater(new Object[0], dependencies).map(expression::getValue) // .as(StepVerifier::create) // .verifyError(SpelEvaluationException.class); @@ -110,7 +110,7 @@ class ReactiveExtensionAwareEvaluationContextProviderUnitTests { ReactiveExtensionAwareEvaluationContextProvider provider = new ReactiveExtensionAwareEvaluationContextProvider( Collections.singletonList(GenericReactiveExtension.INSTANCE)); - provider.getEvaluationContext(new Object[0], dependencies).map(expression::getValue) // + provider.getEvaluationContextLater(new Object[0], dependencies).map(expression::getValue) // .as(StepVerifier::create) // .expectNext(2) // .verifyComplete();