Add support for Value Expressions.

We now support Value Expressions such as `#{1+1}-${spring.application.name:fallback-value}` that are composed of SpEL expressions, literals and Property Placeholders.

See #2369
Original pull request: #3036
This commit is contained in:
Christoph Strobl
2023-11-23 15:27:25 +01:00
committed by Mark Paluch
parent 5ec3c609f6
commit 9f13e5450d
12 changed files with 904 additions and 5 deletions

View File

@@ -40,8 +40,11 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
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;
@@ -49,6 +52,7 @@ 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;
@@ -59,6 +63,7 @@ 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;
@@ -89,7 +94,7 @@ 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 {
implements MappingContext<E, P>, ApplicationEventPublisherAware, ApplicationContextAware, InitializingBean, EnvironmentAware {
private static final Log LOGGER = LogFactory.getLog(MappingContext.class);
@@ -100,6 +105,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
private @Nullable ApplicationEventPublisher applicationEventPublisher;
private EvaluationContextProvider evaluationContextProvider = EvaluationContextProvider.DEFAULT;
private @Nullable EnvironmentAccessor environmentAccessor;
private ManagedTypes managedTypes = ManagedTypes.empty();
@@ -138,6 +144,10 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
}
}
public void setEnvironment(Environment environment) {
this.environmentAccessor = new DelegatingEnvironmentAccessor(environment);
}
/**
* Sets the {@link Set} of types to populate the context initially.
*
@@ -406,6 +416,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
E entity = createPersistentEntity(userTypeInformation);
entity.setEvaluationContextProvider(evaluationContextProvider);
entity.setEnvironmentAccessor(environmentAccessor);
// Eagerly cache the entity as we might have to find it during recursive lookups.
persistentEntities.put(userTypeInformation, Optional.of(entity));
@@ -477,6 +488,10 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
@Override
public void afterPropertiesSet() {
if(this.environmentAccessor == null) {
this.environmentAccessor = new DelegatingEnvironmentAccessor(new StandardEnvironment());
}
initialize();
}
@@ -579,6 +594,9 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
return;
}
if(property instanceof AbstractPersistentProperty<?> pp) {
pp.setEnvironmentAccessor(environmentAccessor);
}
entity.addPersistentProperty(property);
if (property.isAssociation()) {
@@ -776,4 +794,32 @@ 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,6 +27,7 @@ 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;
@@ -74,6 +75,9 @@ 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) {
@@ -306,6 +310,14 @@ 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,6 +37,7 @@ 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;

View File

@@ -36,6 +36,7 @@ import org.springframework.data.domain.Persistable;
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;
@@ -79,6 +80,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 final Lazy<Alias> typeAlias;
private final Lazy<IsNewStrategy> isNewStrategy;
@@ -205,10 +207,8 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
if (versionProperty != null) {
throw new MappingException(
String.format(
"Attempt to add version property %s but already have property %s registered "
+ "as version; Check your mapping configuration",
property.getField(), versionProperty.getField()));
String.format("Attempt to add version property %s but already have property %s registered "
+ "as version; Check your mapping configuration", property.getField(), versionProperty.getField()));
}
this.versionProperty = property;
@@ -220,6 +220,11 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
this.evaluationContextProvider = provider;
}
@Override
public void setEnvironmentAccessor(EnvironmentAccessor accessor) {
this.environmentAccessor = accessor;
}
/**
* Returns the given property if it is a better candidate for the id property than the current id property.
*
@@ -443,6 +448,17 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
return evaluationContextProvider.getEvaluationContext(rootObject, dependencies);
}
/**
* Obtain the {@link EnvironmentAccessor} providing access to the current
* {@link org.springframework.core.env.Environment}.
*
* @return never {@literal null}.
* @since 3.3
*/
protected EnvironmentAccessor getEnvironmentAccessor() {
return environmentAccessor;
}
/**
* Returns the default {@link IsNewStrategy} to be used. Will be a {@link PersistentEntityIsNewStrategy} by default.
* Note, that this strategy only gets used if the entity doesn't implement {@link Persistable} as this indicates the

View File

@@ -21,6 +21,7 @@ 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.
@@ -66,4 +67,12 @@ public interface MutablePersistentEntity<T, P extends PersistentProperty<P>> ext
* @param provider must not be {@literal null}.
*/
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

@@ -0,0 +1,30 @@
/*
* 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.support;
import org.springframework.lang.Nullable;
/**
* @author Christoph Strobl
* @since 3.3
*/
public interface EnvironmentAccessor extends PlaceholderResolver {
@Nullable
String getProperty(String key);
}

View File

@@ -0,0 +1,25 @@
/*
* 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.support;
/**
* @author Christoph Strobl
* @since 3.3
*/
public interface PlaceholderResolver {
String resolvePlaceholders(String text);
}

View File

@@ -0,0 +1,128 @@
/*
* 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;
}
}
}