Skip to content

Commit 096836e

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 Original pull request: #3050
1 parent e9ae6c7 commit 096836e

31 files changed

+1506
-162
lines changed

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

+6
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,10 @@ public String evaluate(ValueEvaluationContext context) {
7272

7373
return builder.toString();
7474
}
75+
76+
@Override
77+
public Class<?> getValueType(ValueEvaluationContext context) {
78+
return String.class;
79+
}
80+
7581
}

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) {

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

+9-4
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,14 @@ public boolean isLiteral() {
4747
public Object evaluate(ValueEvaluationContext context) {
4848

4949
EvaluationContext evaluationContext = context.getEvaluationContext();
50-
if (evaluationContext != null) {
51-
return expression.getValue(evaluationContext);
52-
}
53-
return expression.getValue();
50+
return evaluationContext != null ? expression.getValue(evaluationContext) : expression.getValue();
5451
}
52+
53+
@Override
54+
public Class<?> getValueType(ValueEvaluationContext context) {
55+
56+
EvaluationContext evaluationContext = context.getEvaluationContext();
57+
return evaluationContext != null ? expression.getValueType(evaluationContext) : expression.getValueType();
58+
}
59+
5560
}

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

+5
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,9 @@ public String evaluate(ValueEvaluationContext context) {
3939
return expression;
4040
}
4141

42+
@Override
43+
public Class<?> getValueType(ValueEvaluationContext context) {
44+
return String.class;
45+
}
46+
4247
}

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public boolean isLiteral() {
3838
}
3939

4040
@Override
41-
public Object evaluate(ValueEvaluationContext context) {
41+
public String evaluate(ValueEvaluationContext context) {
4242

4343
Environment environment = context.getEnvironment();
4444
if (environment != null) {
@@ -51,4 +51,9 @@ public Object evaluate(ValueEvaluationContext context) {
5151
return expression;
5252
}
5353

54+
@Override
55+
public Class<?> getValueType(ValueEvaluationContext context) {
56+
return String.class;
57+
}
58+
5459
}
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/ValueExpression.java

+11
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,15 @@ default ExpressionDependencies getExpressionDependencies() {
6262
@Nullable
6363
Object evaluate(ValueEvaluationContext context) throws EvaluationException;
6464

65+
/**
66+
* Return the most general type that the expression would use as return type for the given context.
67+
*
68+
* @param context the context in which to evaluate the expression.
69+
* @return the most general type of value.
70+
* @throws EvaluationException if there is a problem determining the type
71+
* @since 3.4
72+
*/
73+
@Nullable
74+
Class<?> getValueType(ValueEvaluationContext context) throws EvaluationException;
75+
6576
}

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)