Browse Source

Add support for property placeholder in expireAfter for MongoDB indexes.

Closes: #4980
Original Pull Request: #5049

Signed-off-by: Sangbeen Moon <jjabi22@naver.com>
issue/aot-doc
Sangbeen Moon 4 months ago committed by Christoph Strobl
parent
commit
7fd979296c
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 35
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java
  2. 3
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/spel/ExpressionUtils.java
  3. 24
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java

35
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.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@ -35,9 +34,11 @@ import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.core.TypeInformation; import org.springframework.data.core.TypeInformation;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.data.expression.ValueEvaluationContext;
import org.springframework.data.mapping.Association; import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.AssociationHandler; import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.MappingException;
@ -78,6 +79,7 @@ import org.springframework.util.StringUtils;
* @author Mark Paluch * @author Mark Paluch
* @author Dave Perryman * @author Dave Perryman
* @author Stefan Tirea * @author Stefan Tirea
* @author Sangbeen Moon
* @since 1.5 * @since 1.5
*/ */
public class MongoPersistentEntityIndexResolver implements IndexResolver { public class MongoPersistentEntityIndexResolver implements IndexResolver {
@ -493,7 +495,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
return new org.bson.Document(dotPath, 1); 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 dbo = (keyDefToUse instanceof org.bson.Document document) ? document
: org.bson.Document.parse(ObjectUtils.nullSafeToString(keyDefToUse)); : org.bson.Document.parse(ObjectUtils.nullSafeToString(keyDefToUse));
@ -561,7 +563,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
} }
Duration timeout = computeIndexTimeout(index.expireAfter(), Duration timeout = computeIndexTimeout(index.expireAfter(),
() -> getEvaluationContextForProperty(persistentProperty.getOwner())); getValueEvaluationContextForProperty(persistentProperty.getOwner()));
if (!timeout.isNegative()) { if (!timeout.isNegative()) {
indexDefinition.expire(timeout); indexDefinition.expire(timeout);
} }
@ -577,7 +579,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
private PartialIndexFilter evaluatePartialFilter(String filterExpression, @Nullable PersistentEntity<?, ?> entity) { 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) { if (result instanceof org.bson.Document document) {
return PartialIndexFilter.of(document); return PartialIndexFilter.of(document);
@ -588,7 +590,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
private org.bson.Document evaluateWildcardProjection(String projectionExpression, @Nullable PersistentEntity<?, ?> entity) { 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) { if (result instanceof org.bson.Document document) {
return document; return document;
@ -599,7 +601,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
private Collation evaluateCollation(String collationExpression, @Nullable PersistentEntity<?, ?> entity) { 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) { if (result instanceof org.bson.Document document) {
return Collation.from(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} * @param persistentEntity can be {@literal null}
* @return * @return
*/ */
private EvaluationContext getEvaluationContextForProperty(@Nullable PersistentEntity<?, ?> persistentEntity) { private ValueEvaluationContext getValueEvaluationContextForProperty(@Nullable PersistentEntity<?, ?> persistentEntity) {
if (persistentEntity == null || !(persistentEntity instanceof BasicMongoPersistentEntity)) { if (persistentEntity instanceof BasicMongoPersistentEntity<?> mongoEntity) {
return getEvaluationContext(); return mongoEntity.getValueEvaluationContext(null);
} }
EvaluationContext contextFromEntity = ((BasicMongoPersistentEntity<?>) persistentEntity).getEvaluationContext(null); return ValueEvaluationContext.of(
new StandardEnvironment(), getEvaluationContext());
if (contextFromEntity != null && !EvaluationContextProvider.DEFAULT.equals(contextFromEntity)) {
return contextFromEntity;
}
return getEvaluationContext();
} }
/** /**
@ -719,7 +716,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
String nameToUse = ""; String nameToUse = "";
if (StringUtils.hasText(indexName)) { if (StringUtils.hasText(indexName)) {
Object result = ExpressionUtils.evaluate(indexName, () -> getEvaluationContextForProperty(entity)); Object result = ExpressionUtils.evaluate(indexName, () -> getValueEvaluationContextForProperty(entity));
if (result != null) { if (result != null) {
nameToUse = ObjectUtils.nullSafeToString(result); nameToUse = ObjectUtils.nullSafeToString(result);
@ -780,7 +777,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
* @since 2.2 * @since 2.2
* @throws IllegalArgumentException for invalid duration values. * @throws IllegalArgumentException for invalid duration values.
*/ */
private static Duration computeIndexTimeout(String timeoutValue, Supplier<EvaluationContext> evaluationContext) { private static Duration computeIndexTimeout(String timeoutValue, ValueEvaluationContext evaluationContext) {
return DurationUtil.evaluate(timeoutValue, evaluationContext); return DurationUtil.evaluate(timeoutValue, evaluationContext);
} }

3
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 java.util.function.Supplier;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.data.expression.ValueEvaluationContext;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression; import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext; import org.springframework.expression.ParserContext;
@ -52,7 +53,7 @@ public final class ExpressionUtils {
return expression instanceof LiteralExpression ? null : expression; return expression instanceof LiteralExpression ? null : expression;
} }
public static @Nullable Object evaluate(String value, Supplier<EvaluationContext> evaluationContext) { public static @Nullable Object evaluate(String value, Supplier<ValueEvaluationContext> evaluationContext) {
Expression expression = detectExpression(value); Expression expression = detectExpression(value);
if (expression == null) { if (expression == null) {

24
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.junit.runners.Suite.SuiteClasses;
import org.springframework.core.annotation.AliasFor; 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.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.core.TypeInformation; import org.springframework.data.core.TypeInformation;
@ -106,6 +108,23 @@ public class MongoPersistentEntityIndexResolverUnitTests {
assertThat(definitions).isNotEmpty(); 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<IndexDefinitionHolder> indexDefinitions = (List<IndexDefinitionHolder>) resolver
.resolveIndexFor(WithExpireAfterAsPropertyPlaceholder.class);
assertThat(indexDefinitions).hasSize(1);
assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("expireAfterSeconds", 600L);
}
@Test // DATAMONGO-899 @Test // DATAMONGO-899
public void deeplyNestedIndexPathIsResolvedCorrectly() { public void deeplyNestedIndexPathIsResolvedCorrectly() {
@ -432,6 +451,11 @@ public class MongoPersistentEntityIndexResolverUnitTests {
class WithPartialFilter { class WithPartialFilter {
@Indexed(partialFilter = "{'value': {'$exists': true}}") String withPartialFilter; @Indexed(partialFilter = "{'value': {'$exists': true}}") String withPartialFilter;
} }
@Document
class WithExpireAfterAsPropertyPlaceholder {
@Indexed(expireAfter = "${ttl.timeout}") String withTimeout;
}
} }
@Target({ ElementType.FIELD }) @Target({ ElementType.FIELD })

Loading…
Cancel
Save