Skip to content

Commit 622087a

Browse files
committed
Introduce ValueEvaluationContextProvider and ValueExpressionEvaluator.
CachingValueExpressionEvaluatorFactory is a support utility to replace DefaultSpELExpressionEvaluator with support for Value Expressions. Deprecate DefaultSpELExpressionEvaluator and SpELExpressionEvaluator. Revise Parameter type to return value expressions and deprecate getSpel… methods.
1 parent 846aa5a commit 622087a

16 files changed

+520
-68
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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 org.springframework.data.spel.ExpressionDependencies;
19+
import org.springframework.expression.EvaluationContext;
20+
21+
/**
22+
* SPI to provide to access a centrally defined potentially shared {@link ValueEvaluationContext}.
23+
*
24+
* @author Mark Paluch
25+
* @since 3.3
26+
*/
27+
public interface ValueEvaluationContextProvider {
28+
29+
/**
30+
* Return a {@link EvaluationContext} built using the given parameter values.
31+
*
32+
* @param rootObject the root object to set in the {@link EvaluationContext}.
33+
* @return
34+
*/
35+
ValueEvaluationContext getEvaluationContext(Object rootObject);
36+
37+
/**
38+
* Return a tailored {@link EvaluationContext} built using the given parameter values and considering
39+
* {@link ExpressionDependencies expression dependencies}. The returned {@link EvaluationContext} may contain a
40+
* reduced visibility of methods and properties/fields according to the required {@link ExpressionDependencies
41+
* expression dependencies}.
42+
*
43+
* @param rootObject the root object to set in the {@link EvaluationContext}.
44+
* @param dependencies the requested expression dependencies to be available.
45+
* @return
46+
*/
47+
default ValueEvaluationContext getEvaluationContext(Object rootObject, ExpressionDependencies dependencies) {
48+
return getEvaluationContext(rootObject);
49+
}
50+
}

src/main/java/org/springframework/data/mapping/Parameter.java

+52-11
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ public class Parameter<T, P extends PersistentProperty<P>> {
3939
private final @Nullable String name;
4040
private final TypeInformation<T> type;
4141
private final MergedAnnotations annotations;
42-
private final String key;
42+
private final @Nullable String expression;
4343
private final @Nullable PersistentEntity<T, P> entity;
4444

4545
private final Lazy<Boolean> enclosingClassCache;
46-
private final Lazy<Boolean> hasSpelExpression;
46+
private final Lazy<Boolean> hasExpression;
4747

4848
/**
4949
* Creates a new {@link Parameter} with the given name, {@link TypeInformation} as well as an array of
@@ -64,7 +64,7 @@ public Parameter(@Nullable String name, TypeInformation<T> type, Annotation[] an
6464
this.name = name;
6565
this.type = type;
6666
this.annotations = MergedAnnotations.from(annotations);
67-
this.key = getValue(this.annotations);
67+
this.expression = getValue(this.annotations);
6868
this.entity = entity;
6969

7070
this.enclosingClassCache = Lazy.of(() -> {
@@ -77,7 +77,7 @@ public Parameter(@Nullable String name, TypeInformation<T> type, Annotation[] an
7777
return ClassUtils.isInnerClass(owningType) && type.getType().equals(owningType.getEnclosingClass());
7878
});
7979

80-
this.hasSpelExpression = Lazy.of(() -> StringUtils.hasText(getSpelExpression()));
80+
this.hasExpression = Lazy.of(() -> StringUtils.hasText(getValueExpression()));
8181
}
8282

8383
@Nullable
@@ -128,21 +128,62 @@ public Class<T> getRawType() {
128128
}
129129

130130
/**
131-
* Returns the key to be used when looking up a source data structure to populate the actual parameter value.
131+
* Returns the expression to be used when looking up a source data structure to populate the actual parameter value.
132132
*
133-
* @return
133+
* @return the expression to be used when looking up a source data structure.
134+
* @deprecated since 3.3, use {@link #getValueExpression()} instead.
134135
*/
136+
@Nullable
135137
public String getSpelExpression() {
136-
return key;
138+
return getValueExpression();
139+
}
140+
141+
/**
142+
* Returns the expression to be used when looking up a source data structure to populate the actual parameter value.
143+
*
144+
* @return the expression to be used when looking up a source data structure.
145+
* @since 3.3
146+
*/
147+
@Nullable
148+
public String getValueExpression() {
149+
return expression;
150+
}
151+
152+
/**
153+
* Returns the required expression to be used when looking up a source data structure to populate the actual parameter
154+
* value or throws {@link IllegalStateException} if there's no expression.
155+
*
156+
* @return the expression to be used when looking up a source data structure.
157+
* @since 3.3
158+
*/
159+
public String getRequiredValueExpression() {
160+
161+
if (!hasValueExpression()) {
162+
throw new IllegalStateException("No expression associated with this parameter");
163+
}
164+
165+
return getValueExpression();
137166
}
138167

139168
/**
140169
* Returns whether the constructor parameter is equipped with a SpEL expression.
141170
*
142-
* @return
171+
* @return {@literal true}} if the parameter is equipped with a SpEL expression.
172+
* @deprecated since 3.3, use {@link #hasValueExpression()} instead.
143173
*/
174+
@Deprecated(since = "3.3")
144175
public boolean hasSpelExpression() {
145-
return this.hasSpelExpression.get();
176+
return hasValueExpression();
177+
}
178+
179+
/**
180+
* Returns whether the constructor parameter is equipped with a value expression.
181+
*
182+
* @return {@literal true}} if the parameter is equipped with a value expression.
183+
* @since 3.3
184+
*/
185+
public boolean hasValueExpression() {
186+
return this.hasExpression.get();
146187
}
147188

148189
@Override
@@ -157,12 +198,12 @@ public boolean equals(@Nullable Object o) {
157198
}
158199

159200
return Objects.equals(this.name, that.name) && Objects.equals(this.type, that.type)
160-
&& Objects.equals(this.key, that.key) && Objects.equals(this.entity, that.entity);
201+
&& Objects.equals(this.expression, that.expression) && Objects.equals(this.entity, that.entity);
161202
}
162203

163204
@Override
164205
public int hashCode() {
165-
return Objects.hash(name, type, key, entity);
206+
return Objects.hash(name, type, expression, entity);
166207
}
167208

168209
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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.mapping.model;
17+
18+
import org.springframework.core.env.EnvironmentCapable;
19+
import org.springframework.data.expression.ValueEvaluationContext;
20+
import org.springframework.data.expression.ValueEvaluationContextProvider;
21+
import org.springframework.data.expression.ValueExpression;
22+
import org.springframework.data.expression.ValueExpressionParser;
23+
import org.springframework.data.spel.EvaluationContextProvider;
24+
import org.springframework.data.spel.ExpressionDependencies;
25+
import org.springframework.expression.ExpressionParser;
26+
import org.springframework.util.Assert;
27+
import org.springframework.util.ConcurrentLruCache;
28+
29+
/**
30+
* Factory to create a ValueExpressionEvaluator
31+
*
32+
* @author Mark Paluch
33+
* @since 3.3
34+
*/
35+
public class CachingValueExpressionEvaluatorFactory implements ValueEvaluationContextProvider {
36+
37+
private final ConcurrentLruCache<String, ValueExpression> expressionCache;
38+
private final EnvironmentCapable environmentProvider;
39+
private final EvaluationContextProvider evaluationContextProvider;
40+
41+
public CachingValueExpressionEvaluatorFactory(ExpressionParser expressionParser,
42+
EnvironmentCapable environmentProvider, EvaluationContextProvider evaluationContextProvider) {
43+
this(expressionParser, environmentProvider, evaluationContextProvider, 256);
44+
}
45+
46+
public CachingValueExpressionEvaluatorFactory(ExpressionParser expressionParser,
47+
EnvironmentCapable environmentProvider, EvaluationContextProvider evaluationContextProvider, int cacheSize) {
48+
49+
Assert.notNull(expressionParser, "ExpressionParser must not be null");
50+
51+
ValueExpressionParser parser = ValueExpressionParser.create(() -> expressionParser);
52+
this.expressionCache = new ConcurrentLruCache<>(cacheSize, parser::parse);
53+
this.environmentProvider = environmentProvider;
54+
this.evaluationContextProvider = evaluationContextProvider;
55+
}
56+
57+
@Override
58+
public ValueEvaluationContext getEvaluationContext(Object rootObject) {
59+
return ValueEvaluationContext.of(environmentProvider.getEnvironment(),
60+
evaluationContextProvider.getEvaluationContext(rootObject));
61+
}
62+
63+
@Override
64+
public ValueEvaluationContext getEvaluationContext(Object rootObject, ExpressionDependencies dependencies) {
65+
return ValueEvaluationContext.of(environmentProvider.getEnvironment(),
66+
evaluationContextProvider.getEvaluationContext(rootObject, dependencies));
67+
}
68+
69+
/**
70+
* Creates a new {@link ValueExpressionEvaluator} using the given {@code source} as root object.
71+
*
72+
* @param source the root object for evaluating the expression.
73+
* @return a new {@link ValueExpressionEvaluator} to evaluate the expression in the context of the given
74+
* {@code source} object.
75+
*/
76+
public ValueExpressionEvaluator create(Object source) {
77+
78+
return new ValueExpressionEvaluator() {
79+
@SuppressWarnings("unchecked")
80+
@Override
81+
public <T> T evaluate(String expression) {
82+
ValueExpression valueExpression = expressionCache.get(expression);
83+
return (T) valueExpression.evaluate(getEvaluationContext(source, valueExpression.getExpressionDependencies()));
84+
}
85+
};
86+
}
87+
88+
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
* {@link SpelExpressionParser} and {@link EvaluationContext}.
2929
*
3030
* @author Oliver Gierke
31+
* @deprecated since 3.3, use {@link CachingValueExpressionEvaluatorFactory} instead.
3132
*/
33+
@Deprecated(since = "3.3")
3234
public class DefaultSpELExpressionEvaluator implements SpELExpressionEvaluator {
3335

3436
private final Object source;

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

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public PersistentEntityParameterValueProvider(PersistentEntity<?, P> entity, Pro
4545
this.parent = parent;
4646
}
4747

48+
@Override
4849
@Nullable
4950
@SuppressWarnings("unchecked")
5051
public <T> T getParameterValue(Parameter<T, P> parameter) {

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import org.springframework.beans.factory.BeanFactory;
1919
import org.springframework.context.expression.BeanFactoryResolver;
20+
import org.springframework.data.spel.EvaluationContextProvider;
2021
import org.springframework.expression.EvaluationContext;
2122
import org.springframework.expression.ExpressionParser;
2223
import org.springframework.expression.PropertyAccessor;
@@ -30,7 +31,7 @@
3031
*
3132
* @author Oliver Gierke
3233
*/
33-
public class SpELContext {
34+
public class SpELContext implements EvaluationContextProvider {
3435

3536
private final SpelExpressionParser parser;
3637
private final PropertyAccessor accessor;
@@ -90,6 +91,7 @@ public ExpressionParser getParser() {
9091
return this.parser;
9192
}
9293

94+
@Override
9395
public EvaluationContext getEvaluationContext(Object source) {
9496

9597
StandardEvaluationContext evaluationContext = new StandardEvaluationContext(source);

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

+2-11
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,12 @@
1515
*/
1616
package org.springframework.data.mapping.model;
1717

18-
import org.springframework.lang.Nullable;
19-
2018
/**
2119
* SPI for components that can evaluate Spring EL expressions.
2220
*
2321
* @author Oliver Gierke
2422
*/
25-
public interface SpELExpressionEvaluator {
23+
@Deprecated(since = "3.3")
24+
public interface SpELExpressionEvaluator extends ValueExpressionEvaluator {
2625

27-
/**
28-
* Evaluates the given expression.
29-
*
30-
* @param expression
31-
* @return
32-
*/
33-
@Nullable
34-
<T> T evaluate(String expression);
3526
}

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

+4-33
Original file line numberDiff line numberDiff line change
@@ -16,53 +16,24 @@
1616
package org.springframework.data.mapping.model;
1717

1818
import org.springframework.core.convert.ConversionService;
19-
import org.springframework.data.mapping.Parameter;
2019
import org.springframework.data.mapping.PersistentProperty;
21-
import org.springframework.lang.Nullable;
2220

2321
/**
2422
* {@link ParameterValueProvider} that can be used to front a {@link ParameterValueProvider} delegate to prefer a SpEL
2523
* expression evaluation over directly resolving the parameter value with the delegate.
2624
*
2725
* @author Oliver Gierke
2826
* @author Mark Paluch
27+
* @deprecated since 3.3, use {@link ValueExpressionParameterValueProvider} instead.
2928
*/
29+
@Deprecated(since = "3.3")
3030
public class SpELExpressionParameterValueProvider<P extends PersistentProperty<P>>
31-
implements ParameterValueProvider<P> {
32-
33-
private final SpELExpressionEvaluator evaluator;
34-
private final ConversionService conversionService;
35-
private final ParameterValueProvider<P> delegate;
31+
extends ValueExpressionParameterValueProvider<P> implements ParameterValueProvider<P> {
3632

3733
public SpELExpressionParameterValueProvider(SpELExpressionEvaluator evaluator, ConversionService conversionService,
3834
ParameterValueProvider<P> delegate) {
3935

40-
this.evaluator = evaluator;
41-
this.conversionService = conversionService;
42-
this.delegate = delegate;
43-
}
44-
45-
@Nullable
46-
public <T> T getParameterValue(Parameter<T, P> parameter) {
47-
48-
if (!parameter.hasSpelExpression()) {
49-
return delegate == null ? null : delegate.getParameterValue(parameter);
50-
}
51-
52-
Object object = evaluator.evaluate(parameter.getSpelExpression());
53-
return object == null ? null : potentiallyConvertSpelValue(object, parameter);
36+
super(evaluator, conversionService, delegate);
5437
}
5538

56-
/**
57-
* Hook to allow to massage the value resulting from the Spel expression evaluation. Default implementation will
58-
* leverage the configured {@link ConversionService} to massage the value into the parameter type.
59-
*
60-
* @param object the value to massage, will never be {@literal null}.
61-
* @param parameter the {@link Parameter} we create the value for
62-
* @return
63-
*/
64-
@Nullable
65-
protected <T> T potentiallyConvertSpelValue(Object object, Parameter<T, P> parameter) {
66-
return conversionService.convert(object, parameter.getRawType());
67-
}
6839
}

0 commit comments

Comments
 (0)