Evolve ValueExpressionParser.
Introduce literal, expression and placeholder variants. Add parser for composite expressions. Closes #2369 Original pull request: #3036
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
** xref:repositories/null-handling.adoc[]
|
||||
** xref:repositories/projections.adoc[]
|
||||
* xref:query-by-example.adoc[]
|
||||
* xref:value-expressions.adoc[]
|
||||
* xref:auditing.adoc[]
|
||||
* xref:custom-conversions.adoc[]
|
||||
* xref:entity-callbacks.adoc[]
|
||||
|
||||
246
src/main/antora/modules/ROOT/pages/value-expressions.adoc
Normal file
246
src/main/antora/modules/ROOT/pages/value-expressions.adoc
Normal file
@@ -0,0 +1,246 @@
|
||||
[[valueexpressions.fundamentals]]
|
||||
= Value Expressions Fundamentals
|
||||
|
||||
Value Expressions are a combination of {spring-framework-docs}/core/expressions.html[Spring Expression Language (SpEL)] and {spring-framework-docs}/core/beans/environment.html#beans-placeholder-resolution-in-statements[Property Placeholder Resolution].
|
||||
They combine powerful evaluation of programmatic expressions with the simplicity to resort to property-placeholder resolution to obtain values from the `Environment` such as configuration properties.
|
||||
|
||||
Expressions are expected to be defined by a trusted input such as an annotation value and not to be determined from user input.
|
||||
|
||||
The following code demonstrates how to use expressions in the context of annotations.
|
||||
|
||||
.Annotation Usage
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Document("orders-#{tenantService.getOrderCollection()}-${tenant-config.suffix}")
|
||||
class Order {
|
||||
// …
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Value Expressions can be defined from a sole SpEL Expression, a Property Placeholder or a composite expression mixing various expressions including literals.
|
||||
|
||||
.Expression Examples
|
||||
====
|
||||
[source]
|
||||
----
|
||||
#{tenantService.getOrderCollection()} <1>
|
||||
#{(1+1) + '-hello-world'} <2>
|
||||
${tenant-config.suffix} <3>
|
||||
orders-${tenant-config.suffix} <4>
|
||||
#{tenantService.getOrderCollection()}-${tenant-config.suffix} <5>
|
||||
----
|
||||
|
||||
<1> Value Expression using a single SpEL Expression.
|
||||
<2> Value Expression using a static SpEL Expression evaluating to `2-hello-world`.
|
||||
<3> Value Expression using a single Property Placeholder.
|
||||
<4> Composite expression comprised of the literal `orders-` and the Property Placeholder `${tenant-config.suffix}`.
|
||||
<5> Composite expression using SpEL, Property Placeholders and literals.
|
||||
====
|
||||
|
||||
NOTE: Using value expressions introduces a lot of flexibility to your code.
|
||||
Doing so requires evaluation of the expression on each usage and, therefore, value expression evaluation has an impact on the performance profile.
|
||||
|
||||
[[valueexpressions.api]]
|
||||
== Parsing and Evaluation
|
||||
|
||||
Value Expressions are parsed by the `ValueExpressionParser` API.
|
||||
Instances of `ValueExpression` are thread-safe and can be cached for later use to avoid repeated parsing.
|
||||
|
||||
The following example shows the Value Expression API usage:
|
||||
|
||||
.Parsing and Evaluation
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
ValueParserConfiguration configuration = SpelExpressionParser::new;
|
||||
ValueEvaluationContext context = ValueEvaluationContext.of(environment, evaluationContext);
|
||||
|
||||
ValueExpressionParser parser = ValueExpressionParser.create(configuration);
|
||||
ValueExpression expression = parser.parse("Hello, World");
|
||||
Object result = expression.evaluate(context);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val configuration = ValueParserConfiguration { SpelExpressionParser() }
|
||||
val context = ValueEvaluationContext.of(environment, evaluationContext)
|
||||
|
||||
val parser = ValueExpressionParser.create(configuration)
|
||||
val expression: ValueExpression = parser.parse("Hello, World")
|
||||
val result: Any = expression.evaluate(context)
|
||||
----
|
||||
======
|
||||
|
||||
[[valueexpressions.spel]]
|
||||
== SpEL Expressions
|
||||
|
||||
{spring-framework-docs}/core/expressions.html[SpEL Expressions] follow the Template style where the expression is expected to be enclosed within the `#{…}` format.
|
||||
Expressions are evaluated using an `EvaluationContext` that is provided by `EvaluationContextProvider`.
|
||||
The context itself is a powerful `StandardEvaluationContext` allowing a wide range of operations, access to static types and context extensions.
|
||||
|
||||
NOTE: Make sure to parse and evaluate only expressions from trusted sources such as annotations.
|
||||
Accepting user-provided expressions can create an entry path to exploit the application context and your system resulting in a potential security vulnerability.
|
||||
|
||||
=== Extending the Evaluation Context
|
||||
|
||||
`EvaluationContextProvider` and its reactive variant `ReactiveEvaluationContextProvider` provide access to an `EvaluationContext`.
|
||||
`ExtensionAwareEvaluationContextProvider` and its reactive variant `ReactiveExtensionAwareEvaluationContextProvider` are default implementations that determine context extensions from an application context, specifically `ListableBeanFactory`.
|
||||
|
||||
Extensions implement either `EvaluationContextExtension` or `ReactiveEvaluationContextExtension` to provide extension support to hydrate `EvaluationContext`.
|
||||
That are a root object, properties and functions (top-level methods).
|
||||
|
||||
The following example shows a context extension that provides a root object, properties, functions and an aliased function.
|
||||
|
||||
.Implementing a `EvaluationContextExtension`
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Component
|
||||
public class MyExtension implements EvaluationContextExtension {
|
||||
|
||||
@Override
|
||||
public String getExtensionId() {
|
||||
return "my-extension";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getRootObject() {
|
||||
return new CustomExtensionRootObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getProperties() {
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
properties.put("key", "Hello");
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Function> getFunctions() {
|
||||
|
||||
Map<String, Function> functions = new HashMap<>();
|
||||
|
||||
try {
|
||||
functions.put("aliasedMethod", new Function(getClass().getMethod("extensionMethod")));
|
||||
return functions;
|
||||
} catch (Exception o_O) {
|
||||
throw new RuntimeException(o_O);
|
||||
}
|
||||
}
|
||||
|
||||
public static String extensionMethod() {
|
||||
return "Hello World";
|
||||
}
|
||||
|
||||
public static int add(int i1, int i2) {
|
||||
return i1 + i2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class CustomExtensionRootObject {
|
||||
|
||||
public boolean rootObjectInstanceMethod() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
class MyExtension : EvaluationContextExtension {
|
||||
|
||||
override fun getExtensionId(): String {
|
||||
return "my-extension"
|
||||
}
|
||||
|
||||
override fun getRootObject(): Any? {
|
||||
return CustomExtensionRootObject()
|
||||
}
|
||||
|
||||
override fun getProperties(): Map<String, Any> {
|
||||
val properties: MutableMap<String, Any> = HashMap()
|
||||
|
||||
properties["key"] = "Hello"
|
||||
|
||||
return properties
|
||||
}
|
||||
|
||||
override fun getFunctions(): Map<String, Function> {
|
||||
val functions: MutableMap<String, Function> = HashMap()
|
||||
|
||||
try {
|
||||
functions["aliasedMethod"] = Function(javaClass.getMethod("extensionMethod"))
|
||||
return functions
|
||||
} catch (o_O: Exception) {
|
||||
throw RuntimeException(o_O)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun extensionMethod(): String {
|
||||
return "Hello World"
|
||||
}
|
||||
|
||||
fun add(i1: Int, i2: Int): Int {
|
||||
return i1 + i2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CustomExtensionRootObject {
|
||||
fun rootObjectInstanceMethod(): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Once the above shown extension is registered, you can use its exported methods, properties and root object to evaluate SpEL expressions:
|
||||
|
||||
.Expression Evaluation Examples
|
||||
====
|
||||
[source]
|
||||
----
|
||||
#{add(1, 2)} <1>
|
||||
#{extensionMethod()} <2>
|
||||
#{aliasedMethod()} <3>
|
||||
#{key} <4>
|
||||
#{rootObjectInstanceMethod()} <5>
|
||||
----
|
||||
|
||||
<1> Invoke the method `add` declared by `MyExtension` resulting in `3` as the method adds both numeric parameters and returns the sum.
|
||||
<2> Invoke the method `extensionMethod` declared by `MyExtension` resulting in `Hello World`.
|
||||
<3> Invoke the method `aliasedMethod`.
|
||||
The method is exposed as function and redirects into the method `extensionMethod` declared by `MyExtension` resulting in `Hello World`.
|
||||
<4> Evaluate the `key` property resulting in `Hello`.
|
||||
<5> Invoke the method `rootObjectInstanceMethod` on the root object instance `CustomExtensionRootObject`.
|
||||
====
|
||||
|
||||
You can find real-life context extensions at https://github.com/spring-projects/spring-security/blob/main/data/src/main/java/org/springframework/security/data/repository/query/SecurityEvaluationContextExtension.java[`SecurityEvaluationContextExtension`].
|
||||
|
||||
[[valueexpressions.property-placeholders]]
|
||||
== Property Placeholders
|
||||
|
||||
Property placeholders following the form `${…}` refer to properties provided typically by a `PropertySource` through `Environment`.
|
||||
Properties are useful to resolve against system properties, application configuration files, environment configuration or property sources contributed by secret management systems.
|
||||
You can find more details on the property placeholders in {spring-framework-docs}/core/beans/annotation-config/value-annotations.html#page-title[Spring Framework's documentation on `@Value` usage].
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 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.expression;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.spel.ExpressionDependencies;
|
||||
|
||||
/**
|
||||
* Composite {@link ValueExpression} consisting of multiple placeholder, SpEL, and literal expressions.
|
||||
*
|
||||
* @param raw
|
||||
* @param expressions
|
||||
* @author Mark Paluch
|
||||
* @since 3.3
|
||||
*/
|
||||
record CompositeValueExpression(String raw, List<ValueExpression> expressions) implements ValueExpression {
|
||||
|
||||
@Override
|
||||
public String getExpressionString() {
|
||||
return raw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionDependencies getExpressionDependencies() {
|
||||
|
||||
ExpressionDependencies dependencies = ExpressionDependencies.none();
|
||||
|
||||
for (ValueExpression expression : expressions) {
|
||||
ExpressionDependencies dependency = expression.getExpressionDependencies();
|
||||
if (!dependency.equals(ExpressionDependencies.none())) {
|
||||
dependencies = dependencies.mergeWith(dependency);
|
||||
}
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLiteral() {
|
||||
|
||||
for (ValueExpression expression : expressions) {
|
||||
if (!expression.isLiteral()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String evaluate(ValueEvaluationContext context) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (ValueExpression expression : expressions) {
|
||||
builder.append((String) expression.evaluate(context));
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 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.expression;
|
||||
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
|
||||
/**
|
||||
* Default {@link ValueEvaluationContext}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.3
|
||||
*/
|
||||
record DefaultValueEvaluationContext(Environment environment,
|
||||
EvaluationContext evaluationContext) implements ValueEvaluationContext {
|
||||
|
||||
@Override
|
||||
public Environment getEnvironment() {
|
||||
return environment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EvaluationContext getEvaluationContext() {
|
||||
return evaluationContext();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright 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.expression;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.spel.ExpressionDependencies;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ParseException;
|
||||
import org.springframework.expression.ParserContext;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.SystemPropertyUtils;
|
||||
|
||||
/**
|
||||
* Default {@link ValueExpressionParser} implementation. Instances are thread-safe.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.3
|
||||
*/
|
||||
class DefaultValueExpressionParser implements ValueExpressionParser {
|
||||
|
||||
public static final String PLACEHOLDER_PREFIX = SystemPropertyUtils.PLACEHOLDER_PREFIX;
|
||||
public static final String EXPRESSION_PREFIX = ParserContext.TEMPLATE_EXPRESSION.getExpressionPrefix();
|
||||
public static final char SUFFIX = '}';
|
||||
public static final int PLACEHOLDER_PREFIX_LENGTH = PLACEHOLDER_PREFIX.length();
|
||||
public static final char[] QUOTE_CHARS = { '\'', '"' };
|
||||
|
||||
private final ValueParserConfiguration configuration;
|
||||
|
||||
public DefaultValueExpressionParser(ValueParserConfiguration configuration) {
|
||||
|
||||
Assert.notNull(configuration, "ValueParserConfiguration must not be null");
|
||||
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueExpression parse(String expressionString) {
|
||||
|
||||
int placerholderIndex = expressionString.indexOf(PLACEHOLDER_PREFIX);
|
||||
int expressionIndex = expressionString.indexOf(EXPRESSION_PREFIX);
|
||||
|
||||
if (placerholderIndex == -1 && expressionIndex == -1) {
|
||||
return new LiteralValueExpression(expressionString);
|
||||
}
|
||||
|
||||
if (placerholderIndex != -1 && expressionIndex == -1
|
||||
&& findPlaceholderEndIndex(expressionString, placerholderIndex) != expressionString.length()) {
|
||||
return createPlaceholder(expressionString);
|
||||
}
|
||||
|
||||
if (placerholderIndex == -1
|
||||
&& findPlaceholderEndIndex(expressionString, expressionIndex) != expressionString.length()) {
|
||||
return createExpression(expressionString);
|
||||
}
|
||||
|
||||
return parseComposite(expressionString, placerholderIndex, expressionIndex);
|
||||
}
|
||||
|
||||
private CompositeValueExpression parseComposite(String expressionString, int placerholderIndex, int expressionIndex) {
|
||||
|
||||
List<ValueExpression> expressions = new ArrayList<>(PLACEHOLDER_PREFIX_LENGTH);
|
||||
int startIndex = getStartIndex(placerholderIndex, expressionIndex);
|
||||
|
||||
if (startIndex != 0) {
|
||||
expressions.add(new LiteralValueExpression(expressionString.substring(0, startIndex)));
|
||||
}
|
||||
|
||||
while (startIndex != -1) {
|
||||
|
||||
int endIndex = findPlaceholderEndIndex(expressionString, startIndex);
|
||||
|
||||
if (endIndex == -1) {
|
||||
throw new ParseException(expressionString, startIndex,
|
||||
"No ending suffix '}' for expression starting at character %d: %s".formatted(startIndex,
|
||||
expressionString.substring(startIndex)));
|
||||
}
|
||||
|
||||
int afterClosingParenthesisIndex = endIndex + 1;
|
||||
String part = expressionString.substring(startIndex, afterClosingParenthesisIndex);
|
||||
|
||||
if (part.startsWith(PLACEHOLDER_PREFIX)) {
|
||||
expressions.add(createPlaceholder(part));
|
||||
} else {
|
||||
expressions.add(createExpression(part));
|
||||
}
|
||||
|
||||
placerholderIndex = expressionString.indexOf(PLACEHOLDER_PREFIX, endIndex);
|
||||
expressionIndex = expressionString.indexOf(EXPRESSION_PREFIX, endIndex);
|
||||
|
||||
startIndex = getStartIndex(placerholderIndex, expressionIndex);
|
||||
|
||||
if (startIndex == -1) {
|
||||
// no next expression but we're capturing everything after the expression as literal.
|
||||
expressions.add(new LiteralValueExpression(expressionString.substring(afterClosingParenthesisIndex)));
|
||||
} else {
|
||||
// capture literal after the expression ends and before the next starts.
|
||||
expressions
|
||||
.add(new LiteralValueExpression(expressionString.substring(afterClosingParenthesisIndex, startIndex)));
|
||||
}
|
||||
}
|
||||
|
||||
return new CompositeValueExpression(expressionString, expressions);
|
||||
}
|
||||
|
||||
private static int getStartIndex(int placerholderIndex, int expressionIndex) {
|
||||
return placerholderIndex != -1 && expressionIndex != -1 ? Math.min(placerholderIndex, expressionIndex)
|
||||
: placerholderIndex != -1 ? placerholderIndex : expressionIndex;
|
||||
}
|
||||
|
||||
private PlaceholderExpression createPlaceholder(String part) {
|
||||
return new PlaceholderExpression(part);
|
||||
}
|
||||
|
||||
private ExpressionExpression createExpression(String expression) {
|
||||
|
||||
Expression expr = configuration.getExpressionParser().parseExpression(expression,
|
||||
ParserContext.TEMPLATE_EXPRESSION);
|
||||
ExpressionDependencies dependencies = ExpressionDependencies.discover(expr);
|
||||
return new ExpressionExpression(expr, dependencies);
|
||||
}
|
||||
|
||||
private static int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
|
||||
|
||||
int index = startIndex + PLACEHOLDER_PREFIX_LENGTH;
|
||||
char quotationChar = 0;
|
||||
char nestingLevel = 0;
|
||||
boolean skipEscape = false;
|
||||
|
||||
while (index < buf.length()) {
|
||||
|
||||
char c = buf.charAt(index);
|
||||
|
||||
if (!skipEscape && c == '\\') {
|
||||
skipEscape = true;
|
||||
} else if (skipEscape) {
|
||||
skipEscape = false;
|
||||
} else if (quotationChar == 0) {
|
||||
|
||||
for (char quoteChar : QUOTE_CHARS) {
|
||||
if (quoteChar == c) {
|
||||
quotationChar = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (quotationChar == c) {
|
||||
quotationChar = 0;
|
||||
}
|
||||
|
||||
if (!skipEscape && quotationChar == 0) {
|
||||
|
||||
if (nestingLevel != 0 && c == SUFFIX) {
|
||||
nestingLevel--;
|
||||
} else if (c == '{') {
|
||||
nestingLevel++;
|
||||
} else if (nestingLevel == 0 && c == SUFFIX) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 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.expression;
|
||||
|
||||
import org.springframework.data.spel.ExpressionDependencies;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
|
||||
/**
|
||||
* SpEL expression.
|
||||
*
|
||||
* @param expression
|
||||
* @author Mark Paluch
|
||||
* @since 3.3
|
||||
*/
|
||||
record ExpressionExpression(Expression expression, ExpressionDependencies dependencies) implements ValueExpression {
|
||||
|
||||
@Override
|
||||
public String getExpressionString() {
|
||||
return expression.getExpressionString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionDependencies getExpressionDependencies() {
|
||||
return dependencies();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLiteral() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object evaluate(ValueEvaluationContext context) {
|
||||
|
||||
EvaluationContext evaluationContext = context.getEvaluationContext();
|
||||
if (evaluationContext != null) {
|
||||
return expression.getValue(evaluationContext);
|
||||
}
|
||||
return expression.getValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 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.expression;
|
||||
|
||||
/**
|
||||
* Literal expression returning the underlying expression string upon evaluation.
|
||||
*
|
||||
* @param expression
|
||||
* @author Mark Paluch
|
||||
* @since 3.3
|
||||
*/
|
||||
record LiteralValueExpression(String expression) implements ValueExpression {
|
||||
|
||||
@Override
|
||||
public String getExpressionString() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLiteral() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String evaluate(ValueEvaluationContext context) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 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.expression;
|
||||
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
|
||||
/**
|
||||
* Property placeholder expression evaluated against a {@link Environment}.
|
||||
*
|
||||
* @param expression
|
||||
* @author Mark Paluch
|
||||
* @since 3.3
|
||||
*/
|
||||
record PlaceholderExpression(String expression) implements ValueExpression {
|
||||
|
||||
@Override
|
||||
public String getExpressionString() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLiteral() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object evaluate(ValueEvaluationContext context) {
|
||||
|
||||
Environment environment = context.getEnvironment();
|
||||
if (environment != null) {
|
||||
try {
|
||||
return environment.resolveRequiredPlaceholders(expression);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new EvaluationException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 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.expression;
|
||||
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Expressions are executed in an evaluation context. It is in this context that references are resolved when
|
||||
* encountered during expression evaluation.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 3.3
|
||||
*/
|
||||
public interface ValueEvaluationContext {
|
||||
|
||||
/**
|
||||
* Returns a new {@link ValueEvaluationContext}.
|
||||
*
|
||||
* @param environment
|
||||
* @param evaluationContext
|
||||
* @return a new {@link ValueEvaluationContext} for the given environment and evaluation context.
|
||||
*/
|
||||
static ValueEvaluationContext of(Environment environment, EvaluationContext evaluationContext) {
|
||||
return new DefaultValueEvaluationContext(environment, evaluationContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Environment} if provided.
|
||||
*
|
||||
* @return the {@link Environment} or {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
Environment getEnvironment();
|
||||
|
||||
/**
|
||||
* Returns the {@link EvaluationContext} if provided.
|
||||
*
|
||||
* @return the {@link EvaluationContext} or {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
EvaluationContext getEvaluationContext();
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 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.expression;
|
||||
|
||||
import org.springframework.data.spel.ExpressionDependencies;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
|
||||
/**
|
||||
* SPI to provide to access a centrally defined potentially shared {@link ValueEvaluationContext}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.3
|
||||
*/
|
||||
public interface ValueEvaluationContextProvider {
|
||||
|
||||
/**
|
||||
* Return a {@link EvaluationContext} built using the given parameter values.
|
||||
*
|
||||
* @param rootObject the root object to set in the {@link EvaluationContext}.
|
||||
* @return
|
||||
*/
|
||||
ValueEvaluationContext getEvaluationContext(Object rootObject);
|
||||
|
||||
/**
|
||||
* Return a tailored {@link EvaluationContext} built using the given parameter values and considering
|
||||
* {@link ExpressionDependencies expression dependencies}. The returned {@link EvaluationContext} may contain a
|
||||
* reduced visibility of methods and properties/fields according to the required {@link ExpressionDependencies
|
||||
* expression dependencies}.
|
||||
*
|
||||
* @param rootObject the root object to set in the {@link EvaluationContext}.
|
||||
* @param dependencies the requested expression dependencies to be available.
|
||||
* @return
|
||||
*/
|
||||
default ValueEvaluationContext getEvaluationContext(Object rootObject, ExpressionDependencies dependencies) {
|
||||
return getEvaluationContext(rootObject);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 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.expression;
|
||||
|
||||
import org.springframework.data.spel.ExpressionDependencies;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* An expression capable of evaluating itself against context objects. Encapsulates the details of a previously parsed
|
||||
* expression string. Provides a common abstraction for expression evaluation.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 3.3
|
||||
*/
|
||||
public interface ValueExpression {
|
||||
|
||||
/**
|
||||
* Returns the original string used to create this expression (unmodified).
|
||||
*
|
||||
* @return the original expression string.
|
||||
*/
|
||||
String getExpressionString();
|
||||
|
||||
/**
|
||||
* Returns the expression dependencies.
|
||||
*
|
||||
* @return the dependencies the underlying expression requires. Can be {@link ExpressionDependencies#none()}.
|
||||
*/
|
||||
default ExpressionDependencies getExpressionDependencies() {
|
||||
return ExpressionDependencies.none();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the expression is a literal expression (that doesn't actually require evaluation).
|
||||
*
|
||||
* @return {@code true} if the expression is a literal expression; {@code false} if the expression can yield a
|
||||
* different result upon {@link #evaluate(ValueEvaluationContext) evaluation}.
|
||||
*/
|
||||
boolean isLiteral();
|
||||
|
||||
/**
|
||||
* Evaluates this expression using the given evaluation context.
|
||||
*
|
||||
* @return the evaluation result.
|
||||
* @throws EvaluationException if there is a problem during evaluation
|
||||
*/
|
||||
@Nullable
|
||||
Object evaluate(ValueEvaluationContext context) throws EvaluationException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 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.expression;
|
||||
|
||||
import org.springframework.expression.ParseException;
|
||||
|
||||
/**
|
||||
* Parses expression strings into expressions that can be evaluated. Supports parsing expression, configuration
|
||||
* templates as well as literal strings.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 3.3
|
||||
*/
|
||||
public interface ValueExpressionParser {
|
||||
|
||||
/**
|
||||
* Creates a new parser to parse expression strings.
|
||||
*
|
||||
* @param configuration the parser context configuration.
|
||||
* @return the parser instance.
|
||||
*/
|
||||
static ValueExpressionParser create(ValueParserConfiguration configuration) {
|
||||
return new DefaultValueExpressionParser(configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the expression string and return an Expression object you can use for repeated evaluation.
|
||||
* <p>
|
||||
* Some examples:
|
||||
*
|
||||
* <pre class="code">
|
||||
* #{3 + 4}
|
||||
* #{name.firstName}
|
||||
* ${key.one}
|
||||
* #{name.lastName}-${key.one}
|
||||
* </pre>
|
||||
*
|
||||
* @param expressionString the raw expression string to parse.
|
||||
* @return an evaluator for the parsed expression.
|
||||
* @throws ParseException an exception occurred during parsing.
|
||||
*/
|
||||
ValueExpression parse(String expressionString) throws ParseException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 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.expression;
|
||||
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
|
||||
/**
|
||||
* Configuration for {@link ValueExpressionParser}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 3.3
|
||||
*/
|
||||
public interface ValueParserConfiguration {
|
||||
|
||||
/**
|
||||
* Parser for {@link org.springframework.expression.Expression SpEL expressions}.
|
||||
*
|
||||
* @return for {@link org.springframework.expression.Expression SpEL expressions}.
|
||||
*/
|
||||
ExpressionParser getExpressionParser();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Value Expression implementation.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
package org.springframework.data.expression;
|
||||
@@ -39,11 +39,11 @@ public class Parameter<T, P extends PersistentProperty<P>> {
|
||||
private final @Nullable String name;
|
||||
private final TypeInformation<T> type;
|
||||
private final MergedAnnotations annotations;
|
||||
private final String key;
|
||||
private final @Nullable String expression;
|
||||
private final @Nullable PersistentEntity<T, P> entity;
|
||||
|
||||
private final Lazy<Boolean> enclosingClassCache;
|
||||
private final Lazy<Boolean> hasSpelExpression;
|
||||
private final Lazy<Boolean> hasExpression;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Parameter} with the given name, {@link TypeInformation} as well as an array of
|
||||
@@ -64,7 +64,7 @@ public class Parameter<T, P extends PersistentProperty<P>> {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.annotations = MergedAnnotations.from(annotations);
|
||||
this.key = getValue(this.annotations);
|
||||
this.expression = getValue(this.annotations);
|
||||
this.entity = entity;
|
||||
|
||||
this.enclosingClassCache = Lazy.of(() -> {
|
||||
@@ -77,7 +77,7 @@ public class Parameter<T, P extends PersistentProperty<P>> {
|
||||
return ClassUtils.isInnerClass(owningType) && type.getType().equals(owningType.getEnclosingClass());
|
||||
});
|
||||
|
||||
this.hasSpelExpression = Lazy.of(() -> StringUtils.hasText(getSpelExpression()));
|
||||
this.hasExpression = Lazy.of(() -> StringUtils.hasText(getValueExpression()));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -128,21 +128,62 @@ public class Parameter<T, P extends PersistentProperty<P>> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key to be used when looking up a source data structure to populate the actual parameter value.
|
||||
* Returns the expression to be used when looking up a source data structure to populate the actual parameter value.
|
||||
*
|
||||
* @return
|
||||
* @return the expression to be used when looking up a source data structure.
|
||||
* @deprecated since 3.3, use {@link #getValueExpression()} instead.
|
||||
*/
|
||||
@Nullable
|
||||
public String getSpelExpression() {
|
||||
return key;
|
||||
return getValueExpression();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the expression to be used when looking up a source data structure to populate the actual parameter value.
|
||||
*
|
||||
* @return the expression to be used when looking up a source data structure.
|
||||
* @since 3.3
|
||||
*/
|
||||
@Nullable
|
||||
public String getValueExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the required expression to be used when looking up a source data structure to populate the actual parameter
|
||||
* value or throws {@link IllegalStateException} if there's no expression.
|
||||
*
|
||||
* @return the expression to be used when looking up a source data structure.
|
||||
* @since 3.3
|
||||
*/
|
||||
public String getRequiredValueExpression() {
|
||||
|
||||
if (!hasValueExpression()) {
|
||||
throw new IllegalStateException("No expression associated with this parameter");
|
||||
}
|
||||
|
||||
return getValueExpression();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the constructor parameter is equipped with a SpEL expression.
|
||||
*
|
||||
* @return
|
||||
* @return {@literal true}} if the parameter is equipped with a SpEL expression.
|
||||
* @deprecated since 3.3, use {@link #hasValueExpression()} instead.
|
||||
*/
|
||||
@Deprecated(since = "3.3")
|
||||
public boolean hasSpelExpression() {
|
||||
return this.hasSpelExpression.get();
|
||||
return hasValueExpression();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the constructor parameter is equipped with a value expression.
|
||||
*
|
||||
* @return {@literal true}} if the parameter is equipped with a value expression.
|
||||
* @since 3.3
|
||||
*/
|
||||
public boolean hasValueExpression() {
|
||||
return this.hasExpression.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -157,12 +198,12 @@ public class Parameter<T, P extends PersistentProperty<P>> {
|
||||
}
|
||||
|
||||
return Objects.equals(this.name, that.name) && Objects.equals(this.type, that.type)
|
||||
&& Objects.equals(this.key, that.key) && Objects.equals(this.entity, that.entity);
|
||||
&& Objects.equals(this.expression, that.expression) && Objects.equals(this.entity, that.entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, type, key, entity);
|
||||
return Objects.hash(name, type, expression, entity);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,7 +35,10 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
@@ -44,7 +47,6 @@ import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.core.NativeDetector;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.data.domain.ManagedTypes;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
@@ -52,7 +54,6 @@ import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PersistentPropertyPaths;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.model.AbstractPersistentProperty;
|
||||
import org.springframework.data.mapping.model.BeanWrapperPropertyAccessorFactory;
|
||||
import org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory;
|
||||
import org.springframework.data.mapping.model.EntityInstantiators;
|
||||
@@ -63,7 +64,6 @@ import org.springframework.data.mapping.model.Property;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider;
|
||||
import org.springframework.data.support.EnvironmentAccessor;
|
||||
import org.springframework.data.util.KotlinReflectionUtils;
|
||||
import org.springframework.data.util.NullableWrapperConverters;
|
||||
import org.springframework.data.util.Optionals;
|
||||
@@ -94,7 +94,8 @@ import org.springframework.util.ReflectionUtils.FieldFilter;
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?, P>, P extends PersistentProperty<P>>
|
||||
implements MappingContext<E, P>, ApplicationEventPublisherAware, ApplicationContextAware, InitializingBean, EnvironmentAware {
|
||||
implements MappingContext<E, P>, ApplicationEventPublisherAware, ApplicationContextAware, BeanFactoryAware,
|
||||
EnvironmentAware, InitializingBean {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(MappingContext.class);
|
||||
|
||||
@@ -105,7 +106,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
|
||||
|
||||
private @Nullable ApplicationEventPublisher applicationEventPublisher;
|
||||
private EvaluationContextProvider evaluationContextProvider = EvaluationContextProvider.DEFAULT;
|
||||
private @Nullable EnvironmentAccessor environmentAccessor;
|
||||
private @Nullable Environment environment;
|
||||
|
||||
private ManagedTypes managedTypes = ManagedTypes.empty();
|
||||
|
||||
@@ -134,18 +135,47 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the application context. Sets also {@link ApplicationEventPublisher} and {@link Environment} if these weren't
|
||||
* already set.
|
||||
*
|
||||
* @param applicationContext the ApplicationContext object to be used by this object.
|
||||
* @throws BeansException
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
|
||||
this.evaluationContextProvider = new ExtensionAwareEvaluationContextProvider(applicationContext);
|
||||
setBeanFactory(applicationContext);
|
||||
|
||||
if (applicationEventPublisher == null) {
|
||||
if (this.applicationEventPublisher == null) {
|
||||
this.applicationEventPublisher = applicationContext;
|
||||
}
|
||||
|
||||
if (this.environment == null) {
|
||||
this.environment = applicationContext.getEnvironment();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param beanFactory owning BeanFactory.
|
||||
* @throws BeansException
|
||||
* @since 3.3
|
||||
*/
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
|
||||
if (beanFactory instanceof ListableBeanFactory lbf) {
|
||||
this.evaluationContextProvider = new ExtensionAwareEvaluationContextProvider(lbf);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param environment the {@code Environment} that this component runs in.
|
||||
* @since 3.3
|
||||
*/
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environmentAccessor = new DelegatingEnvironmentAccessor(environment);
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,7 +183,6 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
|
||||
*
|
||||
* @param initialEntitySet
|
||||
* @see #setManagedTypes(ManagedTypes)
|
||||
*
|
||||
*/
|
||||
public void setInitialEntitySet(Set<? extends Class<?>> initialEntitySet) {
|
||||
setManagedTypes(ManagedTypes.fromIterable(initialEntitySet));
|
||||
@@ -210,6 +239,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public E getPersistentEntity(Class<?> type) {
|
||||
return getPersistentEntity(TypeInformation.of(type));
|
||||
@@ -416,7 +446,9 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
|
||||
|
||||
E entity = createPersistentEntity(userTypeInformation);
|
||||
entity.setEvaluationContextProvider(evaluationContextProvider);
|
||||
entity.setEnvironmentAccessor(environmentAccessor);
|
||||
if (environment != null) {
|
||||
entity.setEnvironment(environment);
|
||||
}
|
||||
|
||||
// Eagerly cache the entity as we might have to find it during recursive lookups.
|
||||
persistentEntities.put(userTypeInformation, Optional.of(entity));
|
||||
@@ -488,10 +520,6 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
|
||||
if(this.environmentAccessor == null) {
|
||||
this.environmentAccessor = new DelegatingEnvironmentAccessor(new StandardEnvironment());
|
||||
}
|
||||
initialize();
|
||||
}
|
||||
|
||||
@@ -594,9 +622,6 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
|
||||
return;
|
||||
}
|
||||
|
||||
if(property instanceof AbstractPersistentProperty<?> pp) {
|
||||
pp.setEnvironmentAccessor(environmentAccessor);
|
||||
}
|
||||
entity.addPersistentProperty(property);
|
||||
|
||||
if (property.isAssociation()) {
|
||||
@@ -795,31 +820,4 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
*/
|
||||
public static class DelegatingEnvironmentAccessor implements EnvironmentAccessor {
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
static EnvironmentAccessor standard() {
|
||||
return new DelegatingEnvironmentAccessor(new StandardEnvironment());
|
||||
}
|
||||
|
||||
public DelegatingEnvironmentAccessor(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getProperty(String key) {
|
||||
return environment.getProperty(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolvePlaceholders(String text) {
|
||||
return environment.resolvePlaceholders(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import java.util.stream.Collectors;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.support.EnvironmentAccessor;
|
||||
import org.springframework.data.util.KotlinReflectionUtils;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.util.ReflectionUtils;
|
||||
@@ -75,9 +74,6 @@ public abstract class AbstractPersistentProperty<P extends PersistentProperty<P>
|
||||
private final Lazy<Boolean> readable;
|
||||
private final boolean immutable;
|
||||
|
||||
|
||||
private @Nullable EnvironmentAccessor environmentAccessor;
|
||||
|
||||
public AbstractPersistentProperty(Property property, PersistentEntity<?, P> owner,
|
||||
SimpleTypeHolder simpleTypeHolder) {
|
||||
|
||||
@@ -310,14 +306,6 @@ public abstract class AbstractPersistentProperty<P extends PersistentProperty<P>
|
||||
return targetType == null ? information.getRequiredActualType() : targetType;
|
||||
}
|
||||
|
||||
protected EnvironmentAccessor getEnvironmentAccessor() {
|
||||
return environmentAccessor;
|
||||
}
|
||||
|
||||
public void setEnvironmentAccessor(EnvironmentAccessor environmentAccessor) {
|
||||
this.environmentAccessor = environmentAccessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
|
||||
|
||||
@@ -37,8 +37,6 @@ import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.support.EnvironmentAccessor;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.util.Optionals;
|
||||
import org.springframework.data.util.ReflectionUtils;
|
||||
@@ -198,10 +196,12 @@ public abstract class AnnotationBasedPersistentProperty<P extends PersistentProp
|
||||
return isTransient.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdProperty() {
|
||||
return isId.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVersionProperty() {
|
||||
return isVersion.get();
|
||||
}
|
||||
@@ -227,6 +227,7 @@ public abstract class AnnotationBasedPersistentProperty<P extends PersistentProp
|
||||
* @param annotationType must not be {@literal null}.
|
||||
* @return {@literal null} if annotation type not found on property.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public <A extends Annotation> A findAnnotation(Class<A> annotationType) {
|
||||
|
||||
@@ -268,6 +269,7 @@ public abstract class AnnotationBasedPersistentProperty<P extends PersistentProp
|
||||
* @param annotationType the annotation type to look up.
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
|
||||
return doFindAnnotation(annotationType).isPresent();
|
||||
}
|
||||
|
||||
@@ -30,13 +30,14 @@ import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.annotation.Immutable;
|
||||
import org.springframework.data.annotation.TypeAlias;
|
||||
import org.springframework.data.domain.Persistable;
|
||||
import org.springframework.data.expression.ValueEvaluationContext;
|
||||
import org.springframework.data.mapping.*;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.data.spel.ExpressionDependencies;
|
||||
import org.springframework.data.support.EnvironmentAccessor;
|
||||
import org.springframework.data.support.IsNewStrategy;
|
||||
import org.springframework.data.support.PersistableIsNewStrategy;
|
||||
import org.springframework.data.util.Lazy;
|
||||
@@ -80,7 +81,7 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
|
||||
private @Nullable P versionProperty;
|
||||
private PersistentPropertyAccessorFactory propertyAccessorFactory;
|
||||
private EvaluationContextProvider evaluationContextProvider = EvaluationContextProvider.DEFAULT;
|
||||
private @Nullable EnvironmentAccessor environmentAccessor;
|
||||
private @Nullable Environment environment = null;
|
||||
|
||||
private final Lazy<Alias> typeAlias;
|
||||
private final Lazy<IsNewStrategy> isNewStrategy;
|
||||
@@ -148,36 +149,44 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
|
||||
return creator != null && creator.isCreatorParameter(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdProperty(PersistentProperty<?> property) {
|
||||
return idProperty != null && idProperty.equals(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVersionProperty(PersistentProperty<?> property) {
|
||||
return versionProperty != null && versionProperty.equals(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return getType().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public P getIdProperty() {
|
||||
return idProperty;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public P getVersionProperty() {
|
||||
return versionProperty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasIdProperty() {
|
||||
return idProperty != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasVersionProperty() {
|
||||
return versionProperty != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPersistentProperty(P property) {
|
||||
|
||||
Assert.notNull(property, "Property must not be null");
|
||||
@@ -220,9 +229,13 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
|
||||
this.evaluationContextProvider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param environment the {@code Environment} that this component runs in.
|
||||
* @since 3.3
|
||||
*/
|
||||
@Override
|
||||
public void setEnvironmentAccessor(EnvironmentAccessor accessor) {
|
||||
this.environmentAccessor = accessor;
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,6 +261,7 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
|
||||
return property;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAssociation(Association<P> association) {
|
||||
|
||||
Assert.notNull(association, "Association must not be null");
|
||||
@@ -283,18 +297,22 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
|
||||
.filter(it -> it.isAnnotationPresent(annotationType)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getType() {
|
||||
return information.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Alias getTypeAlias() {
|
||||
return typeAlias.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeInformation<T> getTypeInformation() {
|
||||
return information;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doWithProperties(PropertyHandler<P> handler) {
|
||||
|
||||
Assert.notNull(handler, "PropertyHandler must not be null");
|
||||
@@ -314,6 +332,7 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doWithAssociations(AssociationHandler<P> handler) {
|
||||
|
||||
Assert.notNull(handler, "Handler must not be null");
|
||||
@@ -323,6 +342,7 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doWithAssociations(SimpleAssociationHandler handler) {
|
||||
|
||||
Assert.notNull(handler, "Handler must not be null");
|
||||
@@ -350,6 +370,7 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
|
||||
it -> Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(getType(), it)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify() {
|
||||
|
||||
if (comparator != null) {
|
||||
@@ -449,14 +470,26 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the {@link EnvironmentAccessor} providing access to the current
|
||||
* {@link org.springframework.core.env.Environment}.
|
||||
* Obtain a {@link ValueEvaluationContext} for a {@code rootObject}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
* @param rootObject must not be {@literal null}.
|
||||
* @return the evaluation context including all potential extensions.
|
||||
* @since 3.3
|
||||
*/
|
||||
protected EnvironmentAccessor getEnvironmentAccessor() {
|
||||
return environmentAccessor;
|
||||
protected ValueEvaluationContext getValueEvaluationContext(Object rootObject) {
|
||||
return ValueEvaluationContext.of(this.environment, getEvaluationContext(rootObject));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@link ValueEvaluationContext} for a {@code rootObject} given {@link ExpressionDependencies}.
|
||||
*
|
||||
* @param rootObject must not be {@literal null}.
|
||||
* @param dependencies must not be {@literal null}.
|
||||
* @return the evaluation context with extensions loaded that satisfy {@link ExpressionDependencies}.
|
||||
* @since 3.3
|
||||
*/
|
||||
protected ValueEvaluationContext getValueEvaluationContext(Object rootObject, ExpressionDependencies dependencies) {
|
||||
return ValueEvaluationContext.of(this.environment, getEvaluationContext(rootObject, dependencies));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -534,6 +567,7 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(@Nullable Association<P> left, @Nullable Association<P> right) {
|
||||
|
||||
if (left == null) {
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 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.mapping.model;
|
||||
|
||||
import org.springframework.core.env.EnvironmentCapable;
|
||||
import org.springframework.data.expression.ValueEvaluationContext;
|
||||
import org.springframework.data.expression.ValueEvaluationContextProvider;
|
||||
import org.springframework.data.expression.ValueExpression;
|
||||
import org.springframework.data.expression.ValueExpressionParser;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.data.spel.ExpressionDependencies;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentLruCache;
|
||||
|
||||
/**
|
||||
* Factory to create a ValueExpressionEvaluator
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.3
|
||||
*/
|
||||
public class CachingValueExpressionEvaluatorFactory implements ValueEvaluationContextProvider {
|
||||
|
||||
private final ConcurrentLruCache<String, ValueExpression> expressionCache;
|
||||
private final EnvironmentCapable environmentProvider;
|
||||
private final EvaluationContextProvider evaluationContextProvider;
|
||||
|
||||
public CachingValueExpressionEvaluatorFactory(ExpressionParser expressionParser,
|
||||
EnvironmentCapable environmentProvider, EvaluationContextProvider evaluationContextProvider) {
|
||||
this(expressionParser, environmentProvider, evaluationContextProvider, 256);
|
||||
}
|
||||
|
||||
public CachingValueExpressionEvaluatorFactory(ExpressionParser expressionParser,
|
||||
EnvironmentCapable environmentProvider, EvaluationContextProvider evaluationContextProvider, int cacheSize) {
|
||||
|
||||
Assert.notNull(expressionParser, "ExpressionParser must not be null");
|
||||
|
||||
ValueExpressionParser parser = ValueExpressionParser.create(() -> expressionParser);
|
||||
this.expressionCache = new ConcurrentLruCache<>(cacheSize, parser::parse);
|
||||
this.environmentProvider = environmentProvider;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueEvaluationContext getEvaluationContext(Object rootObject) {
|
||||
return ValueEvaluationContext.of(environmentProvider.getEnvironment(),
|
||||
evaluationContextProvider.getEvaluationContext(rootObject));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueEvaluationContext getEvaluationContext(Object rootObject, ExpressionDependencies dependencies) {
|
||||
return ValueEvaluationContext.of(environmentProvider.getEnvironment(),
|
||||
evaluationContextProvider.getEvaluationContext(rootObject, dependencies));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ValueExpressionEvaluator} using the given {@code source} as root object.
|
||||
*
|
||||
* @param source the root object for evaluating the expression.
|
||||
* @return a new {@link ValueExpressionEvaluator} to evaluate the expression in the context of the given
|
||||
* {@code source} object.
|
||||
*/
|
||||
public ValueExpressionEvaluator create(Object source) {
|
||||
|
||||
return new ValueExpressionEvaluator() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T evaluate(String expression) {
|
||||
ValueExpression valueExpression = expressionCache.get(expression);
|
||||
return (T) valueExpression.evaluate(getEvaluationContext(source, valueExpression.getExpressionDependencies()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,7 +28,9 @@ import org.springframework.util.Assert;
|
||||
* {@link SpelExpressionParser} and {@link EvaluationContext}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @deprecated since 3.3, use {@link CachingValueExpressionEvaluatorFactory} instead.
|
||||
*/
|
||||
@Deprecated(since = "3.3")
|
||||
public class DefaultSpELExpressionEvaluator implements SpELExpressionEvaluator {
|
||||
|
||||
private final Object source;
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
*/
|
||||
package org.springframework.data.mapping.model;
|
||||
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.data.support.EnvironmentAccessor;
|
||||
|
||||
/**
|
||||
* Interface capturing mutator methods for {@link PersistentEntity}s.
|
||||
@@ -29,7 +29,8 @@ import org.springframework.data.support.EnvironmentAccessor;
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface MutablePersistentEntity<T, P extends PersistentProperty<P>> extends PersistentEntity<T, P> {
|
||||
public interface MutablePersistentEntity<T, P extends PersistentProperty<P>>
|
||||
extends PersistentEntity<T, P>, EnvironmentAware {
|
||||
|
||||
/**
|
||||
* Adds a {@link PersistentProperty} to the entity.
|
||||
@@ -68,11 +69,4 @@ public interface MutablePersistentEntity<T, P extends PersistentProperty<P>> ext
|
||||
*/
|
||||
void setEvaluationContextProvider(EvaluationContextProvider provider);
|
||||
|
||||
/**
|
||||
* Sets the {@link EnvironmentAccessor} to be used by the entity.
|
||||
*
|
||||
* @param accessor must not be {@literal null}.
|
||||
* @since 3.3
|
||||
*/
|
||||
void setEnvironmentAccessor(EnvironmentAccessor accessor);
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ public class PersistentEntityParameterValueProvider<P extends PersistentProperty
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getParameterValue(Parameter<T, P> parameter) {
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.mapping.model;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
@@ -30,7 +31,7 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class SpELContext {
|
||||
public class SpELContext implements EvaluationContextProvider {
|
||||
|
||||
private final SpelExpressionParser parser;
|
||||
private final PropertyAccessor accessor;
|
||||
@@ -90,6 +91,7 @@ public class SpELContext {
|
||||
return this.parser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EvaluationContext getEvaluationContext(Object source) {
|
||||
|
||||
StandardEvaluationContext evaluationContext = new StandardEvaluationContext(source);
|
||||
|
||||
@@ -15,21 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.mapping.model;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* SPI for components that can evaluate Spring EL expressions.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface SpELExpressionEvaluator {
|
||||
@Deprecated(since = "3.3")
|
||||
public interface SpELExpressionEvaluator extends ValueExpressionEvaluator {
|
||||
|
||||
/**
|
||||
* Evaluates the given expression.
|
||||
*
|
||||
* @param expression
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
<T> T evaluate(String expression);
|
||||
}
|
||||
|
||||
@@ -16,9 +16,7 @@
|
||||
package org.springframework.data.mapping.model;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.mapping.Parameter;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link ParameterValueProvider} that can be used to front a {@link ParameterValueProvider} delegate to prefer a SpEL
|
||||
@@ -26,43 +24,16 @@ import org.springframework.lang.Nullable;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @deprecated since 3.3, use {@link ValueExpressionParameterValueProvider} instead.
|
||||
*/
|
||||
@Deprecated(since = "3.3")
|
||||
public class SpELExpressionParameterValueProvider<P extends PersistentProperty<P>>
|
||||
implements ParameterValueProvider<P> {
|
||||
|
||||
private final SpELExpressionEvaluator evaluator;
|
||||
private final ConversionService conversionService;
|
||||
private final ParameterValueProvider<P> delegate;
|
||||
extends ValueExpressionParameterValueProvider<P> implements ParameterValueProvider<P> {
|
||||
|
||||
public SpELExpressionParameterValueProvider(SpELExpressionEvaluator evaluator, ConversionService conversionService,
|
||||
ParameterValueProvider<P> delegate) {
|
||||
|
||||
this.evaluator = evaluator;
|
||||
this.conversionService = conversionService;
|
||||
this.delegate = delegate;
|
||||
super(evaluator, conversionService, delegate);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T> T getParameterValue(Parameter<T, P> parameter) {
|
||||
|
||||
if (!parameter.hasSpelExpression()) {
|
||||
return delegate == null ? null : delegate.getParameterValue(parameter);
|
||||
}
|
||||
|
||||
Object object = evaluator.evaluate(parameter.getSpelExpression());
|
||||
return object == null ? null : potentiallyConvertSpelValue(object, parameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to allow to massage the value resulting from the Spel expression evaluation. Default implementation will
|
||||
* leverage the configured {@link ConversionService} to massage the value into the parameter type.
|
||||
*
|
||||
* @param object the value to massage, will never be {@literal null}.
|
||||
* @param parameter the {@link Parameter} we create the value for
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
protected <T> T potentiallyConvertSpelValue(Object object, Parameter<T, P> parameter) {
|
||||
return conversionService.convert(object, parameter.getRawType());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
@@ -13,18 +13,24 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.support;
|
||||
package org.springframework.data.mapping.model;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 3.3
|
||||
* SPI for components that can evaluate Value expressions.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.2
|
||||
*/
|
||||
public interface EnvironmentAccessor extends PlaceholderResolver {
|
||||
public interface ValueExpressionEvaluator {
|
||||
|
||||
/**
|
||||
* Evaluates the given expression.
|
||||
*
|
||||
* @param expression
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
String getProperty(String key);
|
||||
|
||||
<T> T evaluate(String expression);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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.mapping.model;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.mapping.Parameter;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ParameterValueProvider} that can be used to front a {@link ParameterValueProvider} delegate to prefer a SpEL
|
||||
* expression evaluation over directly resolving the parameter value with the delegate.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @since 3.3
|
||||
*/
|
||||
public class ValueExpressionParameterValueProvider<P extends PersistentProperty<P>>
|
||||
implements ParameterValueProvider<P> {
|
||||
|
||||
private final ValueExpressionEvaluator evaluator;
|
||||
private final ConversionService conversionService;
|
||||
private final ParameterValueProvider<P> delegate;
|
||||
|
||||
public ValueExpressionParameterValueProvider(ValueExpressionEvaluator evaluator, ConversionService conversionService,
|
||||
ParameterValueProvider<P> delegate) {
|
||||
|
||||
Assert.notNull(evaluator, "ValueExpressionEvaluator must not be null");
|
||||
Assert.notNull(conversionService, "ConversionService must not be null");
|
||||
Assert.notNull(delegate, "Delegate must not be null");
|
||||
|
||||
this.evaluator = evaluator;
|
||||
this.conversionService = conversionService;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T getParameterValue(Parameter<T, P> parameter) {
|
||||
|
||||
if (!parameter.hasValueExpression()) {
|
||||
return delegate.getParameterValue(parameter);
|
||||
}
|
||||
|
||||
// retain compatibility where we accepted bare expressions in @Value
|
||||
String rawExpressionString = parameter.getRequiredValueExpression();
|
||||
String expressionString = rawExpressionString.contains("#{") || rawExpressionString.contains("${")
|
||||
? rawExpressionString
|
||||
: "#{" + rawExpressionString + "}";
|
||||
|
||||
Object object = evaluator.evaluate(expressionString);
|
||||
return object == null ? null : potentiallyConvertExpressionValue(object, parameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to allow to massage the value resulting from the Spel expression evaluation. Default implementation will
|
||||
* leverage the configured {@link ConversionService} to massage the value into the parameter type.
|
||||
*
|
||||
* @param object the value to massage, will never be {@literal null}.
|
||||
* @param parameter the {@link Parameter} we create the value for
|
||||
* @return the converted parameter value.
|
||||
* @deprecated since 3.3, use {@link #potentiallyConvertExpressionValue(Object, Parameter)} instead.
|
||||
*/
|
||||
@Nullable
|
||||
@Deprecated(since = "3.3")
|
||||
protected <T> T potentiallyConvertSpelValue(Object object, Parameter<T, P> parameter) {
|
||||
return conversionService.convert(object, parameter.getRawType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to allow to massage the value resulting from the Spel expression evaluation. Default implementation will
|
||||
* leverage the configured {@link ConversionService} to massage the value into the parameter type.
|
||||
*
|
||||
* @param object the value to massage, will never be {@literal null}.
|
||||
* @param parameter the {@link Parameter} we create the value for
|
||||
* @return the converted parameter value.
|
||||
* @since 3.3
|
||||
*/
|
||||
@Nullable
|
||||
protected <T> T potentiallyConvertExpressionValue(Object object, Parameter<T, P> parameter) {
|
||||
return potentiallyConvertSpelValue(object, parameter);
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.util.AnnotationDetectionMethodCallback;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -46,10 +47,29 @@ import org.springframework.util.ReflectionUtils;
|
||||
public class SpelAwareProxyProjectionFactory extends ProxyProjectionFactory implements BeanFactoryAware {
|
||||
|
||||
private final Map<Class<?>, Boolean> typeCache = new ConcurrentHashMap<>();
|
||||
private final SpelExpressionParser parser = new SpelExpressionParser();
|
||||
private final ExpressionParser parser;
|
||||
|
||||
private @Nullable BeanFactory beanFactory;
|
||||
|
||||
/**
|
||||
* Create a new {@link SpelAwareProxyProjectionFactory}.
|
||||
*/
|
||||
public SpelAwareProxyProjectionFactory() {
|
||||
this(new SpelExpressionParser());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SpelAwareProxyProjectionFactory} for a given {@link ExpressionParser}.
|
||||
*
|
||||
* @param parser the parser to use.
|
||||
* @since 3.3
|
||||
*/
|
||||
public SpelAwareProxyProjectionFactory(ExpressionParser parser) {
|
||||
|
||||
Assert.notNull(parser, "ExpressionParser must not be null");
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
|
||||
@@ -30,9 +30,9 @@ import org.springframework.context.expression.MapAccessor;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.ParserContext;
|
||||
import org.springframework.expression.common.TemplateParserContext;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -71,7 +71,7 @@ class SpelEvaluatingMethodInterceptor implements MethodInterceptor {
|
||||
* @param targetInterface must not be {@literal null}.
|
||||
*/
|
||||
public SpelEvaluatingMethodInterceptor(MethodInterceptor delegate, Object target, @Nullable BeanFactory beanFactory,
|
||||
SpelExpressionParser parser, Class<?> targetInterface) {
|
||||
ExpressionParser parser, Class<?> targetInterface) {
|
||||
|
||||
Assert.notNull(delegate, "Delegate MethodInterceptor must not be null");
|
||||
Assert.notNull(target, "Target object must not be null");
|
||||
@@ -105,7 +105,7 @@ class SpelEvaluatingMethodInterceptor implements MethodInterceptor {
|
||||
* @return
|
||||
*/
|
||||
private static Map<Integer, Expression> potentiallyCreateExpressionsForMethodsOnTargetInterface(
|
||||
SpelExpressionParser parser, Class<?> targetInterface) {
|
||||
ExpressionParser parser, Class<?> targetInterface) {
|
||||
|
||||
Map<Integer, Expression> expressions = new HashMap<>();
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* SpEL support.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
package org.springframework.data.spel;
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023. 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
|
||||
*
|
||||
* http://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.util;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.data.support.EnvironmentAccessor;
|
||||
import org.springframework.expression.BeanResolver;
|
||||
import org.springframework.expression.ConstructorResolver;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.MethodResolver;
|
||||
import org.springframework.expression.OperatorOverloader;
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.expression.TypeComparator;
|
||||
import org.springframework.expression.TypeConverter;
|
||||
import org.springframework.expression.TypeLocator;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2023/11
|
||||
*/
|
||||
public abstract class ExpressionEvaluator {
|
||||
|
||||
private EnvironmentAccessor environmentAccessor;
|
||||
private EvaluationContextProvider evaluationContextProvider;
|
||||
|
||||
public ExpressionEvaluator(EnvironmentAccessor environmentAccessor, EvaluationContextProvider evaluationContextProvider) {
|
||||
this.environmentAccessor = environmentAccessor;
|
||||
this.evaluationContextProvider = evaluationContextProvider;
|
||||
}
|
||||
|
||||
abstract <T> T evaluate(String text, EnvironmentAwareEvaluationContext context);
|
||||
|
||||
public class EnvironmentAwareEvaluationContext implements EvaluationContext, EnvironmentAccessor, EvaluationContextProvider {
|
||||
|
||||
@Override
|
||||
public EvaluationContext getEvaluationContext(Object rootObject) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getProperty(String key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolvePlaceholders(String text) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedValue getRootObject() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PropertyAccessor> getPropertyAccessors() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConstructorResolver> getConstructorResolvers() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MethodResolver> getMethodResolvers() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BeanResolver getBeanResolver() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeLocator getTypeLocator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeConverter getTypeConverter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeComparator getTypeComparator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OperatorOverloader getOperatorOverloader() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVariable(String name, @Nullable Object value) {
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object lookupVariable(String name) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 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.expression;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.ParseException;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ValueExpression} and its parsing.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class ValueEvaluationUnitTests {
|
||||
|
||||
private ValueEvaluationContext evaluationContext;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
||||
MapPropertySource propertySource = new MapPropertySource("map", Map.of("env.key.one", "value", "env.key.two", 42L));
|
||||
StandardEnvironment environment = new StandardEnvironment();
|
||||
environment.getPropertySources().addFirst(propertySource);
|
||||
|
||||
this.evaluationContext = new ValueEvaluationContext() {
|
||||
@Override
|
||||
public Environment getEnvironment() {
|
||||
return environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EvaluationContext getEvaluationContext() {
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
context.setVariable("contextVar", "contextVal");
|
||||
|
||||
return context;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test // GH-2369
|
||||
void shouldParseAndEvaluateExpressions() {
|
||||
|
||||
assertThat(eval("foo")).isEqualTo("foo");
|
||||
assertThat(eval("${env.key.one}")).isEqualTo("value");
|
||||
assertThat(eval("${env.key.two}")).isEqualTo("42");
|
||||
assertThat(eval("${env.key.one")).isEqualTo("${env.key.one");
|
||||
assertThat(eval("#{'foo'}-")).isEqualTo("foo-");
|
||||
assertThat(eval("#{'fo\"o'}-")).isEqualTo("fo\"o-");
|
||||
assertThat(eval("#{\"foo\"}-")).isEqualTo("foo-");
|
||||
assertThat(eval("#{\"fo'o\"}-")).isEqualTo("fo'o-");
|
||||
assertThat(eval("${env.key.one}-")).isEqualTo("value-");
|
||||
assertThat(eval("#{#contextVar}")).isEqualTo("contextVal");
|
||||
assertThat(eval("${env.does.not.exist:some-default}-")).isEqualTo("some-default-");
|
||||
|
||||
assertThatExceptionOfType(EvaluationException.class).isThrownBy(() -> eval("${env.does.not.exist}-"))
|
||||
.withMessageContaining("Could not resolve placeholder 'env.does.not.exist'");
|
||||
}
|
||||
|
||||
@Test // GH-2369
|
||||
void shouldParseLiteral() {
|
||||
|
||||
ValueParserConfiguration parserContext = () -> new SpelExpressionParser();
|
||||
ValueExpressionParser parser = ValueExpressionParser.create(parserContext);
|
||||
|
||||
assertThat(parser.parse("#{'foo'}-${key.one}").isLiteral()).isFalse();
|
||||
assertThat(parser.parse("foo").isLiteral()).isTrue();
|
||||
assertThat(parser.parse("#{'foo'}").isLiteral()).isFalse();
|
||||
assertThat(parser.parse("${key.one}").isLiteral()).isFalse();
|
||||
}
|
||||
|
||||
@Test // GH-2369
|
||||
void shouldParseCompoundExpression() {
|
||||
|
||||
assertThat(eval("#{'foo'}-")).isEqualTo("foo-");
|
||||
assertThat(eval("#{'fo\"o'}-")).isEqualTo("fo\"o-");
|
||||
assertThat(eval("#{\"foo\"}-")).isEqualTo("foo-");
|
||||
assertThat(eval("#{\"fo'o\"}-")).isEqualTo("fo'o-");
|
||||
assertThat(eval("${env.key.one}-")).isEqualTo("value-");
|
||||
assertThat(eval("${env.key.one}-and-some-#{'foo'}-#{#contextVar}")).isEqualTo("value-and-some-foo-contextVal");
|
||||
}
|
||||
|
||||
@Test // GH-2369
|
||||
void shouldRaiseParseException() {
|
||||
|
||||
assertThatExceptionOfType(ParseException.class).isThrownBy(() -> eval("#{'foo'}-${key.one")).withMessageContaining(
|
||||
"Expression [#{'foo'}-${key.one] @9: No ending suffix '}' for expression starting at character 9: ${key.one");
|
||||
|
||||
assertThatExceptionOfType(ParseException.class).isThrownBy(() -> eval("#{'foo'}-${env.key.one"));
|
||||
assertThatExceptionOfType(ParseException.class).isThrownBy(() -> eval("#{'foo'"));
|
||||
|
||||
assertThatExceptionOfType(ParseException.class).isThrownBy(() -> eval("#{#foo - ${numeric.key}}"))
|
||||
.withMessageContaining("[#foo - ${numeric.key}]");
|
||||
|
||||
}
|
||||
|
||||
@Test // GH-2369
|
||||
void shouldParseQuoted() {
|
||||
|
||||
assertThat(eval("#{(1+1) + '-foo'+\"-bar\"}")).isEqualTo("2-foo-bar");
|
||||
assertThat(eval("#{(1+1) + '-foo'+\"-bar}\"}")).isEqualTo("2-foo-bar}");
|
||||
assertThat(eval("#{(1+1) + '-foo}'}")).isEqualTo("2-foo}");
|
||||
assertThat(eval("#{(1+1) + '-foo\\}'}")).isEqualTo("2-foo\\}");
|
||||
assertThat(eval("#{(1+1) + \"-foo'}\" + '-bar}'}")).isEqualTo("2-foo'}-bar}");
|
||||
}
|
||||
|
||||
private String eval(String expressionString) {
|
||||
|
||||
ValueParserConfiguration parserContext = SpelExpressionParser::new;
|
||||
|
||||
ValueExpressionParser parser = ValueExpressionParser.create(parserContext);
|
||||
return (String) parser.parse(expressionString).evaluate(evaluationContext);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -147,7 +147,7 @@ class PreferredConstructorDiscovererUnitTests<P extends PersistentProperty<P>> {
|
||||
var constructor = PreferredConstructorDiscoverer.discover(ClassWithMetaAnnotatedParameter.class);
|
||||
|
||||
assertThat(constructor).isNotNull();
|
||||
assertThat(constructor.getParameters().get(0).getSpelExpression()).isEqualTo("${hello-world}");
|
||||
assertThat(constructor.getParameters().get(0).getValueExpression()).isEqualTo("${hello-world}");
|
||||
assertThat(constructor.getParameters().get(0).getAnnotations()).isNotNull();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 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.mapping.model;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.data.spel.ExpressionDependencies;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link CachingValueExpressionEvaluatorFactory}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class CachingValueExpressionEvaluatorFactoryUnitTests {
|
||||
|
||||
@Test // GH-2369
|
||||
void shouldEvaluateWithDependencies() {
|
||||
|
||||
EvaluationContextProvider contextProviderMock = mock(EvaluationContextProvider.class);
|
||||
CachingValueExpressionEvaluatorFactory factory = new CachingValueExpressionEvaluatorFactory(
|
||||
new SpelExpressionParser(), StandardEnvironment::new, contextProviderMock);
|
||||
ValueExpressionEvaluator evaluator = factory.create(new MyRoot("foo"));
|
||||
|
||||
when(contextProviderMock.getEvaluationContext(any(), any(ExpressionDependencies.class)))
|
||||
.then(invocation -> new StandardEvaluationContext(invocation.getArgument(0)));
|
||||
|
||||
Object result = evaluator.evaluate("#{root}");
|
||||
|
||||
assertThat(result).isEqualTo("foo");
|
||||
|
||||
ArgumentCaptor<ExpressionDependencies> captor = ArgumentCaptor.forClass(ExpressionDependencies.class);
|
||||
|
||||
verify(contextProviderMock).getEvaluationContext(any(), captor.capture());
|
||||
|
||||
ExpressionDependencies value = captor.getValue();
|
||||
assertThat(value).hasSize(1).isNotEqualTo(ExpressionDependencies.none());
|
||||
}
|
||||
|
||||
record MyRoot(String root) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -53,7 +53,7 @@ class SpelExpressionParameterProviderUnitTests {
|
||||
provider = new SpELExpressionParameterValueProvider<>(evaluator, conversionService, delegate);
|
||||
|
||||
parameter = mock(Parameter.class);
|
||||
when(parameter.hasSpelExpression()).thenReturn(true);
|
||||
when(parameter.hasValueExpression()).thenReturn(true);
|
||||
when(parameter.getRawType()).thenReturn(Object.class);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ class SpelExpressionParameterProviderUnitTests {
|
||||
void delegatesIfParameterDoesNotHaveASpELExpression() {
|
||||
|
||||
Parameter<Object, SamplePersistentProperty> parameter = mock(Parameter.class);
|
||||
when(parameter.hasSpelExpression()).thenReturn(false);
|
||||
when(parameter.hasValueExpression()).thenReturn(false);
|
||||
|
||||
provider.getParameterValue(parameter);
|
||||
verify(delegate, times(1)).getParameterValue(parameter);
|
||||
@@ -72,17 +72,17 @@ class SpelExpressionParameterProviderUnitTests {
|
||||
@Test
|
||||
void evaluatesSpELExpression() {
|
||||
|
||||
when(parameter.getSpelExpression()).thenReturn("expression");
|
||||
when(parameter.getRequiredValueExpression()).thenReturn("expression");
|
||||
|
||||
provider.getParameterValue(parameter);
|
||||
verify(delegate, times(0)).getParameterValue(parameter);
|
||||
verify(evaluator, times(1)).evaluate("expression");
|
||||
verify(evaluator, times(1)).evaluate("#{expression}");
|
||||
}
|
||||
|
||||
@Test
|
||||
void handsSpELValueToConversionService() {
|
||||
|
||||
doReturn("source").when(parameter).getSpelExpression();
|
||||
doReturn("source").when(parameter).getRequiredValueExpression();
|
||||
doReturn("value").when(evaluator).evaluate(any());
|
||||
|
||||
provider.getParameterValue(parameter);
|
||||
@@ -94,7 +94,7 @@ class SpelExpressionParameterProviderUnitTests {
|
||||
@Test
|
||||
void doesNotConvertNullValue() {
|
||||
|
||||
doReturn("source").when(parameter).getSpelExpression();
|
||||
doReturn("source").when(parameter).getRequiredValueExpression();
|
||||
doReturn(null).when(evaluator).evaluate(any());
|
||||
|
||||
provider.getParameterValue(parameter);
|
||||
@@ -116,7 +116,7 @@ class SpelExpressionParameterProviderUnitTests {
|
||||
}
|
||||
};
|
||||
|
||||
doReturn("source").when(parameter).getSpelExpression();
|
||||
doReturn("source").when(parameter).getRequiredValueExpression();
|
||||
doReturn("value").when(evaluator).evaluate(any());
|
||||
|
||||
assertThat(provider.getParameterValue(parameter)).isEqualTo("FOO");
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 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.mapping.model;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.expression.MapAccessor;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.data.mapping.Parameter;
|
||||
import org.springframework.data.mapping.context.SamplePersistentProperty;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ValueExpressionParameterValueProvider}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class ValueExpressionParameterValueProviderUnitTests {
|
||||
|
||||
CachingValueExpressionEvaluatorFactory factory = new CachingValueExpressionEvaluatorFactory(
|
||||
new SpelExpressionParser(), StandardEnvironment::new, rootObject -> {
|
||||
|
||||
SpELContext spELContext = new SpELContext(new MapAccessor());
|
||||
return spELContext.getEvaluationContext(rootObject);
|
||||
});
|
||||
|
||||
ValueExpressionParameterValueProvider<SamplePersistentProperty> provider = new ValueExpressionParameterValueProvider<>(
|
||||
factory.create(Map.of("name", "Walter")), DefaultConversionService.getSharedInstance(),
|
||||
new ParameterValueProvider<SamplePersistentProperty>() {
|
||||
@Override
|
||||
public <T> T getParameterValue(Parameter<T, SamplePersistentProperty> parameter) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
@Test // GH-2369
|
||||
void considersValueCompatibilityFormat() {
|
||||
|
||||
Annotation[] annotations = CompatibilityConstructor.class.getConstructors()[0].getParameterAnnotations()[0];
|
||||
String name = provider
|
||||
.getParameterValue(new Parameter<>("name", TypeInformation.of(String.class), annotations, null));
|
||||
|
||||
assertThat(name).isEqualTo("Walter");
|
||||
}
|
||||
|
||||
@Test // GH-2369
|
||||
void considersValueTemplateFormat() {
|
||||
|
||||
Annotation[] annotations = TemplateConstructor.class.getConstructors()[0].getParameterAnnotations()[0];
|
||||
String name = provider
|
||||
.getParameterValue(new Parameter<>("name", TypeInformation.of(String.class), annotations, null));
|
||||
|
||||
assertThat(name).isEqualTo("Walter");
|
||||
}
|
||||
|
||||
static class CompatibilityConstructor {
|
||||
|
||||
public CompatibilityConstructor(@Value("#root.name") String name) {}
|
||||
|
||||
}
|
||||
|
||||
static class TemplateConstructor {
|
||||
|
||||
public TemplateConstructor(@Value("#{#root.name}") String name) {}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023. 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
|
||||
*
|
||||
* http://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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2023. 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
|
||||
*
|
||||
* http://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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2023 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
|
||||
*
|
||||
* http://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.mapping.model.justme;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.core.env.PropertyResolver;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ParserContext;
|
||||
import org.springframework.expression.spel.SpelCompilerMode;
|
||||
import org.springframework.expression.spel.SpelParserConfiguration;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2023/11
|
||||
*/
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
public class PropertySourceUnitTests {
|
||||
|
||||
@Value("${my-type.name}") String entityName;
|
||||
|
||||
@Autowired ListableBeanFactory beanFactory;
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
@Configuration
|
||||
@PropertySource("classpath:persistent-entity.properties")
|
||||
static class Config {
|
||||
|
||||
}
|
||||
|
||||
// @Test
|
||||
void plainContext() {
|
||||
|
||||
System.getProperties().forEach((key,value) -> System.out.printf("%s:%s\n", key, value));
|
||||
Expression expression = parse("#{systemProperties['os.arch']}");
|
||||
|
||||
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
|
||||
Object value = expression.getValue();
|
||||
System.out.println("va: " + value);
|
||||
}
|
||||
|
||||
Expression parse(String source) {
|
||||
return parser.parseExpression(source, getParserContext());
|
||||
}
|
||||
|
||||
// @Test
|
||||
void loadsContext() {
|
||||
|
||||
Map<String, PropertyResolver> beansOfType = beanFactory.getBeansOfType(PropertyResolver.class, false, false);
|
||||
|
||||
ExtensionAwareEvaluationContextProvider contextProvider = new ExtensionAwareEvaluationContextProvider(beanFactory);
|
||||
|
||||
|
||||
// Expression spelExpression = parser.parseExpression("#{ T(java.lang.Math).random() * 100.0 }",
|
||||
// getParserContext());
|
||||
// Expression expression = parser.parseExpression("#{ T(java.lang.Math).random() * 100.0 }");
|
||||
// Expression expression = parser.parseExpression("#{ systemProperties['user.region'] }", getParserContext());
|
||||
|
||||
StandardEvaluationContext evaluationContext = contextProvider.getEvaluationContext(new StandardEnvironment());
|
||||
SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(SpelCompilerMode.OFF, null));
|
||||
Expression expression = parser.parseExpression("#{systemProperties['os.arch']}", getParserContext());
|
||||
Object value = expression.getValue(evaluationContext);
|
||||
System.out.println("value: " + value);
|
||||
// Expression expression = parser.parseExpression("#{ ${x.y.z} ?: 'defaultValue' }");
|
||||
// Expression expression = parser.parseExpression("#{'${my-type.name}'}");
|
||||
|
||||
// System.out.println("expression: " + expression);
|
||||
// Object value = expression.getValue(evaluationContext);
|
||||
// System.out.println("value: " + value);
|
||||
}
|
||||
|
||||
private static ParserContext getParserContext() {
|
||||
return new ParserContext() {
|
||||
@Override
|
||||
public boolean isTemplate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExpressionPrefix() {
|
||||
return "#{";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExpressionSuffix() {
|
||||
return "}";
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023. 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
|
||||
*
|
||||
* http://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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2023 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
|
||||
*
|
||||
* http://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.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.data.mapping.context.AbstractMappingContext.DelegatingEnvironmentAccessor;
|
||||
import org.springframework.data.support.EnvironmentAccessor;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ParserContext;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.expression.spel.SpelCompilerMode;
|
||||
import org.springframework.expression.spel.SpelParserConfiguration;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.SimpleEvaluationContext;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2023/11
|
||||
*/
|
||||
public class ExpressionEvaluatorUnitTests {
|
||||
|
||||
public static final SpelExpressionParser SPEL_EXPRESSION_PARSER = new SpelExpressionParser(new SpelParserConfiguration(SpelCompilerMode.OFF, null));
|
||||
|
||||
@Test
|
||||
void intialTests() {
|
||||
|
||||
ExpressionEvaluator evaluator = new ExpressionEvaluator(new DelegatingEnvironmentAccessor(new StandardEnvironment()));
|
||||
|
||||
{
|
||||
Object result =
|
||||
|
||||
evaluator.prepare("foo-${os.arch}")
|
||||
.parseWith(SpelExpressionParser::new)
|
||||
.withContext(StandardEvaluationContext::new)
|
||||
.evaluate();
|
||||
System.out.println("result: " + result);
|
||||
}
|
||||
|
||||
{
|
||||
Object result = evaluator.prepare("#{systemProperties['os.arch']}")
|
||||
.withContext(() -> new StandardEvaluationContext(new StandardEnvironment()))
|
||||
.evaluate();
|
||||
System.out.println("result: " + result);
|
||||
}
|
||||
|
||||
// @Value("#{1+1}-${os.arch}") -> 2-aarch64
|
||||
|
||||
// StandardBeanExpressionResolver
|
||||
{
|
||||
Expression expression = SPEL_EXPRESSION_PARSER.parseExpression("#{systemProperties['os.arch']}-foo-${os.arch}", ParserContext.TEMPLATE_EXPRESSION);
|
||||
Object value = expression.getValue(new StandardEvaluationContext(new StandardEnvironment()));
|
||||
if(value instanceof String s) {
|
||||
//
|
||||
}
|
||||
|
||||
System.out.println("value: " + value);
|
||||
}
|
||||
|
||||
{
|
||||
Object result = evaluator.prepare("${os.arch}").evaluate();
|
||||
System.out.println("result: " + result);
|
||||
}
|
||||
|
||||
{
|
||||
Object result = evaluator.prepare("foo-${os.arch}").evaluate();
|
||||
System.out.println("result: " + result);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
// check against - boot
|
||||
// plcaechoder replacement after spel
|
||||
Object result = evaluator.prepare("'#{systemProperties['os.arch']}-foo-${os.arch}'")
|
||||
.withContext(() -> new StandardEvaluationContext(new StandardEnvironment()))
|
||||
.evaluate();
|
||||
System.out.println("result: " + result);
|
||||
}
|
||||
|
||||
// evaluator.prepare(source)evaluate(() -> context);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
static class ExpressionEvaluator {
|
||||
|
||||
private final EnvironmentAccessor environmentAccessor;
|
||||
|
||||
public ExpressionEvaluator(EnvironmentAccessor environmentAccessor) {
|
||||
this.environmentAccessor = environmentAccessor;
|
||||
}
|
||||
|
||||
PreparedExpression prepare(String source) {
|
||||
return this.prepare(() -> source);
|
||||
}
|
||||
|
||||
PreparedExpression prepare(Supplier<String> source) {
|
||||
|
||||
return new PreparedExpression(new Supplier<>() {
|
||||
|
||||
@Nullable private String cachedValue;
|
||||
|
||||
@Override
|
||||
public String get() {
|
||||
|
||||
if (cachedValue != null) {
|
||||
return cachedValue;
|
||||
}
|
||||
|
||||
String expressionSource = source.get();
|
||||
String expression = environmentAccessor.resolvePlaceholders(expressionSource);
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(expressionSource, expression)) {
|
||||
cachedValue = expression;
|
||||
}
|
||||
System.out.println("using: '" + expression + "' for " + expressionSource);
|
||||
return expression;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static class PreparedExpression {
|
||||
|
||||
Supplier<SpelExpressionParser> expressionParser;
|
||||
Supplier<String> source;
|
||||
Map<String, Optional<Expression>> expressionCache = new HashMap<>(1, 1F);
|
||||
|
||||
public PreparedExpression(Supplier<String> source) {
|
||||
this.source = source;
|
||||
expressionParser = Lazy.of(SpelExpressionParser::new);
|
||||
}
|
||||
|
||||
PreparedExpression parseWith(SpelExpressionParser expressionParser) {
|
||||
return parseWith(() -> expressionParser);
|
||||
}
|
||||
|
||||
PreparedExpression parseWith(Supplier<SpelExpressionParser> expressionParser) {
|
||||
this.expressionParser = expressionParser;
|
||||
return this;
|
||||
}
|
||||
|
||||
<T> T evaluate() {
|
||||
return withContext(SimpleEvaluationContext.forReadOnlyDataBinding().build()).evaluate();
|
||||
}
|
||||
|
||||
ExpressionEvaluation applyReadonlyBinding() {
|
||||
return withContext(SimpleEvaluationContext.forReadOnlyDataBinding().build());
|
||||
}
|
||||
|
||||
ExpressionEvaluation withContext(EvaluationContext evaluationContext) {
|
||||
return withContext(() -> evaluationContext);
|
||||
}
|
||||
|
||||
ExpressionEvaluation withContext(Supplier<EvaluationContext> evaluationContext) {
|
||||
return new ExpressionEvaluation(this, evaluationContext);
|
||||
}
|
||||
<T> T doEvaluate(Supplier<EvaluationContext> evaluationContext) {
|
||||
|
||||
String expressionString = source.get();
|
||||
Optional<Expression> expression = expressionCache.computeIfAbsent(expressionString,
|
||||
(String key) -> Optional.ofNullable(detectExpression(expressionParser.get(), key)));
|
||||
return (T) expression.map(it -> it.getValue(evaluationContext.get())).orElse(expressionString);
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private static Expression detectExpression(SpelExpressionParser parser, @Nullable String potentialExpression) {
|
||||
|
||||
if (!StringUtils.hasText(potentialExpression)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Expression expression = parser.parseExpression(potentialExpression, ParserContext.TEMPLATE_EXPRESSION);
|
||||
return expression instanceof LiteralExpression ? null : expression;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ExpressionEvaluation {
|
||||
|
||||
final PreparedExpression source;
|
||||
final Supplier<EvaluationContext> evaluationContext;
|
||||
boolean cacheResult;
|
||||
Optional<?> cached;
|
||||
|
||||
public ExpressionEvaluation(PreparedExpression source, Supplier<EvaluationContext> evaluationContext) {
|
||||
this.source = source;
|
||||
this.evaluationContext = evaluationContext;
|
||||
}
|
||||
|
||||
<T> T evaluate() {
|
||||
|
||||
if(cacheResult && cached != null) {
|
||||
return (T) cached.orElse(null);
|
||||
}
|
||||
|
||||
T result = source.doEvaluate(evaluationContext);
|
||||
if(cacheResult) {
|
||||
cached = Optional.ofNullable(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ExpressionEvaluation cache() {
|
||||
cacheResult = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
<T> T reevaluate() {
|
||||
|
||||
if(cacheResult) {
|
||||
cached = null;
|
||||
}
|
||||
return evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 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.util;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.data.mapping.context.AbstractMappingContext.DelegatingEnvironmentAccessor;
|
||||
import org.springframework.data.support.EnvironmentAccessor;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2023/11
|
||||
*/
|
||||
public class ExpressionResolverUnitTests {
|
||||
|
||||
@Test
|
||||
void xxx() {
|
||||
|
||||
ParserContext parserContext = new DefaultParserContext(new DelegatingEnvironmentAccessor(new StandardEnvironment()), new SpelExpressionParser());
|
||||
|
||||
ValueParser parser = ValueParser.create(parserContext);
|
||||
|
||||
ParsableStatement statement = parser.prepareStatement("foo-${os.arch}");
|
||||
// PreparedStatement bindNow = parser.parse("foo-${os.arch}");
|
||||
|
||||
statement.evaluate(ValueContext.spelContext(new StandardEvaluationContext()));
|
||||
}
|
||||
|
||||
|
||||
interface ValueParser {
|
||||
ParsableStatement prepareStatement(String expression);
|
||||
static ValueParser create(ParserContext parserContext) {
|
||||
return new DefaultValueParser(parserContext);
|
||||
}
|
||||
}
|
||||
|
||||
static class DefaultValueParser implements ValueParser {
|
||||
|
||||
private final ParserContext parserContext;
|
||||
|
||||
public DefaultValueParser(ParserContext parserContext) {
|
||||
this.parserContext = parserContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParsableStatement prepareStatement(String expression) {
|
||||
return new RawStatement(expression, parserContext);
|
||||
}
|
||||
}
|
||||
|
||||
interface ParsableStatement {
|
||||
|
||||
<T> T evaluate(ValueContext context);
|
||||
|
||||
default ParsableStatement transform(Function<ParsableStatement, ParsableStatement> mappingFunction) {
|
||||
return mappingFunction.apply(this);
|
||||
}
|
||||
|
||||
String toString();
|
||||
}
|
||||
|
||||
class LazyEvaluatingStatement {
|
||||
|
||||
}
|
||||
|
||||
abstract static class AbstractStatement implements ParsableStatement {
|
||||
|
||||
final ParserContext parserContext;
|
||||
|
||||
public AbstractStatement(ParserContext parserContext) {
|
||||
this.parserContext = parserContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T evaluate(ValueContext context) {
|
||||
|
||||
ParsableStatement executableStatement = transform(parserContext::resolvePlaceholders)
|
||||
.transform(parserContext::parseExpressions);
|
||||
|
||||
return executableStatement.evaluate(context);
|
||||
}
|
||||
|
||||
abstract String getValue();
|
||||
}
|
||||
|
||||
static class RawStatement extends AbstractStatement {
|
||||
|
||||
final String value;
|
||||
|
||||
public RawStatement(String value, ParserContext context) {
|
||||
super(context);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
static class PreparedStatement extends RawStatement {
|
||||
|
||||
public PreparedStatement(String value, ParserContext context) {
|
||||
super(value, context);
|
||||
}
|
||||
}
|
||||
|
||||
static class ExpressionStatement implements ParsableStatement {
|
||||
|
||||
Expression expression;
|
||||
|
||||
public ExpressionStatement(Expression expression) {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T evaluate(ValueContext context) {
|
||||
|
||||
if(context instanceof SpELValueContext spelContext) {
|
||||
return (T) expression.getValue(spelContext.getEvaluationContext());
|
||||
}
|
||||
return (T) expression.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return expression.getExpressionString();
|
||||
}
|
||||
}
|
||||
|
||||
interface ParserContext {
|
||||
|
||||
ParsableStatement resolvePlaceholders(ParsableStatement statement);
|
||||
ParsableStatement parseExpressions(ParsableStatement statement);
|
||||
}
|
||||
|
||||
static class DefaultParserContext implements ParserContext {
|
||||
|
||||
private final EnvironmentAccessor environmentAccessor;
|
||||
private final SpelExpressionParser spelExpressionParser;
|
||||
|
||||
public DefaultParserContext(EnvironmentAccessor environmentAccessor, SpelExpressionParser spelExpressionParser) {
|
||||
|
||||
this.environmentAccessor = environmentAccessor;
|
||||
this.spelExpressionParser = spelExpressionParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParsableStatement resolvePlaceholders(ParsableStatement statement) {
|
||||
return statement.transform(it -> {
|
||||
if (it instanceof PreparedStatement) {
|
||||
return it;
|
||||
}
|
||||
return new PreparedStatement(environmentAccessor.resolvePlaceholders(it.toString()), this);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParsableStatement parseExpressions(ParsableStatement statement) {
|
||||
return statement.transform(it -> {
|
||||
Expression expression = detectExpression(spelExpressionParser, it.toString());
|
||||
if (expression == null || expression instanceof LiteralExpression) {
|
||||
return it;
|
||||
}
|
||||
return new ExpressionStatement(expression);
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Expression detectExpression(SpelExpressionParser parser, @Nullable String potentialExpression) {
|
||||
|
||||
if (!StringUtils.hasText(potentialExpression)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Expression expression = parser.parseExpression(potentialExpression, org.springframework.expression.ParserContext.TEMPLATE_EXPRESSION);
|
||||
return expression instanceof LiteralExpression ? null : expression;
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface ValueContext permits SpELValueContext {
|
||||
|
||||
static ValueContext spelContext(EvaluationContext evaluationContext) {
|
||||
return new SpELValueContext(evaluationContext);
|
||||
}
|
||||
}
|
||||
|
||||
static final class SpELValueContext implements ValueContext {
|
||||
EvaluationContext evaluationContext;
|
||||
|
||||
public SpELValueContext(EvaluationContext evaluationContext) {
|
||||
this.evaluationContext = evaluationContext;
|
||||
}
|
||||
|
||||
EvaluationContext getEvaluationContext() {
|
||||
return evaluationContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user