Evolve ValueExpressionParser.

Introduce literal, expression and placeholder variants. Add parser for composite expressions.

Closes #2369
Original pull request: #3036
This commit is contained in:
Mark Paluch
2024-01-29 15:45:07 +01:00
parent 9f13e5450d
commit 67163d02e3
40 changed files with 1650 additions and 911 deletions

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -0,0 +1,5 @@
/**
* Value Expression implementation.
*/
@org.springframework.lang.NonNullApi
package org.springframework.data.expression;

View File

@@ -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);
}
/**

View File

@@ -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);
}
}
}

View File

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

View File

@@ -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();
}

View File

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

View File

@@ -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()));
}
};
}
}

View File

@@ -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;

View File

@@ -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);
}

View File

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

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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());
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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<>();

View File

@@ -0,0 +1,5 @@
/**
* SpEL support.
*/
@org.springframework.lang.NonNullApi
package org.springframework.data.spel;

View File

@@ -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;
}
}
}