Skip to content

Commit 7fd9792

Browse files
sangbeenmoonchristophstrobl
authored andcommitted
Add support for property placeholder in expireAfter for MongoDB indexes.
Closes: #4980 Original Pull Request: #5049 Signed-off-by: Sangbeen Moon <jjabi22@naver.com>
1 parent 15b958a commit 7fd9792

File tree

3 files changed

+42
-20
lines changed

3 files changed

+42
-20
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,18 @@
2727
import java.util.Map;
2828
import java.util.Set;
2929
import java.util.concurrent.TimeUnit;
30-
import java.util.function.Supplier;
3130
import java.util.stream.Collectors;
3231

3332
import org.apache.commons.logging.Log;
3433
import org.apache.commons.logging.LogFactory;
3534
import org.jspecify.annotations.Nullable;
3635

3736
import org.springframework.core.annotation.MergedAnnotation;
37+
import org.springframework.core.env.StandardEnvironment;
3838
import org.springframework.dao.InvalidDataAccessApiUsageException;
3939
import org.springframework.data.core.TypeInformation;
4040
import org.springframework.data.domain.Sort;
41+
import org.springframework.data.expression.ValueEvaluationContext;
4142
import org.springframework.data.mapping.Association;
4243
import org.springframework.data.mapping.AssociationHandler;
4344
import org.springframework.data.mapping.MappingException;
@@ -78,6 +79,7 @@
7879
* @author Mark Paluch
7980
* @author Dave Perryman
8081
* @author Stefan Tirea
82+
* @author Sangbeen Moon
8183
* @since 1.5
8284
*/
8385
public class MongoPersistentEntityIndexResolver implements IndexResolver {
@@ -493,7 +495,7 @@ private org.bson.Document resolveCompoundIndexKeyFromStringDefinition(String dot
493495
return new org.bson.Document(dotPath, 1);
494496
}
495497

496-
Object keyDefToUse = ExpressionUtils.evaluate(keyDefinitionString, () -> getEvaluationContextForProperty(entity));
498+
Object keyDefToUse = ExpressionUtils.evaluate(keyDefinitionString, () -> getValueEvaluationContextForProperty(entity));
497499

498500
org.bson.Document dbo = (keyDefToUse instanceof org.bson.Document document) ? document
499501
: org.bson.Document.parse(ObjectUtils.nullSafeToString(keyDefToUse));
@@ -561,7 +563,7 @@ private org.bson.Document resolveCompoundIndexKeyFromStringDefinition(String dot
561563
}
562564

563565
Duration timeout = computeIndexTimeout(index.expireAfter(),
564-
() -> getEvaluationContextForProperty(persistentProperty.getOwner()));
566+
getValueEvaluationContextForProperty(persistentProperty.getOwner()));
565567
if (!timeout.isNegative()) {
566568
indexDefinition.expire(timeout);
567569
}
@@ -577,7 +579,7 @@ private org.bson.Document resolveCompoundIndexKeyFromStringDefinition(String dot
577579

578580
private PartialIndexFilter evaluatePartialFilter(String filterExpression, @Nullable PersistentEntity<?, ?> entity) {
579581

580-
Object result = ExpressionUtils.evaluate(filterExpression, () -> getEvaluationContextForProperty(entity));
582+
Object result = ExpressionUtils.evaluate(filterExpression, () -> getValueEvaluationContextForProperty(entity));
581583

582584
if (result instanceof org.bson.Document document) {
583585
return PartialIndexFilter.of(document);
@@ -588,7 +590,7 @@ private PartialIndexFilter evaluatePartialFilter(String filterExpression, @Nulla
588590

589591
private org.bson.Document evaluateWildcardProjection(String projectionExpression, @Nullable PersistentEntity<?, ?> entity) {
590592

591-
Object result = ExpressionUtils.evaluate(projectionExpression, () -> getEvaluationContextForProperty(entity));
593+
Object result = ExpressionUtils.evaluate(projectionExpression, () -> getValueEvaluationContextForProperty(entity));
592594

593595
if (result instanceof org.bson.Document document) {
594596
return document;
@@ -599,7 +601,7 @@ private org.bson.Document evaluateWildcardProjection(String projectionExpression
599601

600602
private Collation evaluateCollation(String collationExpression, @Nullable PersistentEntity<?, ?> entity) {
601603

602-
Object result = ExpressionUtils.evaluate(collationExpression, () -> getEvaluationContextForProperty(entity));
604+
Object result = ExpressionUtils.evaluate(collationExpression, () -> getValueEvaluationContextForProperty(entity));
603605
if (result instanceof org.bson.Document document) {
604606
return Collation.from(document);
605607
}
@@ -650,24 +652,19 @@ protected EvaluationContext getEvaluationContext() {
650652
}
651653

652654
/**
653-
* Get the {@link EvaluationContext} for a given {@link PersistentEntity entity} the default one.
655+
* Get the {@link ValueEvaluationContext} for a given {@link PersistentEntity entity} the default one.
654656
*
655657
* @param persistentEntity can be {@literal null}
656658
* @return
657659
*/
658-
private EvaluationContext getEvaluationContextForProperty(@Nullable PersistentEntity<?, ?> persistentEntity) {
660+
private ValueEvaluationContext getValueEvaluationContextForProperty(@Nullable PersistentEntity<?, ?> persistentEntity) {
659661

660-
if (persistentEntity == null || !(persistentEntity instanceof BasicMongoPersistentEntity)) {
661-
return getEvaluationContext();
662+
if (persistentEntity instanceof BasicMongoPersistentEntity<?> mongoEntity) {
663+
return mongoEntity.getValueEvaluationContext(null);
662664
}
663665

664-
EvaluationContext contextFromEntity = ((BasicMongoPersistentEntity<?>) persistentEntity).getEvaluationContext(null);
665-
666-
if (contextFromEntity != null && !EvaluationContextProvider.DEFAULT.equals(contextFromEntity)) {
667-
return contextFromEntity;
668-
}
669-
670-
return getEvaluationContext();
666+
return ValueEvaluationContext.of(
667+
new StandardEnvironment(), getEvaluationContext());
671668
}
672669

673670
/**
@@ -719,7 +716,7 @@ private String pathAwareIndexName(String indexName, String dotPath, @Nullable Pe
719716
String nameToUse = "";
720717
if (StringUtils.hasText(indexName)) {
721718

722-
Object result = ExpressionUtils.evaluate(indexName, () -> getEvaluationContextForProperty(entity));
719+
Object result = ExpressionUtils.evaluate(indexName, () -> getValueEvaluationContextForProperty(entity));
723720

724721
if (result != null) {
725722
nameToUse = ObjectUtils.nullSafeToString(result);
@@ -780,7 +777,7 @@ private void resolveAndAddIndexesForAssociation(Association<MongoPersistentPrope
780777
* @since 2.2
781778
* @throws IllegalArgumentException for invalid duration values.
782779
*/
783-
private static Duration computeIndexTimeout(String timeoutValue, Supplier<EvaluationContext> evaluationContext) {
780+
private static Duration computeIndexTimeout(String timeoutValue, ValueEvaluationContext evaluationContext) {
784781
return DurationUtil.evaluate(timeoutValue, evaluationContext);
785782
}
786783

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/spel/ExpressionUtils.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.function.Supplier;
1919

2020
import org.jspecify.annotations.Nullable;
21+
import org.springframework.data.expression.ValueEvaluationContext;
2122
import org.springframework.expression.EvaluationContext;
2223
import org.springframework.expression.Expression;
2324
import org.springframework.expression.ParserContext;
@@ -52,7 +53,7 @@ public final class ExpressionUtils {
5253
return expression instanceof LiteralExpression ? null : expression;
5354
}
5455

55-
public static @Nullable Object evaluate(String value, Supplier<EvaluationContext> evaluationContext) {
56+
public static @Nullable Object evaluate(String value, Supplier<ValueEvaluationContext> evaluationContext) {
5657

5758
Expression expression = detectExpression(value);
5859
if (expression == null) {

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import org.junit.runners.Suite.SuiteClasses;
3434

3535
import org.springframework.core.annotation.AliasFor;
36+
import org.springframework.core.env.MapPropertySource;
37+
import org.springframework.core.env.StandardEnvironment;
3638
import org.springframework.dao.InvalidDataAccessApiUsageException;
3739
import org.springframework.data.annotation.Id;
3840
import org.springframework.data.core.TypeInformation;
@@ -106,6 +108,23 @@ public void shouldResolveIndexViaClass() {
106108
assertThat(definitions).isNotEmpty();
107109
}
108110

111+
@Test // GH-4980
112+
public void shouldSupportPropertyPlaceholderInExpireAfter() {
113+
StandardEnvironment environment = new StandardEnvironment();
114+
environment.getPropertySources().addFirst(new MapPropertySource("test", Map.of("ttl.timeout", "10m")));
115+
116+
MongoMappingContext mappingContext = new MongoMappingContext();
117+
mappingContext.setEnvironment(environment);
118+
119+
MongoPersistentEntityIndexResolver resolver = new MongoPersistentEntityIndexResolver(mappingContext);
120+
121+
List<IndexDefinitionHolder> indexDefinitions = (List<IndexDefinitionHolder>) resolver
122+
.resolveIndexFor(WithExpireAfterAsPropertyPlaceholder.class);
123+
124+
assertThat(indexDefinitions).hasSize(1);
125+
assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("expireAfterSeconds", 600L);
126+
}
127+
109128
@Test // DATAMONGO-899
110129
public void deeplyNestedIndexPathIsResolvedCorrectly() {
111130

@@ -432,6 +451,11 @@ class WithIndexNameAsExpression {
432451
class WithPartialFilter {
433452
@Indexed(partialFilter = "{'value': {'$exists': true}}") String withPartialFilter;
434453
}
454+
455+
@Document
456+
class WithExpireAfterAsPropertyPlaceholder {
457+
@Indexed(expireAfter = "${ttl.timeout}") String withTimeout;
458+
}
435459
}
436460

437461
@Target({ ElementType.FIELD })

0 commit comments

Comments
 (0)