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:
committed by
Mark Paluch
parent
5ec3c609f6
commit
9f13e5450d
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user