diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java index 5d124e7ff..a6d2d50ba 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java @@ -32,9 +32,13 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.context.EnvironmentAware; import org.springframework.core.CollectionFactory; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.core.env.Environment; +import org.springframework.core.env.EnvironmentCapable; +import org.springframework.core.env.StandardEnvironment; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.cassandra.core.mapping.*; import org.springframework.data.cassandra.core.mapping.Embedded.OnEmpty; @@ -46,19 +50,20 @@ import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.model.CachingValueExpressionEvaluatorFactory; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; -import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator; import org.springframework.data.mapping.model.EntityInstantiator; import org.springframework.data.mapping.model.ParameterValueProvider; import org.springframework.data.mapping.model.PropertyValueProvider; import org.springframework.data.mapping.model.SpELContext; -import org.springframework.data.mapping.model.SpELExpressionEvaluator; -import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider; +import org.springframework.data.mapping.model.ValueExpressionEvaluator; +import org.springframework.data.mapping.model.ValueExpressionParameterValueProvider; import org.springframework.data.projection.EntityProjection; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.util.Predicates; import org.springframework.data.util.TypeInformation; +import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -87,7 +92,7 @@ import com.datastax.oss.driver.api.core.type.codec.registry.CodecRegistry; * @author Frank Spitulski */ public class MappingCassandraConverter extends AbstractCassandraConverter - implements ApplicationContextAware, BeanClassLoaderAware { + implements ApplicationContextAware, EnvironmentAware, EnvironmentCapable, BeanClassLoaderAware { private final Log log = LogFactory.getLog(getClass()); @@ -99,11 +104,21 @@ public class MappingCassandraConverter extends AbstractCassandraConverter private @Nullable ClassLoader beanClassLoader; + private @Nullable Environment environment; + private SpELContext spELContext; private final DefaultColumnTypeResolver cassandraTypeResolver; + private final EmbeddedEntityOperations embeddedEntityOperations; - private final SpelAwareProxyProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory(); + + private final SpelExpressionParser expressionParser = new SpelExpressionParser(); + + private final SpelAwareProxyProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory( + expressionParser); + + private final CachingValueExpressionEvaluatorFactory expressionEvaluatorFactory = new CachingValueExpressionEvaluatorFactory( + expressionParser, this, o -> spELContext.getEvaluationContext(o)); /** * Create a new {@link MappingCassandraConverter} with a {@link CassandraMappingContext}. @@ -171,10 +186,30 @@ public class MappingCassandraConverter extends AbstractCassandraConverter @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.spELContext = new SpELContext(this.spELContext, applicationContext); + this.environment = applicationContext.getEnvironment(); this.projectionFactory.setBeanFactory(applicationContext); } + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + @Override + public Environment getEnvironment() { + + if (this.environment == null) { + this.environment = new StandardEnvironment(); + } + return this.environment; + } + + public void setSpELContext(SpELContext spELContext) { + this.spELContext = spELContext; + } + @Override public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; @@ -294,8 +329,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter this::doReadTupleValue, this::doReadUdtValue, this::readCollectionOrArray, this::readMap, this::getPotentiallyConvertedSimpleRead, projection); - return doReadProjection(context, new RowValueProvider(row, new DefaultSpELExpressionEvaluator(row, spELContext)), - projection); + return doReadProjection(context, new RowValueProvider(row, expressionEvaluatorFactory.create(row)), projection); } @SuppressWarnings("unchecked") @@ -334,11 +368,10 @@ public class MappingCassandraConverter extends AbstractCassandraConverter ParameterValueProvider provider; if (persistenceCreator != null && persistenceCreator.hasParameters()) { - SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(valueProviderToUse.getSource(), - spELContext); + ValueExpressionEvaluator evaluator = expressionEvaluatorFactory.create(valueProviderToUse.getSource()); ParameterValueProvider parameterValueProvider = newParameterValueProvider(context, entity, valueProviderToUse); - provider = new ConverterAwareSpELExpressionParameterValueProvider(evaluator, getConversionService(), + provider = new ConverterAwareValueExpressionParameterValueProvider(evaluator, getConversionService(), parameterValueProvider, context); } else { provider = NoOpParameterValueProvider.INSTANCE; @@ -362,8 +395,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter if (typeDescriptor.isProjection()) { - CassandraValueProvider valueProvider = new RowValueProvider(row, - new DefaultSpELExpressionEvaluator(row, this.spELContext)); + CassandraValueProvider valueProvider = new RowValueProvider(row, expressionEvaluatorFactory.create(row)); return doReadProjection(context, valueProvider, typeDescriptor); } @@ -376,7 +408,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter if (typeDescriptor.isProjection()) { CassandraValueProvider valueProvider = new UdtValueProvider(udtValue, - new DefaultSpELExpressionEvaluator(udtValue, this.spELContext)); + expressionEvaluatorFactory.create(udtValue)); return doReadProjection(context, valueProvider, typeDescriptor); } @@ -389,7 +421,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter if (typeDescriptor.isProjection()) { CassandraValueProvider valueProvider = new TupleValueProvider(tupleValue, - new DefaultSpELExpressionEvaluator(tupleValue, this.spELContext)); + expressionEvaluatorFactory.create(tupleValue)); return doReadProjection(context, valueProvider, typeDescriptor); } @@ -436,10 +468,10 @@ public class MappingCassandraConverter extends AbstractCassandraConverter } private S doReadEntity(ConversionContext context, Object value, - Function valueProviderSupplier, + Function valueProviderSupplier, TypeInformation typeHint) { - SpELExpressionEvaluator expressionEvaluator = new DefaultSpELExpressionEvaluator(value, this.spELContext); + ValueExpressionEvaluator expressionEvaluator = expressionEvaluatorFactory.create(value); CassandraValueProvider valueProvider = valueProviderSupplier.apply(expressionEvaluator); return doReadEntity(context, valueProvider, typeHint); @@ -507,10 +539,10 @@ public class MappingCassandraConverter extends AbstractCassandraConverter ParameterValueProvider provider; if (persistenceCreator != null && persistenceCreator.hasParameters()) { - SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(valueProvider.getSource(), spELContext); + ValueExpressionEvaluator evaluator = expressionEvaluatorFactory.create(valueProvider.getSource()); ParameterValueProvider parameterValueProvider = newParameterValueProvider(context, entity, valueProvider); - provider = new ConverterAwareSpELExpressionParameterValueProvider(evaluator, getConversionService(), + provider = new ConverterAwareValueExpressionParameterValueProvider(evaluator, getConversionService(), parameterValueProvider, context); } else { provider = NoOpParameterValueProvider.INSTANCE; @@ -1261,23 +1293,23 @@ public class MappingCassandraConverter extends AbstractCassandraConverter } /** - * Extension of {@link SpELExpressionParameterValueProvider} to recursively trigger value conversion on the raw + * Extension of {@link ValueExpressionParameterValueProvider} to recursively trigger value conversion on the raw * resolved SpEL value. */ - private static class ConverterAwareSpELExpressionParameterValueProvider - extends SpELExpressionParameterValueProvider { + private static class ConverterAwareValueExpressionParameterValueProvider + extends ValueExpressionParameterValueProvider { private final ConversionContext context; /** - * Creates a new {@link ConverterAwareSpELExpressionParameterValueProvider}. + * Creates a new {@link ConverterAwareValueExpressionParameterValueProvider}. * * @param evaluator must not be {@literal null}. * @param conversionService must not be {@literal null}. * @param delegate must not be {@literal null}. * @param context must not be {@literal null}. */ - public ConverterAwareSpELExpressionParameterValueProvider(SpELExpressionEvaluator evaluator, + public ConverterAwareValueExpressionParameterValueProvider(ValueExpressionEvaluator evaluator, ConversionService conversionService, ParameterValueProvider delegate, ConversionContext context) { @@ -1286,8 +1318,9 @@ public class MappingCassandraConverter extends AbstractCassandraConverter } @Override - protected T potentiallyConvertSpelValue(Object object, Parameter parameter) { - return (T) context.convert(object, parameter.getType()); + protected T potentiallyConvertExpressionValue(Object object, + Parameter parameter) { + return context.convert(object, parameter.getType()); } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/RowValueProvider.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/RowValueProvider.java index 9c4f9f1c1..2b073df4d 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/RowValueProvider.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/RowValueProvider.java @@ -16,7 +16,7 @@ package org.springframework.data.cassandra.core.convert; import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty; -import org.springframework.data.mapping.model.SpELExpressionEvaluator; +import org.springframework.data.mapping.model.ValueExpressionEvaluator; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -33,16 +33,16 @@ public class RowValueProvider implements CassandraValueProvider { private final RowReader reader; - private final SpELExpressionEvaluator evaluator; + private final ValueExpressionEvaluator evaluator; /** * Create a new {@link RowValueProvider} with the given {@link Row}, {@link CodecRegistry} and - * {@link SpELExpressionEvaluator}. + * {@link ValueExpressionEvaluator}. * * @param source must not be {@literal null}. * @param evaluator must not be {@literal null}. */ - public RowValueProvider(Row source, SpELExpressionEvaluator evaluator) { + public RowValueProvider(Row source, ValueExpressionEvaluator evaluator) { Assert.notNull(source, "Source Row must not be null"); Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null"); diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/TupleValueProvider.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/TupleValueProvider.java index 4e6fe976f..69d7b6922 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/TupleValueProvider.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/TupleValueProvider.java @@ -16,7 +16,7 @@ package org.springframework.data.cassandra.core.convert; import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty; -import org.springframework.data.mapping.model.SpELExpressionEvaluator; +import org.springframework.data.mapping.model.ValueExpressionEvaluator; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -34,17 +34,17 @@ public class TupleValueProvider implements CassandraValueProvider { private final CodecRegistry codecRegistry; - private final SpELExpressionEvaluator evaluator; + private final ValueExpressionEvaluator evaluator; private final TupleValue tupleValue; /** - * Create a new {@link TupleValueProvider} with the given {@link TupleValue} and {@link SpELExpressionEvaluator}. + * Create a new {@link TupleValueProvider} with the given {@link TupleValue} and {@link ValueExpressionEvaluator}. * * @param tupleValue must not be {@literal null}. * @param evaluator must not be {@literal null}. */ - public TupleValueProvider(TupleValue tupleValue, SpELExpressionEvaluator evaluator) { + public TupleValueProvider(TupleValue tupleValue, ValueExpressionEvaluator evaluator) { Assert.notNull(tupleValue, "TupleValue must not be null"); Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null"); diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/UdtValueProvider.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/UdtValueProvider.java index aa48f706d..2539f3fa5 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/UdtValueProvider.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/UdtValueProvider.java @@ -16,7 +16,7 @@ package org.springframework.data.cassandra.core.convert; import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty; -import org.springframework.data.mapping.model.SpELExpressionEvaluator; +import org.springframework.data.mapping.model.ValueExpressionEvaluator; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -32,15 +32,15 @@ public class UdtValueProvider implements CassandraValueProvider { private final UdtValue udtValue; - private final SpELExpressionEvaluator evaluator; + private final ValueExpressionEvaluator evaluator; /** - * Create a new {@link UdtValueProvider} with the given {@link UdtValue} and {@link SpELExpressionEvaluator}. + * Create a new {@link UdtValueProvider} with the given {@link UdtValue} and {@link ValueExpressionEvaluator}. * * @param udtValue must not be {@literal null}. * @param evaluator must not be {@literal null}. */ - public UdtValueProvider(UdtValue udtValue, SpELExpressionEvaluator evaluator) { + public UdtValueProvider(UdtValue udtValue, ValueExpressionEvaluator evaluator) { Assert.notNull(udtValue, "UDTValue must not be null"); Assert.notNull(evaluator, "SpELExpressionEvaluator must not be null"); diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntity.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntity.java index deb55daa5..24fc1ecf5 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntity.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntity.java @@ -23,18 +23,23 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.expression.BeanFactoryAccessor; -import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.data.expression.ValueEvaluationContext; +import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.AssociationHandler; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.Parameter; import org.springframework.data.mapping.model.BasicPersistentEntity; +import org.springframework.data.spel.ExpressionDependencies; import org.springframework.data.util.TypeInformation; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -52,18 +57,20 @@ import com.datastax.oss.driver.api.core.CqlIdentifier; public class BasicCassandraPersistentEntity extends BasicPersistentEntity implements CassandraPersistentEntity, ApplicationContextAware { + static final ValueExpressionParser PARSER = ValueExpressionParser.create(SpelExpressionParser::new); + private static final CassandraPersistentEntityMetadataVerifier DEFAULT_VERIFIER = new CompositeCassandraPersistentEntityMetadataVerifier(); private final CqlIdentifierGenerator namingAccessor = new CqlIdentifierGenerator(); + private @Nullable ApplicationContext applicationContext; + private Boolean forceQuote; private CassandraPersistentEntityMetadataVerifier verifier = DEFAULT_VERIFIER; private CqlIdentifier tableName; - private @Nullable StandardEvaluationContext spelContext; - private final Map, CassandraPersistentProperty> constructorProperties = new ConcurrentHashMap<>(); /** @@ -117,11 +124,45 @@ public class BasicCassandraPersistentEntity extends BasicPersistentEntity extends BasicPersistentEntity getOwner() { @@ -175,7 +162,10 @@ public class BasicCassandraPersistentProperty extends AnnotationBasedPersistentP forceQuote = column.forceQuote(); } - return namingAccessor.generate(overriddenName, forceQuote, NamingStrategy::getColumnName, this, this.spelContext); + BasicCassandraPersistentEntity entity = (BasicCassandraPersistentEntity) getOwner(); + + return namingAccessor.generate(overriddenName, forceQuote, NamingStrategy::getColumnName, this, + BasicCassandraPersistentEntity.PARSER, entity::getValueEvaluationContext); } @Override diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CqlIdentifierGenerator.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CqlIdentifierGenerator.java index b6f317fc7..81b5ea899 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CqlIdentifierGenerator.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CqlIdentifierGenerator.java @@ -18,10 +18,13 @@ package org.springframework.data.cassandra.core.mapping; import java.util.function.BiFunction; import java.util.function.Function; -import org.springframework.data.cassandra.util.SpelUtils; -import org.springframework.expression.EvaluationContext; +import org.springframework.data.expression.ValueEvaluationContext; +import org.springframework.data.expression.ValueExpression; +import org.springframework.data.expression.ValueExpressionParser; +import org.springframework.data.spel.ExpressionDependencies; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import com.datastax.oss.driver.api.core.CqlIdentifier; @@ -59,18 +62,21 @@ class CqlIdentifierGenerator { * @param forceQuote whether to enforce quoting. * @param defaultNameGenerator the default name generator. * @param source source to be used for name generation. - * @param spelContext the SpEL evaluation context for evaluating SpEL expressions provided through - * {@code providedName}. + * @param parser expression parser. + * @param contextFunction evaluation context provider function. * @return the generated name. */ public CqlIdentifier generate(@Nullable String providedName, boolean forceQuote, - BiFunction defaultNameGenerator, T source, @Nullable EvaluationContext spelContext) { + BiFunction defaultNameGenerator, T source, ValueExpressionParser parser, + BiFunction contextFunction) { String name; boolean useForceQuote = forceQuote; if (StringUtils.hasText(providedName)) { - name = spelContext != null ? SpelUtils.evaluate(providedName, spelContext) : providedName; + ValueExpression expression = parser.parse(providedName); + name = ObjectUtils + .nullSafeToString(expression.evaluate(contextFunction.apply(null, expression.getExpressionDependencies()))); useForceQuote = true; } else { name = defaultNameGenerator.apply(getNamingStrategy(forceQuote), source); diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntityUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntityUnitTests.java index c854e6823..6553d191e 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntityUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntityUnitTests.java @@ -33,6 +33,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.AliasFor; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.AssociationHandler; +import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider; import org.springframework.data.util.TypeInformation; import com.datastax.oss.driver.api.core.CqlIdentifier; @@ -81,6 +82,7 @@ class BasicCassandraPersistentEntityUnitTests { BasicCassandraPersistentEntity entity = new BasicCassandraPersistentEntity<>( TypeInformation.of(UserLine.class)); + entity.setEvaluationContextProvider(new ExtensionAwareEvaluationContextProvider(context)); entity.setApplicationContext(context); assertThat(entity.getTableName()).hasToString(bean.tableName); diff --git a/src/main/antora/modules/ROOT/nav.adoc b/src/main/antora/modules/ROOT/nav.adoc index 3f30339ae..dd371f87c 100644 --- a/src/main/antora/modules/ROOT/nav.adoc +++ b/src/main/antora/modules/ROOT/nav.adoc @@ -19,6 +19,7 @@ ** xref:cassandra/property-converters.adoc[] ** xref:cassandra/events.adoc[] ** xref:cassandra/auditing.adoc[] +** xref:cassandra/value-expressions.adoc[] * xref:repositories.adoc[] ** xref:repositories/core-concepts.adoc[] diff --git a/src/main/antora/modules/ROOT/pages/cassandra/value-expressions.adoc b/src/main/antora/modules/ROOT/pages/cassandra/value-expressions.adoc new file mode 100644 index 000000000..6356a4626 --- /dev/null +++ b/src/main/antora/modules/ROOT/pages/cassandra/value-expressions.adoc @@ -0,0 +1 @@ +include::{commons}@data-commons::page$value-expressions.adoc[]