Skip to content

Commit dae1810

Browse files
committed
Add support for property placeholder in expireAfter for MongoDB indexes
Signed-off-by: Sangbeen Moon <[email protected]>
1 parent 3b6798f commit dae1810

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,15 +27,16 @@
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
import org.springframework.core.annotation.MergedAnnotation;
36+
import org.springframework.core.env.StandardEnvironment;
3737
import org.springframework.dao.InvalidDataAccessApiUsageException;
3838
import org.springframework.data.domain.Sort;
39+
import org.springframework.data.expression.ValueEvaluationContext;
3940
import org.springframework.data.mapping.Association;
4041
import org.springframework.data.mapping.AssociationHandler;
4142
import org.springframework.data.mapping.MappingException;
@@ -77,6 +78,7 @@
7778
* @author Mark Paluch
7879
* @author Dave Perryman
7980
* @author Stefan Tirea
81+
* @author Sangbeen Moon
8082
* @since 1.5
8183
*/
8284
public class MongoPersistentEntityIndexResolver implements IndexResolver {
@@ -492,7 +494,7 @@ private org.bson.Document resolveCompoundIndexKeyFromStringDefinition(String dot
492494
return new org.bson.Document(dotPath, 1);
493495
}
494496

495-
Object keyDefToUse = ExpressionUtils.evaluate(keyDefinitionString, () -> getEvaluationContextForProperty(entity));
497+
Object keyDefToUse = ExpressionUtils.evaluate(keyDefinitionString, () -> getValueEvaluationContextForProperty(entity));
496498

497499
org.bson.Document dbo = (keyDefToUse instanceof org.bson.Document document) ? document
498500
: org.bson.Document.parse(ObjectUtils.nullSafeToString(keyDefToUse));
@@ -560,7 +562,7 @@ private org.bson.Document resolveCompoundIndexKeyFromStringDefinition(String dot
560562
}
561563

562564
Duration timeout = computeIndexTimeout(index.expireAfter(),
563-
() -> getEvaluationContextForProperty(persistentProperty.getOwner()));
565+
getValueEvaluationContextForProperty(persistentProperty.getOwner()));
564566
if (!timeout.isNegative()) {
565567
indexDefinition.expire(timeout);
566568
}
@@ -576,7 +578,7 @@ private org.bson.Document resolveCompoundIndexKeyFromStringDefinition(String dot
576578

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

579-
Object result = ExpressionUtils.evaluate(filterExpression, () -> getEvaluationContextForProperty(entity));
581+
Object result = ExpressionUtils.evaluate(filterExpression, () -> getValueEvaluationContextForProperty(entity));
580582

581583
if (result instanceof org.bson.Document document) {
582584
return PartialIndexFilter.of(document);
@@ -587,7 +589,7 @@ private PartialIndexFilter evaluatePartialFilter(String filterExpression, @Nulla
587589

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

590-
Object result = ExpressionUtils.evaluate(projectionExpression, () -> getEvaluationContextForProperty(entity));
592+
Object result = ExpressionUtils.evaluate(projectionExpression, () -> getValueEvaluationContextForProperty(entity));
591593

592594
if (result instanceof org.bson.Document document) {
593595
return document;
@@ -598,7 +600,7 @@ private org.bson.Document evaluateWildcardProjection(String projectionExpression
598600

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

601-
Object result = ExpressionUtils.evaluate(collationExpression, () -> getEvaluationContextForProperty(entity));
603+
Object result = ExpressionUtils.evaluate(collationExpression, () -> getValueEvaluationContextForProperty(entity));
602604
if (result instanceof org.bson.Document document) {
603605
return Collation.from(document);
604606
}
@@ -649,24 +651,19 @@ protected EvaluationContext getEvaluationContext() {
649651
}
650652

651653
/**
652-
* Get the {@link EvaluationContext} for a given {@link PersistentEntity entity} the default one.
654+
* Get the {@link ValueEvaluationContext} for a given {@link PersistentEntity entity} the default one.
653655
*
654656
* @param persistentEntity can be {@literal null}
655657
* @return
656658
*/
657-
private EvaluationContext getEvaluationContextForProperty(@Nullable PersistentEntity<?, ?> persistentEntity) {
659+
private ValueEvaluationContext getValueEvaluationContextForProperty(@Nullable PersistentEntity<?, ?> persistentEntity) {
658660

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

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

672669
/**
@@ -718,7 +715,7 @@ private String pathAwareIndexName(String indexName, String dotPath, @Nullable Pe
718715
String nameToUse = "";
719716
if (StringUtils.hasText(indexName)) {
720717

721-
Object result = ExpressionUtils.evaluate(indexName, () -> getEvaluationContextForProperty(entity));
718+
Object result = ExpressionUtils.evaluate(indexName, () -> getValueEvaluationContextForProperty(entity));
722719

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

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
@@ -32,6 +32,8 @@
3232
import org.junit.runners.Suite;
3333
import org.junit.runners.Suite.SuiteClasses;
3434
import org.springframework.core.annotation.AliasFor;
35+
import org.springframework.core.env.MapPropertySource;
36+
import org.springframework.core.env.StandardEnvironment;
3537
import org.springframework.dao.InvalidDataAccessApiUsageException;
3638
import org.springframework.data.annotation.Id;
3739
import org.springframework.data.geo.Point;
@@ -105,6 +107,23 @@ public void shouldResolveIndexViaClass() {
105107
assertThat(definitions).isNotEmpty();
106108
}
107109

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

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

436460
@Target({ ElementType.FIELD })

0 commit comments

Comments
 (0)