diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java index b7beaaa3e1..b7a201de15 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java @@ -27,15 +27,16 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.env.StandardEnvironment; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Sort; +import org.springframework.data.expression.ValueEvaluationContext; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.AssociationHandler; import org.springframework.data.mapping.MappingException; @@ -77,6 +78,7 @@ * @author Mark Paluch * @author Dave Perryman * @author Stefan Tirea + * @author Sangbeen Moon * @since 1.5 */ public class MongoPersistentEntityIndexResolver implements IndexResolver { @@ -492,7 +494,7 @@ private org.bson.Document resolveCompoundIndexKeyFromStringDefinition(String dot return new org.bson.Document(dotPath, 1); } - Object keyDefToUse = ExpressionUtils.evaluate(keyDefinitionString, () -> getEvaluationContextForProperty(entity)); + Object keyDefToUse = ExpressionUtils.evaluate(keyDefinitionString, () -> getValueEvaluationContextForProperty(entity)); org.bson.Document dbo = (keyDefToUse instanceof org.bson.Document document) ? document : org.bson.Document.parse(ObjectUtils.nullSafeToString(keyDefToUse)); @@ -560,7 +562,7 @@ private org.bson.Document resolveCompoundIndexKeyFromStringDefinition(String dot } Duration timeout = computeIndexTimeout(index.expireAfter(), - () -> getEvaluationContextForProperty(persistentProperty.getOwner())); + getValueEvaluationContextForProperty(persistentProperty.getOwner())); if (!timeout.isNegative()) { indexDefinition.expire(timeout); } @@ -576,7 +578,7 @@ private org.bson.Document resolveCompoundIndexKeyFromStringDefinition(String dot private PartialIndexFilter evaluatePartialFilter(String filterExpression, @Nullable PersistentEntity entity) { - Object result = ExpressionUtils.evaluate(filterExpression, () -> getEvaluationContextForProperty(entity)); + Object result = ExpressionUtils.evaluate(filterExpression, () -> getValueEvaluationContextForProperty(entity)); if (result instanceof org.bson.Document document) { return PartialIndexFilter.of(document); @@ -587,7 +589,7 @@ private PartialIndexFilter evaluatePartialFilter(String filterExpression, @Nulla private org.bson.Document evaluateWildcardProjection(String projectionExpression, @Nullable PersistentEntity entity) { - Object result = ExpressionUtils.evaluate(projectionExpression, () -> getEvaluationContextForProperty(entity)); + Object result = ExpressionUtils.evaluate(projectionExpression, () -> getValueEvaluationContextForProperty(entity)); if (result instanceof org.bson.Document document) { return document; @@ -598,7 +600,7 @@ private org.bson.Document evaluateWildcardProjection(String projectionExpression private Collation evaluateCollation(String collationExpression, @Nullable PersistentEntity entity) { - Object result = ExpressionUtils.evaluate(collationExpression, () -> getEvaluationContextForProperty(entity)); + Object result = ExpressionUtils.evaluate(collationExpression, () -> getValueEvaluationContextForProperty(entity)); if (result instanceof org.bson.Document document) { return Collation.from(document); } @@ -649,24 +651,19 @@ protected EvaluationContext getEvaluationContext() { } /** - * Get the {@link EvaluationContext} for a given {@link PersistentEntity entity} the default one. + * Get the {@link ValueEvaluationContext} for a given {@link PersistentEntity entity} the default one. * * @param persistentEntity can be {@literal null} * @return */ - private EvaluationContext getEvaluationContextForProperty(@Nullable PersistentEntity persistentEntity) { + private ValueEvaluationContext getValueEvaluationContextForProperty(@Nullable PersistentEntity persistentEntity) { - if (persistentEntity == null || !(persistentEntity instanceof BasicMongoPersistentEntity)) { - return getEvaluationContext(); + if (persistentEntity instanceof BasicMongoPersistentEntity mongoEntity) { + return mongoEntity.getValueEvaluationContext(null); } - EvaluationContext contextFromEntity = ((BasicMongoPersistentEntity) persistentEntity).getEvaluationContext(null); - - if (contextFromEntity != null && !EvaluationContextProvider.DEFAULT.equals(contextFromEntity)) { - return contextFromEntity; - } - - return getEvaluationContext(); + return ValueEvaluationContext.of( + new StandardEnvironment(), getEvaluationContext()); } /** @@ -718,7 +715,7 @@ private String pathAwareIndexName(String indexName, String dotPath, @Nullable Pe String nameToUse = ""; if (StringUtils.hasText(indexName)) { - Object result = ExpressionUtils.evaluate(indexName, () -> getEvaluationContextForProperty(entity)); + Object result = ExpressionUtils.evaluate(indexName, () -> getValueEvaluationContextForProperty(entity)); if (result != null) { nameToUse = ObjectUtils.nullSafeToString(result); @@ -779,7 +776,7 @@ private void resolveAndAddIndexesForAssociation(Association evaluationContext) { + private static Duration computeIndexTimeout(String timeoutValue, ValueEvaluationContext evaluationContext) { return DurationUtil.evaluate(timeoutValue, evaluationContext); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/spel/ExpressionUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/spel/ExpressionUtils.java index 796f618906..60d0df3c60 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/spel/ExpressionUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/spel/ExpressionUtils.java @@ -18,6 +18,7 @@ import java.util.function.Supplier; import org.jspecify.annotations.Nullable; +import org.springframework.data.expression.ValueEvaluationContext; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.ParserContext; @@ -52,7 +53,7 @@ public final class ExpressionUtils { return expression instanceof LiteralExpression ? null : expression; } - public static @Nullable Object evaluate(String value, Supplier evaluationContext) { + public static @Nullable Object evaluate(String value, Supplier evaluationContext) { Expression expression = detectExpression(value); if (expression == null) { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java index dda16f7849..4042c1a8bd 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java @@ -32,6 +32,8 @@ import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; import org.springframework.core.annotation.AliasFor; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.StandardEnvironment; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.annotation.Id; import org.springframework.data.geo.Point; @@ -105,6 +107,23 @@ public void shouldResolveIndexViaClass() { assertThat(definitions).isNotEmpty(); } + @Test // GH-4980 + public void shouldSupportPropertyPlaceholderInExpireAfter() { + StandardEnvironment environment = new StandardEnvironment(); + environment.getPropertySources().addFirst(new MapPropertySource("test", Map.of("ttl.timeout", "10m"))); + + MongoMappingContext mappingContext = new MongoMappingContext(); + mappingContext.setEnvironment(environment); + + MongoPersistentEntityIndexResolver resolver = new MongoPersistentEntityIndexResolver(mappingContext); + + List indexDefinitions = (List) resolver + .resolveIndexFor(WithExpireAfterAsPropertyPlaceholder.class); + + assertThat(indexDefinitions).hasSize(1); + assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("expireAfterSeconds", 600L); + } + @Test // DATAMONGO-899 public void deeplyNestedIndexPathIsResolvedCorrectly() { @@ -431,6 +450,11 @@ class WithIndexNameAsExpression { class WithPartialFilter { @Indexed(partialFilter = "{'value': {'$exists': true}}") String withPartialFilter; } + + @Document + class WithExpireAfterAsPropertyPlaceholder { + @Indexed(expireAfter = "${ttl.timeout}") String withTimeout; + } } @Target({ ElementType.FIELD })