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 2c6c6c4aa..5fc3cf7e4 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,7 +27,6 @@ import java.util.List; 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; @@ -35,9 +34,11 @@ 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.core.TypeInformation; 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; @@ -78,6 +79,7 @@ import org.springframework.util.StringUtils; * @author Mark Paluch * @author Dave Perryman * @author Stefan Tirea + * @author Sangbeen Moon * @since 1.5 */ public class MongoPersistentEntityIndexResolver implements IndexResolver { @@ -493,7 +495,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { 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)); @@ -561,7 +563,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { } Duration timeout = computeIndexTimeout(index.expireAfter(), - () -> getEvaluationContextForProperty(persistentProperty.getOwner())); + getValueEvaluationContextForProperty(persistentProperty.getOwner())); if (!timeout.isNegative()) { indexDefinition.expire(timeout); } @@ -577,7 +579,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { 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); @@ -588,7 +590,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { 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; @@ -599,7 +601,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { 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); } @@ -650,24 +652,19 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { } /** - * 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()); } /** @@ -719,7 +716,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { 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); @@ -780,7 +777,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { * @since 2.2 * @throws IllegalArgumentException for invalid duration values. */ - private static Duration computeIndexTimeout(String timeoutValue, Supplier 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 796f61890..60d0df3c6 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 @@ package org.springframework.data.mongodb.util.spel; 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 08cb32987..a7f21a16b 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 @@ -33,6 +33,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.core.TypeInformation; @@ -106,6 +108,23 @@ public class MongoPersistentEntityIndexResolverUnitTests { 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() { @@ -432,6 +451,11 @@ public class MongoPersistentEntityIndexResolverUnitTests { class WithPartialFilter { @Indexed(partialFilter = "{'value': {'$exists': true}}") String withPartialFilter; } + + @Document + class WithExpireAfterAsPropertyPlaceholder { + @Indexed(expireAfter = "${ttl.timeout}") String withTimeout; + } } @Target({ ElementType.FIELD })