diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java
index f58f0d809..0fbd88d72 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java
@@ -36,10 +36,29 @@ import java.lang.annotation.Target;
public @interface CompoundIndex {
/**
- * The actual index definition in JSON format. The keys of the JSON document are the fields to be indexed, the values
- * define the index direction (1 for ascending, -1 for descending).
+ * The actual index definition in JSON format or a {@link org.springframework.expression.spel.standard.SpelExpression
+ * template expression} resolving to either a JSON String or a {@link org.bson.Document}. The keys of the JSON
+ * document are the fields to be indexed, the values define the index direction (1 for ascending, -1 for descending).
+ *
* If left empty on nested document, the whole document will be indexed.
*
+ *
+ *
+ *
+ * @Document
+ * @CompoundIndex(def = "{'h1': 1, 'h2': 1}")
+ * class JsonStringIndexDefinition {
+ * String h1, h2;
+ * }
+ *
+ * @Document
+ * @CompoundIndex(def = "#{T(org.bson.Document).parse("{ 'h1': 1, 'h2': 1 }")}")
+ * class ExpressionIndexDefinition {
+ * String h1, h2;
+ * }
+ *
+ *
+ *
* @return
*/
String def() default "";
@@ -79,7 +98,8 @@ public @interface CompoundIndex {
boolean dropDups() default false;
/**
- * The name of the index to be created.
*
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 f7481fed4..fdcdd5d2e 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
@@ -40,6 +40,7 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.MappingException;
+import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.CycleGuard.Path;
@@ -62,6 +63,7 @@ import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.NumberUtils;
+import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@@ -356,10 +358,10 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
MongoPersistentEntity> entity) {
CompoundIndexDefinition indexDefinition = new CompoundIndexDefinition(
- resolveCompoundIndexKeyFromStringDefinition(dotPath, index.def()));
+ resolveCompoundIndexKeyFromStringDefinition(dotPath, index.def(), entity));
if (!index.useGeneratedName()) {
- indexDefinition.named(pathAwareIndexName(index.name(), dotPath, null));
+ indexDefinition.named(pathAwareIndexName(index.name(), dotPath, entity, null));
}
if (index.unique()) {
@@ -377,7 +379,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
}
- private org.bson.Document resolveCompoundIndexKeyFromStringDefinition(String dotPath, String keyDefinitionString) {
+ private org.bson.Document resolveCompoundIndexKeyFromStringDefinition(String dotPath, String keyDefinitionString,
+ PersistentEntity, ?> entity) {
if (!StringUtils.hasText(dotPath) && !StringUtils.hasText(keyDefinitionString)) {
throw new InvalidDataAccessApiUsageException("Cannot create index on root level for empty keys.");
@@ -387,7 +390,12 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
return new org.bson.Document(dotPath, 1);
}
- org.bson.Document dbo = org.bson.Document.parse(keyDefinitionString);
+ Object keyDefToUse = evaluatePotentialTemplateExpression(keyDefinitionString,
+ getEvaluationContextForProperty(entity));
+
+ org.bson.Document dbo = (keyDefToUse instanceof org.bson.Document) ? (org.bson.Document) keyDefToUse
+ : org.bson.Document.parse(ObjectUtils.nullSafeToString(keyDefToUse));
+
if (!StringUtils.hasText(dotPath)) {
return dbo;
}
@@ -423,7 +431,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
IndexDirection.ASCENDING.equals(index.direction()) ? Sort.Direction.ASC : Sort.Direction.DESC);
if (!index.useGeneratedName()) {
- indexDefinition.named(pathAwareIndexName(index.name(), dotPath, persitentProperty));
+ indexDefinition.named(pathAwareIndexName(index.name(), dotPath, persitentProperty.getOwner(), persitentProperty));
}
if (index.unique()) {
@@ -446,21 +454,12 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
if (index.expireAfterSeconds() >= 0) {
throw new IllegalStateException(String.format(
- "@Indexed already defines an expiration timeout of %s sec. via Indexed#expireAfterSeconds. Please make to use either expireAfterSeconds or expireAfter.", index.expireAfterSeconds()));
+ "@Indexed already defines an expiration timeout of %s sec. via Indexed#expireAfterSeconds. Please make to use either expireAfterSeconds or expireAfter.",
+ index.expireAfterSeconds()));
}
- EvaluationContext ctx = getEvaluationContext();
-
- if (persitentProperty.getOwner() instanceof BasicMongoPersistentEntity) {
-
- EvaluationContext contextFromEntity = ((BasicMongoPersistentEntity>) persitentProperty.getOwner())
- .getEvaluationContext(null);
- if (contextFromEntity != null && !EvaluationContextProvider.DEFAULT.equals(contextFromEntity)) {
- ctx = contextFromEntity;
- }
- }
-
- Duration timeout = computeIndexTimeout(index.expireAfter(), ctx);
+ Duration timeout = computeIndexTimeout(index.expireAfter(),
+ getEvaluationContextForProperty(persitentProperty.getOwner()));
if (!timeout.isZero() && !timeout.isNegative()) {
indexDefinition.expire(timeout);
}
@@ -479,6 +478,27 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
return evaluationContextProvider.getEvaluationContext(null);
}
+ /**
+ * Get the {@link EvaluationContext} for a given {@link PersistentEntity entity} the default one.
+ *
+ * @param persistentEntity can be {@literal null}
+ * @return
+ */
+ private EvaluationContext getEvaluationContextForProperty(@Nullable PersistentEntity, ?> persistentEntity) {
+
+ if (persistentEntity == null || !(persistentEntity instanceof BasicMongoPersistentEntity)) {
+ return getEvaluationContext();
+ }
+
+ EvaluationContext contextFromEntity = ((BasicMongoPersistentEntity>) persistentEntity).getEvaluationContext(null);
+
+ if (contextFromEntity != null && !EvaluationContextProvider.DEFAULT.equals(contextFromEntity)) {
+ return contextFromEntity;
+ }
+
+ return getEvaluationContext();
+ }
+
/**
* Set the {@link EvaluationContextProvider} used for obtaining the {@link EvaluationContext} used to compute
* {@link org.springframework.expression.spel.standard.SpelExpression expressions}.
@@ -514,7 +534,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
indexDefinition.withMin(index.min()).withMax(index.max());
if (!index.useGeneratedName()) {
- indexDefinition.named(pathAwareIndexName(index.name(), dotPath, persistentProperty));
+ indexDefinition
+ .named(pathAwareIndexName(index.name(), dotPath, persistentProperty.getOwner(), persistentProperty));
}
indexDefinition.typed(index.type()).withBucketSize(index.bucketSize()).withAdditionalField(index.additionalField());
@@ -522,9 +543,13 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
}
- private String pathAwareIndexName(String indexName, String dotPath, @Nullable MongoPersistentProperty property) {
+ private String pathAwareIndexName(String indexName, String dotPath, @Nullable PersistentEntity, ?> entity,
+ @Nullable MongoPersistentProperty property) {
- String nameToUse = StringUtils.hasText(indexName) ? indexName : "";
+ String nameToUse = StringUtils.hasText(indexName)
+ ? ObjectUtils
+ .nullSafeToString(evaluatePotentialTemplateExpression(indexName, getEvaluationContextForProperty(entity)))
+ : "";
if (!StringUtils.hasText(dotPath) || (property != null && dotPath.equals(property.getFieldName()))) {
return StringUtils.hasText(nameToUse) ? nameToUse : dotPath;
@@ -582,7 +607,17 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
*/
private static Duration computeIndexTimeout(String timeoutValue, EvaluationContext evaluationContext) {
- String val = evaluatePotentialTemplateExpression(timeoutValue, evaluationContext);
+ Object evaluatedTimeout = evaluatePotentialTemplateExpression(timeoutValue, evaluationContext);
+
+ if (evaluatedTimeout == null) {
+ return Duration.ZERO;
+ }
+
+ if (evaluatedTimeout instanceof Duration) {
+ return (Duration) evaluatedTimeout;
+ }
+
+ String val = evaluatedTimeout.toString();
if (val == null) {
return Duration.ZERO;
@@ -611,15 +646,14 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
}
@Nullable
- private static String evaluatePotentialTemplateExpression(String value, EvaluationContext evaluationContext) {
+ private static Object evaluatePotentialTemplateExpression(String value, EvaluationContext evaluationContext) {
Expression expression = PARSER.parseExpression(value, ParserContext.TEMPLATE_EXPRESSION);
if (expression instanceof LiteralExpression) {
return value;
}
- return expression.getValue(evaluationContext, String.class);
-
+ return expression.getValue(evaluationContext, Object.class);
}
/**
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 e9ce7b350..1b35616c0 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
@@ -220,6 +220,15 @@ public class MongoPersistentEntityIndexResolverUnitTests {
Assertions.assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("expireAfterSeconds", 11L);
}
+ @Test // DATAMONGO-2112
+ public void shouldResolveTimeoutFromExpressionReturningDuration() {
+
+ List indexDefinitions = prepareMappingContextAndResolveIndexForType(
+ WithExpireAfterAsExpressionResultingInDuration.class);
+
+ Assertions.assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("expireAfterSeconds", 100L);
+ }
+
@Test // DATAMONGO-2112
public void shouldErrorOnInvalidTimeoutExpression() {
@@ -241,6 +250,15 @@ public class MongoPersistentEntityIndexResolverUnitTests {
.resolveIndexForEntity(mappingContext.getRequiredPersistentEntity(WithDuplicateExpiry.class)));
}
+ @Test // DATAMONGO-2112
+ public void resolveExpressionIndexName() {
+
+ List indexDefinitions = prepareMappingContextAndResolveIndexForType(
+ WithIndexNameAsExpression.class);
+
+ Assertions.assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("name", "my1st");
+ }
+
@Document("Zero")
static class IndexOnLevelZero {
@Indexed String indexedProperty;
@@ -341,6 +359,11 @@ public class MongoPersistentEntityIndexResolverUnitTests {
@Indexed(expireAfter = "#{10 + 1 + 's'}") String withTimeout;
}
+ @Document
+ static class WithExpireAfterAsExpressionResultingInDuration {
+ @Indexed(expireAfter = "#{T(java.time.Duration).ofSeconds(100)}") String withTimeout;
+ }
+
@Document
class WithInvalidExpireAfter {
@Indexed(expireAfter = "123ops") String withTimeout;
@@ -350,6 +373,11 @@ public class MongoPersistentEntityIndexResolverUnitTests {
class WithDuplicateExpiry {
@Indexed(expireAfter = "1s", expireAfterSeconds = 2) String withTimeout;
}
+
+ @Document
+ static class WithIndexNameAsExpression {
+ @Indexed(name = "#{'my' + 1 + 'st'}") String spelIndexName;
+ }
}
@Target({ ElementType.FIELD })
@@ -427,6 +455,15 @@ public class MongoPersistentEntityIndexResolverUnitTests {
isBsonObject().containing("name", "my_geo_index_name").containing("bucketSize", 2.0));
}
+ @Test // DATAMONGO-2112
+ public void resolveExpressionIndexNameForGeoIndex() {
+
+ List indexDefinitions = prepareMappingContextAndResolveIndexForType(
+ GeoIndexWithNameAsExpression.class);
+
+ Assertions.assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("name", "my1st");
+ }
+
@Document("Zero")
static class GeoSpatialIndexOnLevelZero {
@GeoSpatialIndexed Point geoIndexedProperty;
@@ -474,6 +511,11 @@ public class MongoPersistentEntityIndexResolverUnitTests {
GeoSpatialIndexType indexType() default GeoSpatialIndexType.GEO_HAYSTACK;
}
+ @Document
+ static class GeoIndexWithNameAsExpression {
+ @GeoSpatialIndexed(name = "#{'my' + 1 + 'st'}") Point spelIndexName;
+ }
+
}
/**
@@ -573,6 +615,26 @@ public class MongoPersistentEntityIndexResolverUnitTests {
.containing("unique", true).containing("background", true));
}
+ @Test // DATAMONGO-2112
+ public void resolveExpressionIndexNameForCompoundIndex() {
+
+ List indexDefinitions = prepareMappingContextAndResolveIndexForType(
+ CompoundIndexWithNameExpression.class);
+
+ Assertions.assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("name", "cmp2name");
+ }
+
+ @Test // DATAMONGO-2112
+ public void resolveExpressionDefForCompoundIndex() {
+
+ List indexDefinitions = prepareMappingContextAndResolveIndexForType(
+ CompoundIndexWithDefExpression.class);
+
+ assertThat(indexDefinitions, hasSize(1));
+ assertIndexPathAndCollection(new String[] { "foo", "bar" }, "compoundIndexWithDefExpression",
+ indexDefinitions.get(0));
+ }
+
@Document("CompoundIndexOnLevelOne")
static class CompoundIndexOnLevelOne {
@@ -637,6 +699,14 @@ public class MongoPersistentEntityIndexResolverUnitTests {
}
+ @Document
+ @CompoundIndex(name = "#{'cmp' + 2 + 'name'}", def = "{'foo': 1, 'bar': -1}")
+ static class CompoundIndexWithNameExpression {}
+
+ @Document
+ @CompoundIndex(def = "#{T(org.bson.Document).parse(\"{ 'foo': 1, 'bar': -1 }\")}")
+ static class CompoundIndexWithDefExpression {}
+
}
public static class TextIndexedResolutionTests {