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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2023. the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2023. the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mapping.model.justme;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.core.env.PropertyResolver;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ParserContext;
|
||||
import org.springframework.expression.spel.SpelCompilerMode;
|
||||
import org.springframework.expression.spel.SpelParserConfiguration;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2023/11
|
||||
*/
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
public class PropertySourceUnitTests {
|
||||
|
||||
@Value("${my-type.name}") String entityName;
|
||||
|
||||
@Autowired ListableBeanFactory beanFactory;
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
@Configuration
|
||||
@PropertySource("classpath:persistent-entity.properties")
|
||||
static class Config {
|
||||
|
||||
}
|
||||
|
||||
// @Test
|
||||
void plainContext() {
|
||||
|
||||
System.getProperties().forEach((key,value) -> System.out.printf("%s:%s\n", key, value));
|
||||
Expression expression = parse("#{systemProperties['os.arch']}");
|
||||
|
||||
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
|
||||
Object value = expression.getValue();
|
||||
System.out.println("va: " + value);
|
||||
}
|
||||
|
||||
Expression parse(String source) {
|
||||
return parser.parseExpression(source, getParserContext());
|
||||
}
|
||||
|
||||
// @Test
|
||||
void loadsContext() {
|
||||
|
||||
Map<String, PropertyResolver> beansOfType = beanFactory.getBeansOfType(PropertyResolver.class, false, false);
|
||||
|
||||
ExtensionAwareEvaluationContextProvider contextProvider = new ExtensionAwareEvaluationContextProvider(beanFactory);
|
||||
|
||||
|
||||
// Expression spelExpression = parser.parseExpression("#{ T(java.lang.Math).random() * 100.0 }",
|
||||
// getParserContext());
|
||||
// Expression expression = parser.parseExpression("#{ T(java.lang.Math).random() * 100.0 }");
|
||||
// Expression expression = parser.parseExpression("#{ systemProperties['user.region'] }", getParserContext());
|
||||
|
||||
StandardEvaluationContext evaluationContext = contextProvider.getEvaluationContext(new StandardEnvironment());
|
||||
SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(SpelCompilerMode.OFF, null));
|
||||
Expression expression = parser.parseExpression("#{systemProperties['os.arch']}", getParserContext());
|
||||
Object value = expression.getValue(evaluationContext);
|
||||
System.out.println("value: " + value);
|
||||
// Expression expression = parser.parseExpression("#{ ${x.y.z} ?: 'defaultValue' }");
|
||||
// Expression expression = parser.parseExpression("#{'${my-type.name}'}");
|
||||
|
||||
// System.out.println("expression: " + expression);
|
||||
// Object value = expression.getValue(evaluationContext);
|
||||
// System.out.println("value: " + value);
|
||||
}
|
||||
|
||||
private static ParserContext getParserContext() {
|
||||
return new ParserContext() {
|
||||
@Override
|
||||
public boolean isTemplate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExpressionPrefix() {
|
||||
return "#{";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExpressionSuffix() {
|
||||
return "}";
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright 2023. the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.data.mapping.context.AbstractMappingContext.DelegatingEnvironmentAccessor;
|
||||
import org.springframework.data.support.EnvironmentAccessor;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ParserContext;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.expression.spel.SpelCompilerMode;
|
||||
import org.springframework.expression.spel.SpelParserConfiguration;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.SimpleEvaluationContext;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2023/11
|
||||
*/
|
||||
public class ExpressionEvaluatorUnitTests {
|
||||
|
||||
public static final SpelExpressionParser SPEL_EXPRESSION_PARSER = new SpelExpressionParser(new SpelParserConfiguration(SpelCompilerMode.OFF, null));
|
||||
|
||||
@Test
|
||||
void intialTests() {
|
||||
|
||||
ExpressionEvaluator evaluator = new ExpressionEvaluator(new DelegatingEnvironmentAccessor(new StandardEnvironment()));
|
||||
|
||||
{
|
||||
Object result =
|
||||
|
||||
evaluator.prepare("foo-${os.arch}")
|
||||
.parseWith(SpelExpressionParser::new)
|
||||
.withContext(StandardEvaluationContext::new)
|
||||
.evaluate();
|
||||
System.out.println("result: " + result);
|
||||
}
|
||||
|
||||
{
|
||||
Object result = evaluator.prepare("#{systemProperties['os.arch']}")
|
||||
.withContext(() -> new StandardEvaluationContext(new StandardEnvironment()))
|
||||
.evaluate();
|
||||
System.out.println("result: " + result);
|
||||
}
|
||||
|
||||
// @Value("#{1+1}-${os.arch}") -> 2-aarch64
|
||||
|
||||
// StandardBeanExpressionResolver
|
||||
{
|
||||
Expression expression = SPEL_EXPRESSION_PARSER.parseExpression("#{systemProperties['os.arch']}-foo-${os.arch}", ParserContext.TEMPLATE_EXPRESSION);
|
||||
Object value = expression.getValue(new StandardEvaluationContext(new StandardEnvironment()));
|
||||
if(value instanceof String s) {
|
||||
//
|
||||
}
|
||||
|
||||
System.out.println("value: " + value);
|
||||
}
|
||||
|
||||
{
|
||||
Object result = evaluator.prepare("${os.arch}").evaluate();
|
||||
System.out.println("result: " + result);
|
||||
}
|
||||
|
||||
{
|
||||
Object result = evaluator.prepare("foo-${os.arch}").evaluate();
|
||||
System.out.println("result: " + result);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
// check against - boot
|
||||
// plcaechoder replacement after spel
|
||||
Object result = evaluator.prepare("'#{systemProperties['os.arch']}-foo-${os.arch}'")
|
||||
.withContext(() -> new StandardEvaluationContext(new StandardEnvironment()))
|
||||
.evaluate();
|
||||
System.out.println("result: " + result);
|
||||
}
|
||||
|
||||
// evaluator.prepare(source)evaluate(() -> context);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
static class ExpressionEvaluator {
|
||||
|
||||
private final EnvironmentAccessor environmentAccessor;
|
||||
|
||||
public ExpressionEvaluator(EnvironmentAccessor environmentAccessor) {
|
||||
this.environmentAccessor = environmentAccessor;
|
||||
}
|
||||
|
||||
PreparedExpression prepare(String source) {
|
||||
return this.prepare(() -> source);
|
||||
}
|
||||
|
||||
PreparedExpression prepare(Supplier<String> source) {
|
||||
|
||||
return new PreparedExpression(new Supplier<>() {
|
||||
|
||||
@Nullable private String cachedValue;
|
||||
|
||||
@Override
|
||||
public String get() {
|
||||
|
||||
if (cachedValue != null) {
|
||||
return cachedValue;
|
||||
}
|
||||
|
||||
String expressionSource = source.get();
|
||||
String expression = environmentAccessor.resolvePlaceholders(expressionSource);
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(expressionSource, expression)) {
|
||||
cachedValue = expression;
|
||||
}
|
||||
System.out.println("using: '" + expression + "' for " + expressionSource);
|
||||
return expression;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static class PreparedExpression {
|
||||
|
||||
Supplier<SpelExpressionParser> expressionParser;
|
||||
Supplier<String> source;
|
||||
Map<String, Optional<Expression>> expressionCache = new HashMap<>(1, 1F);
|
||||
|
||||
public PreparedExpression(Supplier<String> source) {
|
||||
this.source = source;
|
||||
expressionParser = Lazy.of(SpelExpressionParser::new);
|
||||
}
|
||||
|
||||
PreparedExpression parseWith(SpelExpressionParser expressionParser) {
|
||||
return parseWith(() -> expressionParser);
|
||||
}
|
||||
|
||||
PreparedExpression parseWith(Supplier<SpelExpressionParser> expressionParser) {
|
||||
this.expressionParser = expressionParser;
|
||||
return this;
|
||||
}
|
||||
|
||||
<T> T evaluate() {
|
||||
return withContext(SimpleEvaluationContext.forReadOnlyDataBinding().build()).evaluate();
|
||||
}
|
||||
|
||||
ExpressionEvaluation applyReadonlyBinding() {
|
||||
return withContext(SimpleEvaluationContext.forReadOnlyDataBinding().build());
|
||||
}
|
||||
|
||||
ExpressionEvaluation withContext(EvaluationContext evaluationContext) {
|
||||
return withContext(() -> evaluationContext);
|
||||
}
|
||||
|
||||
ExpressionEvaluation withContext(Supplier<EvaluationContext> evaluationContext) {
|
||||
return new ExpressionEvaluation(this, evaluationContext);
|
||||
}
|
||||
<T> T doEvaluate(Supplier<EvaluationContext> evaluationContext) {
|
||||
|
||||
String expressionString = source.get();
|
||||
Optional<Expression> expression = expressionCache.computeIfAbsent(expressionString,
|
||||
(String key) -> Optional.ofNullable(detectExpression(expressionParser.get(), key)));
|
||||
return (T) expression.map(it -> it.getValue(evaluationContext.get())).orElse(expressionString);
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private static Expression detectExpression(SpelExpressionParser parser, @Nullable String potentialExpression) {
|
||||
|
||||
if (!StringUtils.hasText(potentialExpression)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Expression expression = parser.parseExpression(potentialExpression, ParserContext.TEMPLATE_EXPRESSION);
|
||||
return expression instanceof LiteralExpression ? null : expression;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ExpressionEvaluation {
|
||||
|
||||
final PreparedExpression source;
|
||||
final Supplier<EvaluationContext> evaluationContext;
|
||||
boolean cacheResult;
|
||||
Optional<?> cached;
|
||||
|
||||
public ExpressionEvaluation(PreparedExpression source, Supplier<EvaluationContext> evaluationContext) {
|
||||
this.source = source;
|
||||
this.evaluationContext = evaluationContext;
|
||||
}
|
||||
|
||||
<T> T evaluate() {
|
||||
|
||||
if(cacheResult && cached != null) {
|
||||
return (T) cached.orElse(null);
|
||||
}
|
||||
|
||||
T result = source.doEvaluate(evaluationContext);
|
||||
if(cacheResult) {
|
||||
cached = Optional.ofNullable(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ExpressionEvaluation cache() {
|
||||
cacheResult = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
<T> T reevaluate() {
|
||||
|
||||
if(cacheResult) {
|
||||
cached = null;
|
||||
}
|
||||
return evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.util;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.data.mapping.context.AbstractMappingContext.DelegatingEnvironmentAccessor;
|
||||
import org.springframework.data.support.EnvironmentAccessor;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2023/11
|
||||
*/
|
||||
public class ExpressionResolverUnitTests {
|
||||
|
||||
@Test
|
||||
void xxx() {
|
||||
|
||||
ParserContext parserContext = new DefaultParserContext(new DelegatingEnvironmentAccessor(new StandardEnvironment()), new SpelExpressionParser());
|
||||
|
||||
ValueParser parser = ValueParser.create(parserContext);
|
||||
|
||||
ParsableStatement statement = parser.prepareStatement("foo-${os.arch}");
|
||||
// PreparedStatement bindNow = parser.parse("foo-${os.arch}");
|
||||
|
||||
statement.evaluate(ValueContext.spelContext(new StandardEvaluationContext()));
|
||||
}
|
||||
|
||||
|
||||
interface ValueParser {
|
||||
ParsableStatement prepareStatement(String expression);
|
||||
static ValueParser create(ParserContext parserContext) {
|
||||
return new DefaultValueParser(parserContext);
|
||||
}
|
||||
}
|
||||
|
||||
static class DefaultValueParser implements ValueParser {
|
||||
|
||||
private final ParserContext parserContext;
|
||||
|
||||
public DefaultValueParser(ParserContext parserContext) {
|
||||
this.parserContext = parserContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParsableStatement prepareStatement(String expression) {
|
||||
return new RawStatement(expression, parserContext);
|
||||
}
|
||||
}
|
||||
|
||||
interface ParsableStatement {
|
||||
|
||||
<T> T evaluate(ValueContext context);
|
||||
|
||||
default ParsableStatement transform(Function<ParsableStatement, ParsableStatement> mappingFunction) {
|
||||
return mappingFunction.apply(this);
|
||||
}
|
||||
|
||||
String toString();
|
||||
}
|
||||
|
||||
class LazyEvaluatingStatement {
|
||||
|
||||
}
|
||||
|
||||
abstract static class AbstractStatement implements ParsableStatement {
|
||||
|
||||
final ParserContext parserContext;
|
||||
|
||||
public AbstractStatement(ParserContext parserContext) {
|
||||
this.parserContext = parserContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T evaluate(ValueContext context) {
|
||||
|
||||
ParsableStatement executableStatement = transform(parserContext::resolvePlaceholders)
|
||||
.transform(parserContext::parseExpressions);
|
||||
|
||||
return executableStatement.evaluate(context);
|
||||
}
|
||||
|
||||
abstract String getValue();
|
||||
}
|
||||
|
||||
static class RawStatement extends AbstractStatement {
|
||||
|
||||
final String value;
|
||||
|
||||
public RawStatement(String value, ParserContext context) {
|
||||
super(context);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
static class PreparedStatement extends RawStatement {
|
||||
|
||||
public PreparedStatement(String value, ParserContext context) {
|
||||
super(value, context);
|
||||
}
|
||||
}
|
||||
|
||||
static class ExpressionStatement implements ParsableStatement {
|
||||
|
||||
Expression expression;
|
||||
|
||||
public ExpressionStatement(Expression expression) {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T evaluate(ValueContext context) {
|
||||
|
||||
if(context instanceof SpELValueContext spelContext) {
|
||||
return (T) expression.getValue(spelContext.getEvaluationContext());
|
||||
}
|
||||
return (T) expression.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return expression.getExpressionString();
|
||||
}
|
||||
}
|
||||
|
||||
interface ParserContext {
|
||||
|
||||
ParsableStatement resolvePlaceholders(ParsableStatement statement);
|
||||
ParsableStatement parseExpressions(ParsableStatement statement);
|
||||
}
|
||||
|
||||
static class DefaultParserContext implements ParserContext {
|
||||
|
||||
private final EnvironmentAccessor environmentAccessor;
|
||||
private final SpelExpressionParser spelExpressionParser;
|
||||
|
||||
public DefaultParserContext(EnvironmentAccessor environmentAccessor, SpelExpressionParser spelExpressionParser) {
|
||||
|
||||
this.environmentAccessor = environmentAccessor;
|
||||
this.spelExpressionParser = spelExpressionParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParsableStatement resolvePlaceholders(ParsableStatement statement) {
|
||||
return statement.transform(it -> {
|
||||
if (it instanceof PreparedStatement) {
|
||||
return it;
|
||||
}
|
||||
return new PreparedStatement(environmentAccessor.resolvePlaceholders(it.toString()), this);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParsableStatement parseExpressions(ParsableStatement statement) {
|
||||
return statement.transform(it -> {
|
||||
Expression expression = detectExpression(spelExpressionParser, it.toString());
|
||||
if (expression == null || expression instanceof LiteralExpression) {
|
||||
return it;
|
||||
}
|
||||
return new ExpressionStatement(expression);
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Expression detectExpression(SpelExpressionParser parser, @Nullable String potentialExpression) {
|
||||
|
||||
if (!StringUtils.hasText(potentialExpression)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Expression expression = parser.parseExpression(potentialExpression, org.springframework.expression.ParserContext.TEMPLATE_EXPRESSION);
|
||||
return expression instanceof LiteralExpression ? null : expression;
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface ValueContext permits SpELValueContext {
|
||||
|
||||
static ValueContext spelContext(EvaluationContext evaluationContext) {
|
||||
return new SpELValueContext(evaluationContext);
|
||||
}
|
||||
}
|
||||
|
||||
static final class SpELValueContext implements ValueContext {
|
||||
EvaluationContext evaluationContext;
|
||||
|
||||
public SpELValueContext(EvaluationContext evaluationContext) {
|
||||
this.evaluationContext = evaluationContext;
|
||||
}
|
||||
|
||||
EvaluationContext getEvaluationContext() {
|
||||
return evaluationContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/test/resources/persistent-entity.properties
Normal file
1
src/test/resources/persistent-entity.properties
Normal file
@@ -0,0 +1 @@
|
||||
my-type.name=foo
|
||||
Reference in New Issue
Block a user