Skip to content

Commit 83a7ee5

Browse files
committed
Add ValueExpression infrastructure for query methods.
Introduce ValueExpressionQueryRewriter as replacement for SpelQueryContext and QueryMethodValueEvaluationContextAccessor to encapsulate common ValueExpression functionality for Spring Data modules wanting to resolve Value Expressions in query methods. Reduce dependencies in RepositoryFactoryBeanSupport and RepositoryFactorySupport to EvaluationContextProvider instead of QueryMethodEvaluationContextProvider to simplify dependencies. Deprecate QueryMethodEvaluationContextProvider and its reactive variant for future removal. Closes #3049
1 parent c905402 commit 83a7ee5

27 files changed

+1406
-157
lines changed

src/main/java/org/springframework/data/expression/DefaultValueEvaluationContext.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@
1717

1818
import org.springframework.core.env.Environment;
1919
import org.springframework.expression.EvaluationContext;
20+
import org.springframework.lang.Nullable;
2021

2122
/**
2223
* Default {@link ValueEvaluationContext}.
2324
*
2425
* @author Mark Paluch
2526
* @since 3.3
2627
*/
27-
record DefaultValueEvaluationContext(Environment environment,
28+
record DefaultValueEvaluationContext(@Nullable Environment environment,
2829
EvaluationContext evaluationContext) implements ValueEvaluationContext {
2930

3031
@Override

src/main/java/org/springframework/data/expression/DefaultValueExpressionParser.java

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.expression.Expression;
2323
import org.springframework.expression.ParseException;
2424
import org.springframework.expression.ParserContext;
25+
import org.springframework.expression.spel.standard.SpelExpressionParser;
2526
import org.springframework.util.Assert;
2627
import org.springframework.util.SystemPropertyUtils;
2728

@@ -39,6 +40,8 @@ class DefaultValueExpressionParser implements ValueExpressionParser {
3940
public static final int PLACEHOLDER_PREFIX_LENGTH = PLACEHOLDER_PREFIX.length();
4041
public static final char[] QUOTE_CHARS = { '\'', '"' };
4142

43+
public static final ValueExpressionParser DEFAULT = new DefaultValueExpressionParser(SpelExpressionParser::new);
44+
4245
private final ValueParserConfiguration configuration;
4346

4447
public DefaultValueExpressionParser(ValueParserConfiguration configuration) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.expression;
17+
18+
import reactor.core.publisher.Mono;
19+
20+
import org.springframework.data.spel.ExpressionDependencies;
21+
import org.springframework.lang.Nullable;
22+
23+
/**
24+
* Reactive extension to {@link ValueEvaluationContext} for obtaining a {@link ValueEvaluationContext} that participates
25+
* in the reactive flow.
26+
*
27+
* @author Mark Paluch
28+
* @since 3.4
29+
*/
30+
public interface ReactiveValueEvaluationContextProvider extends ValueEvaluationContextProvider {
31+
32+
/**
33+
* Return a {@link ValueEvaluationContext} built using the given parameter values.
34+
*
35+
* @param rootObject the root object to set in the {@link ValueEvaluationContext}.
36+
* @return a mono that emits exactly one {@link ValueEvaluationContext}.
37+
*/
38+
Mono<ValueEvaluationContext> getEvaluationContextLater(@Nullable Object rootObject);
39+
40+
/**
41+
* Return a tailored {@link ValueEvaluationContext} built using the given parameter values and considering
42+
* {@link ExpressionDependencies expression dependencies}. The returned {@link ValueEvaluationContext} may contain a
43+
* reduced visibility of methods and properties/fields according to the required {@link ExpressionDependencies
44+
* expression dependencies}.
45+
*
46+
* @param rootObject the root object to set in the {@link ValueEvaluationContext}.
47+
* @param dependencies the requested expression dependencies to be available.
48+
* @return a mono that emits exactly one {@link ValueEvaluationContext}.
49+
*/
50+
default Mono<ValueEvaluationContext> getEvaluationContextLater(@Nullable Object rootObject,
51+
ExpressionDependencies dependencies) {
52+
return getEvaluationContextLater(rootObject);
53+
}
54+
}

src/main/java/org/springframework/data/expression/ValueEvaluationContext.java

+20-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public interface ValueEvaluationContext {
3636
* @param evaluationContext
3737
* @return a new {@link ValueEvaluationContext} for the given environment and evaluation context.
3838
*/
39-
static ValueEvaluationContext of(Environment environment, EvaluationContext evaluationContext) {
39+
static ValueEvaluationContext of(@Nullable Environment environment, EvaluationContext evaluationContext) {
4040
return new DefaultValueEvaluationContext(environment, evaluationContext);
4141
}
4242

@@ -51,8 +51,26 @@ static ValueEvaluationContext of(Environment environment, EvaluationContext eval
5151
/**
5252
* Returns the {@link EvaluationContext} if provided.
5353
*
54-
* @return the {@link EvaluationContext} or {@literal null}.
54+
* @return the {@link EvaluationContext} or {@literal null} if not set.
5555
*/
5656
@Nullable
5757
EvaluationContext getEvaluationContext();
58+
59+
/**
60+
* Returns the required {@link EvaluationContext} or throws {@link IllegalStateException} if there is no evaluation
61+
* context available.
62+
*
63+
* @return the {@link EvaluationContext}.
64+
* @since 3.4
65+
*/
66+
default EvaluationContext getRequiredEvaluationContext() {
67+
68+
EvaluationContext evaluationContext = getEvaluationContext();
69+
70+
if (evaluationContext == null) {
71+
throw new IllegalStateException("No evaluation context available");
72+
}
73+
74+
return evaluationContext;
75+
}
5876
}

src/main/java/org/springframework/data/expression/ValueEvaluationContextProvider.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717

1818
import org.springframework.data.spel.ExpressionDependencies;
1919
import org.springframework.expression.EvaluationContext;
20+
import org.springframework.lang.Nullable;
2021

2122
/**
2223
* SPI to provide to access a centrally defined potentially shared {@link ValueEvaluationContext}.
2324
*
2425
* @author Mark Paluch
2526
* @since 3.3
2627
*/
28+
@FunctionalInterface
2729
public interface ValueEvaluationContextProvider {
2830

2931
/**
@@ -32,7 +34,7 @@ public interface ValueEvaluationContextProvider {
3234
* @param rootObject the root object to set in the {@link EvaluationContext}.
3335
* @return
3436
*/
35-
ValueEvaluationContext getEvaluationContext(Object rootObject);
37+
ValueEvaluationContext getEvaluationContext(@Nullable Object rootObject);
3638

3739
/**
3840
* Return a tailored {@link EvaluationContext} built using the given parameter values and considering
@@ -44,7 +46,8 @@ public interface ValueEvaluationContextProvider {
4446
* @param dependencies the requested expression dependencies to be available.
4547
* @return
4648
*/
47-
default ValueEvaluationContext getEvaluationContext(Object rootObject, ExpressionDependencies dependencies) {
49+
default ValueEvaluationContext getEvaluationContext(@Nullable Object rootObject,
50+
ExpressionDependencies dependencies) {
4851
return getEvaluationContext(rootObject);
4952
}
5053
}

src/main/java/org/springframework/data/expression/ValueExpressionParser.java

+10
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@
2727
*/
2828
public interface ValueExpressionParser {
2929

30+
/**
31+
* Creates a default parser to parse expression strings.
32+
*
33+
* @return the parser instance.
34+
* @since 3.4
35+
*/
36+
static ValueExpressionParser create() {
37+
return DefaultValueExpressionParser.DEFAULT;
38+
}
39+
3040
/**
3141
* Creates a new parser to parse expression strings.
3242
*

src/main/java/org/springframework/data/mapping/model/CachingValueExpressionEvaluatorFactory.java

+21-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.data.spel.EvaluationContextProvider;
2424
import org.springframework.data.spel.ExpressionDependencies;
2525
import org.springframework.expression.ExpressionParser;
26+
import org.springframework.lang.Nullable;
2627
import org.springframework.util.Assert;
2728
import org.springframework.util.ConcurrentLruCache;
2829

@@ -38,11 +39,29 @@ public class CachingValueExpressionEvaluatorFactory implements ValueEvaluationCo
3839
private final EnvironmentCapable environmentProvider;
3940
private final EvaluationContextProvider evaluationContextProvider;
4041

42+
/**
43+
* Creates a new {@link CachingValueExpressionEvaluatorFactory} for the given {@link ExpressionParser},
44+
* {@link EnvironmentCapable Environment provider} and {@link EvaluationContextProvider} with a cache size of 256.
45+
*
46+
* @param expressionParser
47+
* @param environmentProvider
48+
* @param evaluationContextProvider
49+
*/
4150
public CachingValueExpressionEvaluatorFactory(ExpressionParser expressionParser,
4251
EnvironmentCapable environmentProvider, EvaluationContextProvider evaluationContextProvider) {
4352
this(expressionParser, environmentProvider, evaluationContextProvider, 256);
4453
}
4554

55+
/**
56+
* Creates a new {@link CachingValueExpressionEvaluatorFactory} for the given {@link ExpressionParser},
57+
* {@link EnvironmentCapable Environment provider} and {@link EvaluationContextProvider} with a specific
58+
* {@code cacheSize}.
59+
*
60+
* @param expressionParser
61+
* @param environmentProvider
62+
* @param evaluationContextProvider
63+
* @param cacheSize
64+
*/
4665
public CachingValueExpressionEvaluatorFactory(ExpressionParser expressionParser,
4766
EnvironmentCapable environmentProvider, EvaluationContextProvider evaluationContextProvider, int cacheSize) {
4867

@@ -55,13 +74,13 @@ public CachingValueExpressionEvaluatorFactory(ExpressionParser expressionParser,
5574
}
5675

5776
@Override
58-
public ValueEvaluationContext getEvaluationContext(Object rootObject) {
77+
public ValueEvaluationContext getEvaluationContext(@Nullable Object rootObject) {
5978
return ValueEvaluationContext.of(environmentProvider.getEnvironment(),
6079
evaluationContextProvider.getEvaluationContext(rootObject));
6180
}
6281

6382
@Override
64-
public ValueEvaluationContext getEvaluationContext(Object rootObject, ExpressionDependencies dependencies) {
83+
public ValueEvaluationContext getEvaluationContext(@Nullable Object rootObject, ExpressionDependencies dependencies) {
6584
return ValueEvaluationContext.of(environmentProvider.getEnvironment(),
6685
evaluationContextProvider.getEvaluationContext(rootObject, dependencies));
6786
}

src/main/java/org/springframework/data/repository/core/support/ReactiveRepositoryFactorySupport.java

+28
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,20 @@
1717

1818
import java.lang.reflect.Method;
1919
import java.util.Arrays;
20+
import java.util.Optional;
2021

2122
import org.reactivestreams.Publisher;
23+
2224
import org.springframework.dao.InvalidDataAccessApiUsageException;
2325
import org.springframework.data.repository.core.RepositoryMetadata;
26+
import org.springframework.data.repository.query.QueryLookupStrategy;
2427
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
28+
import org.springframework.data.repository.query.ReactiveExtensionAwareQueryMethodEvaluationContextProvider;
2529
import org.springframework.data.repository.query.ReactiveQueryMethodEvaluationContextProvider;
30+
import org.springframework.data.repository.query.ValueExpressionDelegate;
2631
import org.springframework.data.repository.util.ReactiveWrapperConverters;
2732
import org.springframework.data.util.ReactiveWrappers;
33+
import org.springframework.lang.Nullable;
2834
import org.springframework.util.ClassUtils;
2935

3036
/**
@@ -69,6 +75,28 @@ public void setEvaluationContextProvider(QueryMethodEvaluationContextProvider ev
6975
: evaluationContextProvider);
7076
}
7177

78+
/**
79+
* Returns the {@link QueryLookupStrategy} for the given {@link QueryLookupStrategy.Key} and
80+
* {@link ValueExpressionDelegate}. Favor implementing this method over
81+
* {@link #getQueryLookupStrategy(QueryLookupStrategy.Key, QueryMethodEvaluationContextProvider)} for extended
82+
* {@link org.springframework.data.expression.ValueExpression} support.
83+
* <p>
84+
* This method delegates to
85+
* {@link #getQueryLookupStrategy(QueryLookupStrategy.Key, QueryMethodEvaluationContextProvider)} unless overridden.
86+
* </p>
87+
*
88+
* @param key can be {@literal null}.
89+
* @param valueExpressionDelegate will never be {@literal null}.
90+
* @return the {@link QueryLookupStrategy} to use or {@literal null} if no queries should be looked up.
91+
* @since 3.4
92+
*/
93+
@Override
94+
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable QueryLookupStrategy.Key key,
95+
ValueExpressionDelegate valueExpressionDelegate) {
96+
return getQueryLookupStrategy(key,
97+
new ReactiveExtensionAwareQueryMethodEvaluationContextProvider(getEvaluationContextProvider()));
98+
}
99+
72100
/**
73101
* We need to make sure that the necessary conversion libraries are in place if the repository interface uses RxJava 1
74102
* types.

0 commit comments

Comments
 (0)