Skip to content

Commit 8fbaba0

Browse files
christophstroblmp911de
authored andcommitted
DATAMONGO-2112 - Allow SpEL expression to be used for annotated index & geoIndex names as well as compound index definition.
We now also evaluate SpEL expressions for the name of indices as well as the def attribute of the compound index definition. @CompoundIndex(name = "#{'cmp' + 2 + 'name‘}“, def = "#{T(org.bson.Document).parse(\"{ 'foo': 1, 'bar': -1 }\")}") class WithCompoundIndexFromExpression { // … } An expression used for Indexed.expireAfter may now not only return a plain String value with the timeout but also a java.time.Duration. Original pull request: #647.
1 parent 82da802 commit 8fbaba0

File tree

5 files changed

+162
-32
lines changed

5 files changed

+162
-32
lines changed

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

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,29 @@
3636
public @interface CompoundIndex {
3737

3838
/**
39-
* The actual index definition in JSON format. The keys of the JSON document are the fields to be indexed, the values
40-
* define the index direction (1 for ascending, -1 for descending). <br />
39+
* The actual index definition in JSON format or a {@link org.springframework.expression.spel.standard.SpelExpression
40+
* template expression} resolving to either a JSON String or a {@link org.bson.Document}. The keys of the JSON
41+
* document are the fields to be indexed, the values define the index direction (1 for ascending, -1 for descending).
42+
* <br />
4143
* If left empty on nested document, the whole document will be indexed.
4244
*
45+
* <pre>
46+
* <code>
47+
*
48+
* &#64;Document
49+
* &#64;CompoundIndex(def = "{'h1': 1, 'h2': 1}")
50+
* class JsonStringIndexDefinition {
51+
* String h1, h2;
52+
* }
53+
*
54+
* &#64;Document
55+
* &#64;CompoundIndex(def = "#{T(org.bson.Document).parse("{ 'h1': 1, 'h2': 1 }")}")
56+
* class ExpressionIndexDefinition {
57+
* String h1, h2;
58+
* }
59+
* </code>
60+
* </pre>
61+
*
4362
* @return
4463
*/
4564
String def() default "";
@@ -79,7 +98,8 @@
7998
boolean dropDups() default false;
8099

81100
/**
82-
* The name of the index to be created. <br />
101+
* Index name of the index to be created either as plain value or as
102+
* {@link org.springframework.expression.spel.standard.SpelExpression template expression}. <br />
83103
* <br />
84104
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the
85105
* provided name will be prefixed with the path leading to the entity. <br />

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
public @interface GeoSpatialIndexed {
3535

3636
/**
37-
* Index name. <br />
38-
* <br />
37+
* Index name either as plain value or as {@link org.springframework.expression.spel.standard.SpelExpression template
38+
* expression}. <br />
3939
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the
4040
* provided name will be prefixed with the path leading to the entity. <br />
4141
* <br />
@@ -52,6 +52,7 @@
5252
* &#64;Document
5353
* class Hybrid {
5454
* &#64;GeoSpatialIndexed(name="index") Point h1;
55+
* &#64;GeoSpatialIndexed(name="#{&#64;myBean.indexName}") Point h2;
5556
* }
5657
*
5758
* class Nested {
@@ -67,6 +68,7 @@
6768
* db.root.createIndex( { hybrid.h1: "2d" } , { name: "hybrid.index" } )
6869
* db.root.createIndex( { nested.n1: "2d" } , { name: "nested.index" } )
6970
* db.hybrid.createIndex( { h1: "2d" } , { name: "index" } )
71+
* db.hybrid.createIndex( { h2: "2d"} , { name: the value myBean.getIndexName() returned } )
7072
* </code>
7173
* </pre>
7274
*

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@
6565
boolean dropDups() default false;
6666

6767
/**
68-
* Index name. <br />
68+
* Index name either as plain value or as {@link org.springframework.expression.spel.standard.SpelExpression template
69+
* expression}. <br />
6970
* <br />
7071
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the
7172
* provided name will be prefixed with the path leading to the entity. <br />
@@ -83,6 +84,7 @@
8384
* &#64;Document
8485
* class Hybrid {
8586
* &#64;Indexed(name="index") String h1;
87+
* &#64;Indexed(name="#{&#64;myBean.indexName}") String h2;
8688
* }
8789
*
8890
* class Nested {
@@ -98,6 +100,7 @@
98100
* db.root.createIndex( { hybrid.h1: 1 } , { name: "hybrid.index" } )
99101
* db.root.createIndex( { nested.n1: 1 } , { name: "nested.index" } )
100102
* db.hybrid.createIndex( { h1: 1} , { name: "index" } )
103+
* db.hybrid.createIndex( { h2: 1} , { name: the value myBean.getIndexName() returned } )
101104
* </code>
102105
* </pre>
103106
*
@@ -135,7 +138,8 @@
135138
/**
136139
* Alternative for {@link #expireAfterSeconds()} to configure the timeout after which the collection should expire.
137140
* Defaults to an empty String for no expiry. Accepts numeric values followed by their unit of measure (d(ays),
138-
* h(ours), m(inutes), s(seconds)) or a Spring {@literal template expression}.
141+
* h(ours), m(inutes), s(seconds)) or a Spring {@literal template expression}. The expression can result in a a valid
142+
* expiration {@link String} following the conventions already mentioned or a {@link java.time.Duration}.
139143
*
140144
* <pre>
141145
* <code>

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

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.springframework.data.mapping.Association;
4141
import org.springframework.data.mapping.AssociationHandler;
4242
import org.springframework.data.mapping.MappingException;
43+
import org.springframework.data.mapping.PersistentEntity;
4344
import org.springframework.data.mapping.PersistentProperty;
4445
import org.springframework.data.mapping.PropertyHandler;
4546
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.CycleGuard.Path;
@@ -62,6 +63,7 @@
6263
import org.springframework.util.Assert;
6364
import org.springframework.util.ClassUtils;
6465
import org.springframework.util.NumberUtils;
66+
import org.springframework.util.ObjectUtils;
6567
import org.springframework.util.StringUtils;
6668

6769
/**
@@ -356,10 +358,10 @@ protected IndexDefinitionHolder createCompoundIndexDefinition(String dotPath, St
356358
MongoPersistentEntity<?> entity) {
357359

358360
CompoundIndexDefinition indexDefinition = new CompoundIndexDefinition(
359-
resolveCompoundIndexKeyFromStringDefinition(dotPath, index.def()));
361+
resolveCompoundIndexKeyFromStringDefinition(dotPath, index.def(), entity));
360362

361363
if (!index.useGeneratedName()) {
362-
indexDefinition.named(pathAwareIndexName(index.name(), dotPath, null));
364+
indexDefinition.named(pathAwareIndexName(index.name(), dotPath, entity, null));
363365
}
364366

365367
if (index.unique()) {
@@ -377,7 +379,8 @@ protected IndexDefinitionHolder createCompoundIndexDefinition(String dotPath, St
377379
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
378380
}
379381

380-
private org.bson.Document resolveCompoundIndexKeyFromStringDefinition(String dotPath, String keyDefinitionString) {
382+
private org.bson.Document resolveCompoundIndexKeyFromStringDefinition(String dotPath, String keyDefinitionString,
383+
PersistentEntity<?, ?> entity) {
381384

382385
if (!StringUtils.hasText(dotPath) && !StringUtils.hasText(keyDefinitionString)) {
383386
throw new InvalidDataAccessApiUsageException("Cannot create index on root level for empty keys.");
@@ -387,7 +390,12 @@ private org.bson.Document resolveCompoundIndexKeyFromStringDefinition(String dot
387390
return new org.bson.Document(dotPath, 1);
388391
}
389392

390-
org.bson.Document dbo = org.bson.Document.parse(keyDefinitionString);
393+
Object keyDefToUse = evaluatePotentialTemplateExpression(keyDefinitionString,
394+
getEvaluationContextForProperty(entity));
395+
396+
org.bson.Document dbo = (keyDefToUse instanceof org.bson.Document) ? (org.bson.Document) keyDefToUse
397+
: org.bson.Document.parse(ObjectUtils.nullSafeToString(keyDefToUse));
398+
391399
if (!StringUtils.hasText(dotPath)) {
392400
return dbo;
393401
}
@@ -423,7 +431,7 @@ protected IndexDefinitionHolder createIndexDefinition(String dotPath, String col
423431
IndexDirection.ASCENDING.equals(index.direction()) ? Sort.Direction.ASC : Sort.Direction.DESC);
424432

425433
if (!index.useGeneratedName()) {
426-
indexDefinition.named(pathAwareIndexName(index.name(), dotPath, persitentProperty));
434+
indexDefinition.named(pathAwareIndexName(index.name(), dotPath, persitentProperty.getOwner(), persitentProperty));
427435
}
428436

429437
if (index.unique()) {
@@ -446,21 +454,12 @@ protected IndexDefinitionHolder createIndexDefinition(String dotPath, String col
446454

447455
if (index.expireAfterSeconds() >= 0) {
448456
throw new IllegalStateException(String.format(
449-
"@Indexed already defines an expiration timeout of %s sec. via Indexed#expireAfterSeconds. Please make to use either expireAfterSeconds or expireAfter.", index.expireAfterSeconds()));
450-
}
451-
452-
EvaluationContext ctx = getEvaluationContext();
453-
454-
if (persitentProperty.getOwner() instanceof BasicMongoPersistentEntity) {
455-
456-
EvaluationContext contextFromEntity = ((BasicMongoPersistentEntity<?>) persitentProperty.getOwner())
457-
.getEvaluationContext(null);
458-
if (contextFromEntity != null && !EvaluationContextProvider.DEFAULT.equals(contextFromEntity)) {
459-
ctx = contextFromEntity;
460-
}
457+
"@Indexed already defines an expiration timeout of %s sec. via Indexed#expireAfterSeconds. Please make to use either expireAfterSeconds or expireAfter.",
458+
index.expireAfterSeconds()));
461459
}
462460

463-
Duration timeout = computeIndexTimeout(index.expireAfter(), ctx);
461+
Duration timeout = computeIndexTimeout(index.expireAfter(),
462+
getEvaluationContextForProperty(persitentProperty.getOwner()));
464463
if (!timeout.isZero() && !timeout.isNegative()) {
465464
indexDefinition.expire(timeout);
466465
}
@@ -479,6 +478,27 @@ protected EvaluationContext getEvaluationContext() {
479478
return evaluationContextProvider.getEvaluationContext(null);
480479
}
481480

481+
/**
482+
* Get the {@link EvaluationContext} for a given {@link PersistentEntity entity} the default one.
483+
*
484+
* @param persistentEntity can be {@literal null}
485+
* @return
486+
*/
487+
private EvaluationContext getEvaluationContextForProperty(@Nullable PersistentEntity<?, ?> persistentEntity) {
488+
489+
if (persistentEntity == null || !(persistentEntity instanceof BasicMongoPersistentEntity)) {
490+
return getEvaluationContext();
491+
}
492+
493+
EvaluationContext contextFromEntity = ((BasicMongoPersistentEntity<?>) persistentEntity).getEvaluationContext(null);
494+
495+
if (contextFromEntity != null && !EvaluationContextProvider.DEFAULT.equals(contextFromEntity)) {
496+
return contextFromEntity;
497+
}
498+
499+
return getEvaluationContext();
500+
}
501+
482502
/**
483503
* Set the {@link EvaluationContextProvider} used for obtaining the {@link EvaluationContext} used to compute
484504
* {@link org.springframework.expression.spel.standard.SpelExpression expressions}.
@@ -514,17 +534,22 @@ protected IndexDefinitionHolder createGeoSpatialIndexDefinition(String dotPath,
514534
indexDefinition.withMin(index.min()).withMax(index.max());
515535

516536
if (!index.useGeneratedName()) {
517-
indexDefinition.named(pathAwareIndexName(index.name(), dotPath, persistentProperty));
537+
indexDefinition
538+
.named(pathAwareIndexName(index.name(), dotPath, persistentProperty.getOwner(), persistentProperty));
518539
}
519540

520541
indexDefinition.typed(index.type()).withBucketSize(index.bucketSize()).withAdditionalField(index.additionalField());
521542

522543
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
523544
}
524545

525-
private String pathAwareIndexName(String indexName, String dotPath, @Nullable MongoPersistentProperty property) {
546+
private String pathAwareIndexName(String indexName, String dotPath, @Nullable PersistentEntity<?, ?> entity,
547+
@Nullable MongoPersistentProperty property) {
526548

527-
String nameToUse = StringUtils.hasText(indexName) ? indexName : "";
549+
String nameToUse = StringUtils.hasText(indexName)
550+
? ObjectUtils
551+
.nullSafeToString(evaluatePotentialTemplateExpression(indexName, getEvaluationContextForProperty(entity)))
552+
: "";
528553

529554
if (!StringUtils.hasText(dotPath) || (property != null && dotPath.equals(property.getFieldName()))) {
530555
return StringUtils.hasText(nameToUse) ? nameToUse : dotPath;
@@ -582,7 +607,17 @@ private void resolveAndAddIndexesForAssociation(Association<MongoPersistentPrope
582607
*/
583608
private static Duration computeIndexTimeout(String timeoutValue, EvaluationContext evaluationContext) {
584609

585-
String val = evaluatePotentialTemplateExpression(timeoutValue, evaluationContext);
610+
Object evaluatedTimeout = evaluatePotentialTemplateExpression(timeoutValue, evaluationContext);
611+
612+
if (evaluatedTimeout == null) {
613+
return Duration.ZERO;
614+
}
615+
616+
if (evaluatedTimeout instanceof Duration) {
617+
return (Duration) evaluatedTimeout;
618+
}
619+
620+
String val = evaluatedTimeout.toString();
586621

587622
if (val == null) {
588623
return Duration.ZERO;
@@ -611,15 +646,14 @@ private static Duration computeIndexTimeout(String timeoutValue, EvaluationConte
611646
}
612647

613648
@Nullable
614-
private static String evaluatePotentialTemplateExpression(String value, EvaluationContext evaluationContext) {
649+
private static Object evaluatePotentialTemplateExpression(String value, EvaluationContext evaluationContext) {
615650

616651
Expression expression = PARSER.parseExpression(value, ParserContext.TEMPLATE_EXPRESSION);
617652
if (expression instanceof LiteralExpression) {
618653
return value;
619654
}
620655

621-
return expression.getValue(evaluationContext, String.class);
622-
656+
return expression.getValue(evaluationContext, Object.class);
623657
}
624658

625659
/**

0 commit comments

Comments
 (0)