diff --git a/pom.xml b/pom.xml
index d0c3937c5b..3ac689c387 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.2.0.BUILD-SNAPSHOT
+ 2.2.0.DATAMONGO-2112-SNAPSHOT
pom
Spring Data MongoDB
diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml
index bb7d9f03cc..225875429d 100644
--- a/spring-data-mongodb-benchmarks/pom.xml
+++ b/spring-data-mongodb-benchmarks/pom.xml
@@ -7,7 +7,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.2.0.BUILD-SNAPSHOT
+ 2.2.0.DATAMONGO-2112-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml
index b32dcba387..46337578f2 100644
--- a/spring-data-mongodb-distribution/pom.xml
+++ b/spring-data-mongodb-distribution/pom.xml
@@ -14,7 +14,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.2.0.BUILD-SNAPSHOT
+ 2.2.0.DATAMONGO-2112-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index b611cf01a8..bc5377e1c3 100644
--- a/spring-data-mongodb/pom.xml
+++ b/spring-data-mongodb/pom.xml
@@ -11,7 +11,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.2.0.BUILD-SNAPSHOT
+ 2.2.0.DATAMONGO-2112-SNAPSHOT
../pom.xml
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 f58f0d8095..0fbd88d728 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 @@
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 @@
boolean dropDups() default false;
/**
- * The name of the index to be created.
+ * Index name of the index to be created either as plain value or as
+ * {@link org.springframework.expression.spel.standard.SpelExpression template expression}.
*
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the
* provided name will be prefixed with the path leading to the entity.
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/DurationStyle.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/DurationStyle.java
new file mode 100644
index 0000000000..c7f702691d
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/DurationStyle.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.mongodb.core.index;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Duration format styles.
+ *
+ * Fork of {@code org.springframework.boot.convert.DurationStyle}.
+ *
+ * @author Phillip Webb
+ * @since 2.2
+ */
+enum DurationStyle {
+
+ /**
+ * Simple formatting, for example '1s'.
+ */
+ SIMPLE("^([\\+\\-]?\\d+)([a-zA-Z]{0,2})$") {
+
+ @Override
+ public Duration parse(String value, @Nullable ChronoUnit unit) {
+ try {
+ Matcher matcher = matcher(value);
+ Assert.state(matcher.matches(), "Does not match simple duration pattern");
+ String suffix = matcher.group(2);
+ return (StringUtils.hasLength(suffix) ? Unit.fromSuffix(suffix) : Unit.fromChronoUnit(unit))
+ .parse(matcher.group(1));
+ } catch (Exception ex) {
+ throw new IllegalArgumentException("'" + value + "' is not a valid simple duration", ex);
+ }
+ }
+ },
+
+ /**
+ * ISO-8601 formatting.
+ */
+ ISO8601("^[\\+\\-]?P.*$") {
+
+ @Override
+ public Duration parse(String value, @Nullable ChronoUnit unit) {
+ try {
+ return Duration.parse(value);
+ } catch (Exception ex) {
+ throw new IllegalArgumentException("'" + value + "' is not a valid ISO-8601 duration", ex);
+ }
+ }
+ };
+
+ private final Pattern pattern;
+
+ DurationStyle(String pattern) {
+ this.pattern = Pattern.compile(pattern);
+ }
+
+ protected final boolean matches(String value) {
+ return this.pattern.matcher(value).matches();
+ }
+
+ protected final Matcher matcher(String value) {
+ return this.pattern.matcher(value);
+ }
+
+ /**
+ * Parse the given value to a duration.
+ *
+ * @param value the value to parse
+ * @return a duration
+ */
+ public Duration parse(String value) {
+ return parse(value, null);
+ }
+
+ /**
+ * Parse the given value to a duration.
+ *
+ * @param value the value to parse
+ * @param unit the duration unit to use if the value doesn't specify one ({@code null} will default to ms)
+ * @return a duration
+ */
+ public abstract Duration parse(String value, @Nullable ChronoUnit unit);
+
+ /**
+ * Detect the style then parse the value to return a duration.
+ *
+ * @param value the value to parse
+ * @return the parsed duration
+ * @throws IllegalStateException if the value is not a known style or cannot be parsed
+ */
+ public static Duration detectAndParse(String value) {
+ return detectAndParse(value, null);
+ }
+
+ /**
+ * Detect the style then parse the value to return a duration.
+ *
+ * @param value the value to parse
+ * @param unit the duration unit to use if the value doesn't specify one ({@code null} will default to ms)
+ * @return the parsed duration
+ * @throws IllegalStateException if the value is not a known style or cannot be parsed
+ */
+ public static Duration detectAndParse(String value, @Nullable ChronoUnit unit) {
+ return detect(value).parse(value, unit);
+ }
+
+ /**
+ * Detect the style from the given source value.
+ *
+ * @param value the source value
+ * @return the duration style
+ * @throws IllegalStateException if the value is not a known style
+ */
+ public static DurationStyle detect(String value) {
+ Assert.notNull(value, "Value must not be null");
+ for (DurationStyle candidate : values()) {
+ if (candidate.matches(value)) {
+ return candidate;
+ }
+ }
+ throw new IllegalArgumentException("'" + value + "' is not a valid duration");
+ }
+
+ /**
+ * Units that we support.
+ */
+ enum Unit {
+
+ /**
+ * Milliseconds.
+ */
+ MILLIS(ChronoUnit.MILLIS, "ms", Duration::toMillis),
+
+ /**
+ * Seconds.
+ */
+ SECONDS(ChronoUnit.SECONDS, "s", Duration::getSeconds),
+
+ /**
+ * Minutes.
+ */
+ MINUTES(ChronoUnit.MINUTES, "m", Duration::toMinutes),
+
+ /**
+ * Hours.
+ */
+ HOURS(ChronoUnit.HOURS, "h", Duration::toHours),
+
+ /**
+ * Days.
+ */
+ DAYS(ChronoUnit.DAYS, "d", Duration::toDays);
+
+ private final ChronoUnit chronoUnit;
+
+ private final String suffix;
+
+ private Function longValue;
+
+ Unit(ChronoUnit chronoUnit, String suffix, Function toUnit) {
+ this.chronoUnit = chronoUnit;
+ this.suffix = suffix;
+ this.longValue = toUnit;
+ }
+
+ public Duration parse(String value) {
+ return Duration.of(Long.valueOf(value), this.chronoUnit);
+ }
+
+ public long longValue(Duration value) {
+ return this.longValue.apply(value);
+ }
+
+ public static Unit fromChronoUnit(ChronoUnit chronoUnit) {
+ if (chronoUnit == null) {
+ return Unit.MILLIS;
+ }
+ for (Unit candidate : values()) {
+ if (candidate.chronoUnit == chronoUnit) {
+ return candidate;
+ }
+ }
+ throw new IllegalArgumentException("Unknown unit " + chronoUnit);
+ }
+
+ public static Unit fromSuffix(String suffix) {
+ for (Unit candidate : values()) {
+ if (candidate.suffix.equalsIgnoreCase(suffix)) {
+ return candidate;
+ }
+ }
+ throw new IllegalArgumentException("Unknown unit '" + suffix + "'");
+ }
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeoSpatialIndexed.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeoSpatialIndexed.java
index e7e51c9059..506e1c7428 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeoSpatialIndexed.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeoSpatialIndexed.java
@@ -34,8 +34,8 @@
public @interface GeoSpatialIndexed {
/**
- * Index name.
- *
+ * Index name either as plain value or as {@link org.springframework.expression.spel.standard.SpelExpression template
+ * expression}.
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the
* provided name will be prefixed with the path leading to the entity.
*
@@ -52,6 +52,7 @@
* @Document
* class Hybrid {
* @GeoSpatialIndexed(name="index") Point h1;
+ * @GeoSpatialIndexed(name="#{@myBean.indexName}") Point h2;
* }
*
* class Nested {
@@ -67,6 +68,7 @@
* db.root.createIndex( { hybrid.h1: "2d" } , { name: "hybrid.index" } )
* db.root.createIndex( { nested.n1: "2d" } , { name: "nested.index" } )
* db.hybrid.createIndex( { h1: "2d" } , { name: "index" } )
+ * db.hybrid.createIndex( { h2: "2d"} , { name: the value myBean.getIndexName() returned } )
*
*
*
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java
index 747202e80b..3f7b1a4cd0 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java
@@ -15,6 +15,7 @@
*/
package org.springframework.data.mongodb.core.index;
+import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
@@ -116,6 +117,20 @@ public Index expire(long value) {
return expire(value, TimeUnit.SECONDS);
}
+ /**
+ * Specifies the TTL.
+ *
+ * @param timeout must not be {@literal null}.
+ * @return this.
+ * @throws IllegalArgumentException if given {@literal timeout} is {@literal null}.
+ * @since 2.2
+ */
+ public Index expire(Duration timeout) {
+
+ Assert.notNull(timeout, "Timeout must not be null!");
+ return expire(timeout.getSeconds());
+ }
+
/**
* Specifies TTL with given {@link TimeUnit}.
*
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Indexed.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Indexed.java
index 1440386618..5549260f66 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Indexed.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Indexed.java
@@ -30,6 +30,7 @@
* @author Thomas Darimont
* @author Christoph Strobl
* @author Jordi Llach
+ * @author Mark Paluch
*/
@Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@@ -65,7 +66,8 @@
boolean dropDups() default false;
/**
- * Index name.
+ * Index name either as plain value or as {@link org.springframework.expression.spel.standard.SpelExpression template
+ * expression}.
*
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the
* provided name will be prefixed with the path leading to the entity.
@@ -83,6 +85,7 @@
* @Document
* class Hybrid {
* @Indexed(name="index") String h1;
+ * @Indexed(name="#{@myBean.indexName}") String h2;
* }
*
* class Nested {
@@ -98,6 +101,7 @@
* db.root.createIndex( { hybrid.h1: 1 } , { name: "hybrid.index" } )
* db.root.createIndex( { nested.n1: 1 } , { name: "nested.index" } )
* db.hybrid.createIndex( { h1: 1} , { name: "index" } )
+ * db.hybrid.createIndex( { h2: 1} , { name: the value myBean.getIndexName() returned } )
*
*
*
@@ -131,4 +135,34 @@
* "https://docs.mongodb.org/manual/tutorial/expire-data/">https://docs.mongodb.org/manual/tutorial/expire-data/
*/
int expireAfterSeconds() default -1;
+
+ /**
+ * Alternative for {@link #expireAfterSeconds()} to configure the timeout after which the document should expire.
+ * Defaults to an empty {@link String} for no expiry. Accepts numeric values followed by their unit of measure:
+ *
+ * d : Days
+ * h : Hours
+ * m : Minutes
+ * s : Seconds
+ * Alternatively: A Spring {@literal template expression}. The expression can result in a
+ * {@link java.time.Duration} or a valid expiration {@link String} according to the already mentioned
+ * conventions.
+ *
+ * Supports ISO-8601 style.
+ *
+ *
+ *
+ * @Indexed(expireAfter = "10s") String expireAfterTenSeconds;
+ *
+ * @Indexed(expireAfter = "1d") String expireAfterOneDay;
+ *
+ * @Indexed(expireAfter = "P2D") String expireAfterTwoDays;
+ *
+ * @Indexed(expireAfter = "#{@mySpringBean.timeout}") String expireAfterTimeoutObtainedFromSpringBean;
+ *
+ *
+ * @return empty by default.
+ * @since 2.2
+ */
+ String expireAfter() default "";
}
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 af56d06b0d..041241c0fc 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
@@ -19,6 +19,7 @@
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -32,25 +33,35 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+
import org.springframework.dao.InvalidDataAccessApiUsageException;
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;
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.TextIndexIncludeOptions.IncludeStrategy;
import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexDefinitionBuilder;
import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexedFieldSpec;
+import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
+import org.springframework.data.spel.EvaluationContextProvider;
import org.springframework.data.util.TypeInformation;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ParserContext;
+import org.springframework.expression.common.LiteralExpression;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@@ -68,8 +79,10 @@
public class MongoPersistentEntityIndexResolver implements IndexResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(MongoPersistentEntityIndexResolver.class);
+ private static final SpelExpressionParser PARSER = new SpelExpressionParser();
private final MongoMappingContext mappingContext;
+ private EvaluationContextProvider evaluationContextProvider = EvaluationContextProvider.DEFAULT;
/**
* Create new {@link MongoPersistentEntityIndexResolver}.
@@ -337,15 +350,14 @@ protected List createCompoundIndexDefinitions(String dotP
return indexDefinitions;
}
- @SuppressWarnings("deprecation")
protected IndexDefinitionHolder createCompoundIndexDefinition(String dotPath, String collection, CompoundIndex index,
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()) {
@@ -363,7 +375,8 @@ protected IndexDefinitionHolder createCompoundIndexDefinition(String dotPath, St
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.");
@@ -373,7 +386,11 @@ private org.bson.Document resolveCompoundIndexKeyFromStringDefinition(String dot
return new org.bson.Document(dotPath, 1);
}
- org.bson.Document dbo = org.bson.Document.parse(keyDefinitionString);
+ Object keyDefToUse = evaluate(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;
}
@@ -409,7 +426,7 @@ protected IndexDefinitionHolder createIndexDefinition(String dotPath, String col
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()) {
@@ -428,9 +445,66 @@ protected IndexDefinitionHolder createIndexDefinition(String dotPath, String col
indexDefinition.expire(index.expireAfterSeconds(), TimeUnit.SECONDS);
}
+ if (StringUtils.hasText(index.expireAfter())) {
+
+ if (index.expireAfterSeconds() >= 0) {
+ throw new IllegalStateException(String.format(
+ "@Indexed already defines an expiration timeout of %s seconds via Indexed#expireAfterSeconds. Please make to use either expireAfterSeconds or expireAfter.",
+ index.expireAfterSeconds()));
+ }
+
+ Duration timeout = computeIndexTimeout(index.expireAfter(),
+ getEvaluationContextForProperty(persitentProperty.getOwner()));
+ if (!timeout.isZero() && !timeout.isNegative()) {
+ indexDefinition.expire(timeout);
+ }
+ }
+
return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
}
+ /**
+ * Get the default {@link EvaluationContext}.
+ *
+ * @return never {@literal null}.
+ * @since 2.2
+ */
+ protected EvaluationContext getEvaluationContext() {
+ 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}.
+ *
+ * @param evaluationContextProvider must not be {@literal null}.
+ * @since 2.2
+ */
+ public void setEvaluationContextProvider(EvaluationContextProvider evaluationContextProvider) {
+ this.evaluationContextProvider = evaluationContextProvider;
+ }
+
/**
* Creates {@link IndexDefinition} wrapped in {@link IndexDefinitionHolder} out of {@link GeoSpatialIndexed} for
* {@link MongoPersistentProperty}.
@@ -455,7 +529,8 @@ protected IndexDefinitionHolder createGeoSpatialIndexDefinition(String dotPath,
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());
@@ -463,9 +538,18 @@ protected IndexDefinitionHolder createGeoSpatialIndexDefinition(String dotPath,
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 = "";
+ if (StringUtils.hasText(indexName)) {
- String nameToUse = StringUtils.hasText(indexName) ? indexName : "";
+ Object result = evaluate(indexName, getEvaluationContextForProperty(entity));
+
+ if (result != null) {
+ nameToUse = ObjectUtils.nullSafeToString(result);
+ }
+ }
if (!StringUtils.hasText(dotPath) || (property != null && dotPath.equals(property.getFieldName()))) {
return StringUtils.hasText(nameToUse) ? nameToUse : dotPath;
@@ -511,6 +595,48 @@ private void resolveAndAddIndexesForAssociation(Association indexInfo = operations.execute("withSpelIndexTimeout", collection -> {
+
+ return collection.listIndexes(org.bson.Document.class).into(new ArrayList<>()) //
+ .stream() //
+ .filter(it -> it.get("name").equals("someString")) //
+ .findFirst();
+ });
+
+ assertThat(indexInfo).isPresent();
+ assertThat(indexInfo.get()).containsEntry("expireAfterSeconds", 11L);
}
@Target({ ElementType.FIELD })
@@ -110,6 +156,17 @@ class IndexedPerson {
@Field("_lastname") @IndexedFieldAnnotation String lastname;
}
+ @RequiredArgsConstructor
+ @Getter
+ static class TimeoutResolver {
+ final String timeout;
+ }
+
+ @Document
+ class WithSpelIndexTimeout {
+ @Indexed(expireAfter = "#{@myTimeoutResolver?.timeout}") String someString;
+ }
+
/**
* Returns whether an index with the given name exists for the given entity type.
*
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 e4693df31d..7bac84fe01 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
@@ -15,10 +15,8 @@
*/
package org.springframework.data.mongodb.core.index;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
-import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
+import static org.springframework.data.mongodb.test.util.Assertions.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -31,6 +29,7 @@
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
+
import org.springframework.core.annotation.AliasFor;
import org.springframework.data.annotation.Id;
import org.springframework.data.geo.Point;
@@ -52,12 +51,15 @@
import org.springframework.data.util.ClassTypeInformation;
/**
+ * Tests for {@link MongoPersistentEntityIndexResolver}.
+ *
* @author Christoph Strobl
* @author Mark Paluch
*/
@RunWith(Suite.class)
@SuiteClasses({ IndexResolutionTests.class, GeoSpatialIndexResolutionTests.class, CompoundIndexResolutionTests.class,
TextIndexedResolutionTests.class, MixedIndexResolutionTests.class })
+@SuppressWarnings("unused")
public class MongoPersistentEntityIndexResolverUnitTests {
/**
@@ -74,7 +76,7 @@ public void indexPathOnLevelZeroIsResolvedCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
IndexOnLevelZero.class);
- assertThat(indexDefinitions, hasSize(1));
+ assertThat(indexDefinitions).hasSize(1);
assertIndexPathAndCollection("indexedProperty", "Zero", indexDefinitions.get(0));
}
@@ -83,7 +85,7 @@ public void indexPathOnLevelOneIsResolvedCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelOne.class);
- assertThat(indexDefinitions, hasSize(1));
+ assertThat(indexDefinitions).hasSize(1);
assertIndexPathAndCollection("zero.indexedProperty", "One", indexDefinitions.get(0));
}
@@ -94,7 +96,7 @@ public void shouldResolveIndexViaClass() {
IndexResolver indexResolver = IndexResolver.create(mappingContext);
Iterable extends IndexDefinition> definitions = indexResolver.resolveIndexFor(IndexOnLevelOne.class);
- assertThat(definitions.iterator().hasNext(), is(true));
+ assertThat(definitions).isNotEmpty();
}
@Test // DATAMONGO-899
@@ -102,7 +104,7 @@ public void deeplyNestedIndexPathIsResolvedCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelTwo.class);
- assertThat(indexDefinitions, hasSize(1));
+ assertThat(indexDefinitions).hasSize(1);
assertIndexPathAndCollection("one.zero.indexedProperty", "Two", indexDefinitions.get(0));
}
@@ -112,7 +114,7 @@ public void resolvesIndexPathNameForNamedPropertiesCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
IndexOnLevelOneWithExplicitlyNamedField.class);
- assertThat(indexDefinitions, hasSize(1));
+ assertThat(indexDefinitions).hasSize(1);
assertIndexPathAndCollection("customZero.customFieldName", "indexOnLevelOneWithExplicitlyNamedField",
indexDefinitions.get(0));
}
@@ -124,7 +126,7 @@ public void resolvesIndexDefinitionCorrectly() {
IndexOnLevelZero.class);
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
- assertThat(indexDefinition.getIndexOptions(), equalTo(new org.bson.Document().append("name", "indexedProperty")));
+ assertThat(indexDefinition.getIndexOptions()).isEqualTo(new org.bson.Document("name", "indexedProperty"));
}
@Test // DATAMONGO-899
@@ -134,8 +136,8 @@ public void resolvesIndexDefinitionOptionsCorrectly() {
WithOptionsOnIndexedProperty.class);
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
- assertThat(indexDefinition.getIndexOptions(), equalTo(new org.bson.Document().append("name", "indexedProperty")
- .append("unique", true).append("sparse", true).append("background", true).append("expireAfterSeconds", 10L)));
+ assertThat(indexDefinition.getIndexOptions()).isEqualTo(new org.bson.Document().append("name", "indexedProperty")
+ .append("unique", true).append("sparse", true).append("background", true).append("expireAfterSeconds", 10L));
}
@Test // DATAMONGO-1297
@@ -143,9 +145,9 @@ public void resolvesIndexOnDbrefWhenDefined() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(WithDbRef.class);
- assertThat(indexDefinitions, hasSize(1));
- assertThat(indexDefinitions.get(0).getCollection(), equalTo("withDbRef"));
- assertThat(indexDefinitions.get(0).getIndexKeys(), equalTo(new org.bson.Document().append("indexedDbRef", 1)));
+ assertThat(indexDefinitions).hasSize(1);
+ assertThat(indexDefinitions.get(0).getCollection()).isEqualTo("withDbRef");
+ assertThat(indexDefinitions.get(0).getIndexKeys()).isEqualTo(new org.bson.Document("indexedDbRef", 1));
}
@Test // DATAMONGO-1297
@@ -154,10 +156,9 @@ public void resolvesIndexOnDbrefWhenDefinedOnNestedElement() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
WrapperOfWithDbRef.class);
- assertThat(indexDefinitions, hasSize(1));
- assertThat(indexDefinitions.get(0).getCollection(), equalTo("wrapperOfWithDbRef"));
- assertThat(indexDefinitions.get(0).getIndexKeys(),
- equalTo(new org.bson.Document().append("nested.indexedDbRef", 1)));
+ assertThat(indexDefinitions).hasSize(1);
+ assertThat(indexDefinitions.get(0).getCollection()).isEqualTo("wrapperOfWithDbRef");
+ assertThat(indexDefinitions.get(0).getIndexKeys()).isEqualTo(new org.bson.Document("nested.indexedDbRef", 1));
}
@Test // DATAMONGO-1163
@@ -166,9 +167,9 @@ public void resolveIndexDefinitionInMetaAnnotatedFields() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
IndexOnMetaAnnotatedField.class);
- assertThat(indexDefinitions, hasSize(1));
- assertThat(indexDefinitions.get(0).getCollection(), equalTo("indexOnMetaAnnotatedField"));
- assertThat(indexDefinitions.get(0).getIndexOptions(), equalTo(new org.bson.Document().append("name", "_name")));
+ assertThat(indexDefinitions).hasSize(1);
+ assertThat(indexDefinitions.get(0).getCollection()).isEqualTo("indexOnMetaAnnotatedField");
+ assertThat(indexDefinitions.get(0).getIndexOptions()).isEqualTo(new org.bson.Document("name", "_name"));
}
@Test // DATAMONGO-1373
@@ -177,13 +178,15 @@ public void resolveIndexDefinitionInComposedAnnotatedFields() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
IndexedDocumentWithComposedAnnotations.class);
- assertThat(indexDefinitions, hasSize(2));
+ assertThat(indexDefinitions).hasSize(2);
IndexDefinitionHolder indexDefinitionHolder = indexDefinitions.get(1);
- assertThat(indexDefinitionHolder.getIndexKeys(), isBsonObject().containing("fieldWithMyIndexName", 1));
- assertThat(indexDefinitionHolder.getIndexOptions(),
- isBsonObject().containing("sparse", true).containing("unique", true).containing("name", "my_index_name"));
+ assertThat(indexDefinitionHolder.getIndexKeys()).containsEntry("fieldWithMyIndexName", 1);
+ assertThat(indexDefinitionHolder.getIndexOptions()) //
+ .containsEntry("sparse", true) //
+ .containsEntry("unique", true) //
+ .containsEntry("name", "my_index_name");
}
@Test // DATAMONGO-1373
@@ -192,13 +195,80 @@ public void resolveIndexDefinitionInCustomComposedAnnotatedFields() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
IndexedDocumentWithComposedAnnotations.class);
- assertThat(indexDefinitions, hasSize(2));
+ assertThat(indexDefinitions).hasSize(2);
IndexDefinitionHolder indexDefinitionHolder = indexDefinitions.get(0);
- assertThat(indexDefinitionHolder.getIndexKeys(), isBsonObject().containing("fieldWithDifferentIndexName", 1));
- assertThat(indexDefinitionHolder.getIndexOptions(),
- isBsonObject().containing("sparse", true).containing("name", "different_name").notContaining("unique"));
+ assertThat(indexDefinitionHolder.getIndexKeys()).containsEntry("fieldWithDifferentIndexName", 1);
+ assertThat(indexDefinitionHolder.getIndexOptions()) //
+ .containsEntry("sparse", true) //
+ .containsEntry("name", "different_name") //
+ .doesNotContainKey("unique");
+ }
+
+ @Test // DATAMONGO-2112
+ public void shouldResolveTimeoutFromString() {
+
+ List indexDefinitions = prepareMappingContextAndResolveIndexForType(
+ WithExpireAfterAsPlainString.class);
+
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("expireAfterSeconds", 600L);
+ }
+
+ @Test // DATAMONGO-2112
+ public void shouldResolveTimeoutFromIso8601String() {
+
+ List indexDefinitions = prepareMappingContextAndResolveIndexForType(
+ WithIso8601Style.class);
+
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("expireAfterSeconds", 86400L);
+ }
+
+ @Test // DATAMONGO-2112
+ public void shouldResolveTimeoutFromExpression() {
+
+ List indexDefinitions = prepareMappingContextAndResolveIndexForType(
+ WithExpireAfterAsExpression.class);
+
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("expireAfterSeconds", 11L);
+ }
+
+ @Test // DATAMONGO-2112
+ public void shouldResolveTimeoutFromExpressionReturningDuration() {
+
+ List indexDefinitions = prepareMappingContextAndResolveIndexForType(
+ WithExpireAfterAsExpressionResultingInDuration.class);
+
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("expireAfterSeconds", 100L);
+ }
+
+ @Test // DATAMONGO-2112
+ public void shouldErrorOnInvalidTimeoutExpression() {
+
+ MongoMappingContext mappingContext = prepareMappingContext(WithInvalidExpireAfter.class);
+ MongoPersistentEntityIndexResolver indexResolver = new MongoPersistentEntityIndexResolver(mappingContext);
+
+ assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> indexResolver
+ .resolveIndexForEntity(mappingContext.getRequiredPersistentEntity(WithInvalidExpireAfter.class)));
+ }
+
+ @Test // DATAMONGO-2112
+ public void shouldErrorOnDuplicateTimeoutExpression() {
+
+ MongoMappingContext mappingContext = prepareMappingContext(WithDuplicateExpiry.class);
+ MongoPersistentEntityIndexResolver indexResolver = new MongoPersistentEntityIndexResolver(mappingContext);
+
+ assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> indexResolver
+ .resolveIndexForEntity(mappingContext.getRequiredPersistentEntity(WithDuplicateExpiry.class)));
+ }
+
+ @Test // DATAMONGO-2112
+ public void resolveExpressionIndexName() {
+
+ List indexDefinitions = prepareMappingContextAndResolveIndexForType(
+ WithIndexNameAsExpression.class);
+
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("name", "my1st");
}
@Document("Zero")
@@ -264,13 +334,13 @@ static class IndexedDocumentWithComposedAnnotations {
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
@ComposedIndexedAnnotation(indexName = "different_name", beUnique = false)
- static @interface CustomIndexedAnnotation {
+ @interface CustomIndexedAnnotation {
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Indexed
- static @interface ComposedIndexedAnnotation {
+ @interface ComposedIndexedAnnotation {
@AliasFor(annotation = Indexed.class, attribute = "unique")
boolean beUnique() default true;
@@ -285,18 +355,52 @@ static class IndexedDocumentWithComposedAnnotations {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@org.springframework.data.mongodb.core.mapping.Field
- static @interface ComposedFieldAnnotation {
+ @interface ComposedFieldAnnotation {
@AliasFor(annotation = org.springframework.data.mongodb.core.mapping.Field.class, attribute = "value")
String name() default "_id";
}
+
+ @Document
+ static class WithExpireAfterAsPlainString {
+ @Indexed(expireAfter = "10m") String withTimeout;
+ }
+
+ @Document
+ class WithIso8601Style {
+ @Indexed(expireAfter = "P1D") String withTimeout;
+ }
+
+ @Document
+ static class WithExpireAfterAsExpression {
+ @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;
+ }
+
+ @Document
+ class WithDuplicateExpiry {
+ @Indexed(expireAfter = "1s", expireAfterSeconds = 2) String withTimeout;
+ }
+
+ @Document
+ static class WithIndexNameAsExpression {
+ @Indexed(name = "#{'my' + 1 + 'st'}") String spelIndexName;
+ }
}
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Indexed
@interface IndexedFieldAnnotation {
-
}
@Document
@@ -317,7 +421,7 @@ public void geoSpatialIndexPathOnLevelZeroIsResolvedCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
GeoSpatialIndexOnLevelZero.class);
- assertThat(indexDefinitions, hasSize(1));
+ assertThat(indexDefinitions).hasSize(1);
assertIndexPathAndCollection("geoIndexedProperty", "Zero", indexDefinitions.get(0));
}
@@ -327,7 +431,7 @@ public void geoSpatialIndexPathOnLevelOneIsResolvedCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
GeoSpatialIndexOnLevelOne.class);
- assertThat(indexDefinitions, hasSize(1));
+ assertThat(indexDefinitions).hasSize(1);
assertIndexPathAndCollection("zero.geoIndexedProperty", "One", indexDefinitions.get(0));
}
@@ -337,7 +441,7 @@ public void depplyNestedGeoSpatialIndexPathIsResolvedCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
GeoSpatialIndexOnLevelTwo.class);
- assertThat(indexDefinitions, hasSize(1));
+ assertThat(indexDefinitions).hasSize(1);
assertIndexPathAndCollection("one.zero.geoIndexedProperty", "Two", indexDefinitions.get(0));
}
@@ -349,8 +453,8 @@ public void resolvesIndexDefinitionOptionsCorrectly() {
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
- assertThat(indexDefinition.getIndexOptions(), equalTo(
- new org.bson.Document().append("name", "location").append("min", 1).append("max", 100).append("bits", 2)));
+ assertThat(indexDefinition.getIndexOptions()).isEqualTo(
+ new org.bson.Document().append("name", "location").append("min", 1).append("max", 100).append("bits", 2));
}
@Test // DATAMONGO-1373
@@ -361,10 +465,19 @@ public void resolvesComposedAnnotationIndexDefinitionOptionsCorrectly() {
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
- assertThat(indexDefinition.getIndexKeys(),
- isBsonObject().containing("location", "geoHaystack").containing("What light?", 1));
- assertThat(indexDefinition.getIndexOptions(),
- isBsonObject().containing("name", "my_geo_index_name").containing("bucketSize", 2.0));
+ assertThat(indexDefinition.getIndexKeys()).containsEntry("location", "geoHaystack").containsEntry("What light?",
+ 1);
+ assertThat(indexDefinition.getIndexOptions()).containsEntry("name", "my_geo_index_name")
+ .containsEntry("bucketSize", 2.0);
+ }
+
+ @Test // DATAMONGO-2112
+ public void resolveExpressionIndexNameForGeoIndex() {
+
+ List indexDefinitions = prepareMappingContextAndResolveIndexForType(
+ GeoIndexWithNameAsExpression.class);
+
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("name", "my1st");
}
@Document("Zero")
@@ -414,6 +527,11 @@ static class GeoSpatialIndexedDocumentWithComposedAnnotation {
GeoSpatialIndexType indexType() default GeoSpatialIndexType.GEO_HAYSTACK;
}
+ @Document
+ static class GeoIndexWithNameAsExpression {
+ @GeoSpatialIndexed(name = "#{'my' + 1 + 'st'}") Point spelIndexName;
+ }
+
}
/**
@@ -429,7 +547,7 @@ public void compoundIndexPathOnLevelZeroIsResolvedCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
CompoundIndexOnLevelZero.class);
- assertThat(indexDefinitions, hasSize(1));
+ assertThat(indexDefinitions).hasSize(1);
assertIndexPathAndCollection(new String[] { "foo", "bar" }, "CompoundIndexOnLevelZero", indexDefinitions.get(0));
}
@@ -440,9 +558,9 @@ public void compoundIndexOptionsResolvedCorrectly() {
CompoundIndexOnLevelZero.class);
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
- assertThat(indexDefinition.getIndexOptions(), equalTo(new org.bson.Document().append("name", "compound_index")
- .append("unique", true).append("sparse", true).append("background", true)));
- assertThat(indexDefinition.getIndexKeys(), equalTo(new org.bson.Document().append("foo", 1).append("bar", -1)));
+ assertThat(indexDefinition.getIndexOptions()).isEqualTo(new org.bson.Document("name", "compound_index")
+ .append("unique", true).append("sparse", true).append("background", true));
+ assertThat(indexDefinition.getIndexKeys()).isEqualTo(new org.bson.Document().append("foo", 1).append("bar", -1));
}
@Test // DATAMONGO-909
@@ -452,9 +570,9 @@ public void compoundIndexOnSuperClassResolvedCorrectly() {
IndexDefinedOnSuperClass.class);
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
- assertThat(indexDefinition.getIndexOptions(), equalTo(new org.bson.Document().append("name", "compound_index")
- .append("unique", true).append("sparse", true).append("background", true)));
- assertThat(indexDefinition.getIndexKeys(), equalTo(new org.bson.Document().append("foo", 1).append("bar", -1)));
+ assertThat(indexDefinition.getIndexOptions()).isEqualTo(new org.bson.Document().append("name", "compound_index")
+ .append("unique", true).append("sparse", true).append("background", true));
+ assertThat(indexDefinition.getIndexKeys()).isEqualTo(new org.bson.Document().append("foo", 1).append("bar", -1));
}
@Test // DATAMONGO-827
@@ -464,9 +582,9 @@ public void compoundIndexDoesNotSpecifyNameWhenUsingGenerateName() {
ComountIndexWithAutogeneratedName.class);
IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition();
- assertThat(indexDefinition.getIndexOptions(),
- equalTo(new org.bson.Document().append("unique", true).append("sparse", true).append("background", true)));
- assertThat(indexDefinition.getIndexKeys(), equalTo(new org.bson.Document().append("foo", 1).append("bar", -1)));
+ assertThat(indexDefinition.getIndexOptions())
+ .isEqualTo(new org.bson.Document().append("unique", true).append("sparse", true).append("background", true));
+ assertThat(indexDefinition.getIndexKeys()).isEqualTo(new org.bson.Document().append("foo", 1).append("bar", -1));
}
@Test // DATAMONGO-929
@@ -475,7 +593,7 @@ public void compoundIndexPathOnLevelOneIsResolvedCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
CompoundIndexOnLevelOne.class);
- assertThat(indexDefinitions, hasSize(1));
+ assertThat(indexDefinitions).hasSize(1);
assertIndexPathAndCollection(new String[] { "zero.foo", "zero.bar" }, "CompoundIndexOnLevelOne",
indexDefinitions.get(0));
}
@@ -486,7 +604,7 @@ public void emptyCompoundIndexPathOnLevelOneIsResolvedCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
CompoundIndexOnLevelOneWithEmptyIndexDefinition.class);
- assertThat(indexDefinitions, hasSize(1));
+ assertThat(indexDefinitions).hasSize(1);
assertIndexPathAndCollection(new String[] { "zero" }, "CompoundIndexOnLevelZeroWithEmptyIndexDef",
indexDefinitions.get(0));
}
@@ -497,7 +615,7 @@ public void singleCompoundIndexPathOnLevelZeroIsResolvedCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
SingleCompoundIndex.class);
- assertThat(indexDefinitions, hasSize(1));
+ assertThat(indexDefinitions).hasSize(1);
assertIndexPathAndCollection(new String[] { "foo", "bar" }, "CompoundIndexOnLevelZero", indexDefinitions.get(0));
}
@@ -507,10 +625,30 @@ public void singleCompoundIndexUsingComposedAnnotationsOnTypeResolvedCorrectly()
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
CompoundIndexDocumentWithComposedAnnotation.class);
- assertThat(indexDefinitions, hasSize(1));
- assertThat(indexDefinitions.get(0).getIndexKeys(), isBsonObject().containing("foo", 1).containing("bar", -1));
- assertThat(indexDefinitions.get(0).getIndexOptions(), isBsonObject().containing("name", "my_compound_index_name")
- .containing("unique", true).containing("background", true));
+ assertThat(indexDefinitions).hasSize(1);
+ assertThat(indexDefinitions.get(0).getIndexKeys()).containsEntry("foo", 1).containsEntry("bar", -1);
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("name", "my_compound_index_name")
+ .containsEntry("unique", true).containsEntry("background", true);
+ }
+
+ @Test // DATAMONGO-2112
+ public void resolveExpressionIndexNameForCompoundIndex() {
+
+ List indexDefinitions = prepareMappingContextAndResolveIndexForType(
+ CompoundIndexWithNameExpression.class);
+
+ 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")
@@ -538,22 +676,16 @@ static class CompoundIndexOnLevelZeroWithEmptyIndexDef {}
unique = true)
static class SingleCompoundIndex {}
- static class IndexDefinedOnSuperClass extends CompoundIndexOnLevelZero {
-
- }
+ static class IndexDefinedOnSuperClass extends CompoundIndexOnLevelZero {}
@Document("ComountIndexWithAutogeneratedName")
@CompoundIndexes({ @CompoundIndex(useGeneratedName = true, def = "{'foo': 1, 'bar': -1}", background = true,
sparse = true, unique = true) })
- static class ComountIndexWithAutogeneratedName {
-
- }
+ static class ComountIndexWithAutogeneratedName {}
@Document("WithComposedAnnotation")
@ComposedCompoundIndex
- static class CompoundIndexDocumentWithComposedAnnotation {
-
- }
+ static class CompoundIndexDocumentWithComposedAnnotation {}
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@@ -577,6 +709,14 @@ static class CompoundIndexDocumentWithComposedAnnotation {
}
+ @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 {
@@ -586,7 +726,8 @@ public void shouldResolveSingleFieldTextIndexCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
TextIndexOnSinglePropertyInRoot.class);
- assertThat(indexDefinitions.size(), equalTo(1));
+
+ assertThat(indexDefinitions).hasSize(1);
assertIndexPathAndCollection("bar", "textIndexOnSinglePropertyInRoot", indexDefinitions.get(0));
}
@@ -595,7 +736,8 @@ public void shouldResolveMultiFieldTextIndexCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
TextIndexOnMutiplePropertiesInRoot.class);
- assertThat(indexDefinitions.size(), equalTo(1));
+
+ assertThat(indexDefinitions).hasSize(1);
assertIndexPathAndCollection(new String[] { "foo", "bar" }, "textIndexOnMutiplePropertiesInRoot",
indexDefinitions.get(0));
}
@@ -605,7 +747,7 @@ public void shouldResolveTextIndexOnElementCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
TextIndexOnNestedRoot.class);
- assertThat(indexDefinitions.size(), equalTo(1));
+ assertThat(indexDefinitions).hasSize(1);
assertIndexPathAndCollection(new String[] { "nested.foo" }, "textIndexOnNestedRoot", indexDefinitions.get(0));
}
@@ -614,12 +756,12 @@ public void shouldResolveTextIndexOnElementWithWeightCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
TextIndexOnNestedWithWeightRoot.class);
- assertThat(indexDefinitions.size(), equalTo(1));
+ assertThat(indexDefinitions).hasSize(1);
assertIndexPathAndCollection(new String[] { "nested.foo" }, "textIndexOnNestedWithWeightRoot",
indexDefinitions.get(0));
org.bson.Document weights = DocumentTestUtils.getAsDocument(indexDefinitions.get(0).getIndexOptions(), "weights");
- assertThat(weights.get("nested.foo"), is((Object) 5F));
+ assertThat(weights.get("nested.foo")).isEqualTo(5F);
}
@Test // DATAMONGO-937
@@ -627,13 +769,13 @@ public void shouldResolveTextIndexOnElementWithMostSpecificWeightCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
TextIndexOnNestedWithMostSpecificValueRoot.class);
- assertThat(indexDefinitions.size(), equalTo(1));
+ assertThat(indexDefinitions).hasSize(1);
assertIndexPathAndCollection(new String[] { "nested.foo", "nested.bar" },
"textIndexOnNestedWithMostSpecificValueRoot", indexDefinitions.get(0));
org.bson.Document weights = DocumentTestUtils.getAsDocument(indexDefinitions.get(0).getIndexOptions(), "weights");
- assertThat(weights.get("nested.foo"), is((Object) 5F));
- assertThat(weights.get("nested.bar"), is((Object) 10F));
+ assertThat(weights.get("nested.foo")).isEqualTo(5F);
+ assertThat(weights.get("nested.bar")).isEqualTo(10F);
}
@Test // DATAMONGO-937
@@ -641,7 +783,7 @@ public void shouldSetDefaultLanguageCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
DocumentWithDefaultLanguage.class);
- assertThat(indexDefinitions.get(0).getIndexOptions().get("default_language"), is((Object) "spanish"));
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("default_language", "spanish");
}
@Test // DATAMONGO-937, DATAMONGO-1049
@@ -649,7 +791,7 @@ public void shouldResolveTextIndexLanguageOverrideCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
DocumentWithLanguageOverride.class);
- assertThat(indexDefinitions.get(0).getIndexOptions().get("language_override"), is((Object) "lang"));
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("language_override", "lang");
}
@Test // DATAMONGO-1049
@@ -657,7 +799,7 @@ public void shouldIgnoreTextIndexLanguageOverrideOnNestedElements() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
DocumentWithLanguageOverrideOnNestedElement.class);
- assertThat(indexDefinitions.get(0).getIndexOptions().get("language_override"), is(nullValue()));
+ assertThat(indexDefinitions.get(0).getIndexOptions().get("language_override")).isNull();
}
@Test // DATAMONGO-1049
@@ -665,7 +807,8 @@ public void shouldNotCreateIndexDefinitionWhenOnlyLanguageButNoTextIndexPresent(
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
DocumentWithNoTextIndexPropertyButReservedFieldLanguage.class);
- assertThat(indexDefinitions, is(empty()));
+
+ assertThat(indexDefinitions).isEmpty();
}
@Test // DATAMONGO-1049
@@ -673,7 +816,8 @@ public void shouldNotCreateIndexDefinitionWhenOnlyAnnotatedLanguageButNoTextInde
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
DocumentWithNoTextIndexPropertyButReservedFieldLanguageAnnotated.class);
- assertThat(indexDefinitions, is(empty()));
+
+ assertThat(indexDefinitions).isEmpty();
}
@Test // DATAMONGO-1049
@@ -681,7 +825,8 @@ public void shouldPreferExplicitlyAnnotatedLanguageProperty() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
DocumentWithOverlappingLanguageProps.class);
- assertThat(indexDefinitions.get(0).getIndexOptions().get("language_override"), is((Object) "lang"));
+
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("language_override", "lang");
}
@Test // DATAMONGO-1373
@@ -691,7 +836,7 @@ public void shouldResolveComposedAnnotationCorrectly() {
TextIndexedDocumentWithComposedAnnotation.class);
org.bson.Document weights = DocumentTestUtils.getAsDocument(indexDefinitions.get(0).getIndexOptions(), "weights");
- assertThat(weights, isBsonObject().containing("foo", 99f));
+ assertThat(weights).containsEntry("foo", 99f);
}
@Document
@@ -789,7 +934,7 @@ static class TextIndexedDocumentWithComposedAnnotation {
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@TextIndexed
- static @interface ComposedTextIndexedAnnotation {
+ @interface ComposedTextIndexedAnnotation {
@AliasFor(annotation = TextIndexed.class, attribute = "weight")
float heavyweight() default 99f;
@@ -803,25 +948,27 @@ public void multipleIndexesResolvedCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(MixedIndexRoot.class);
- assertThat(indexDefinitions, hasSize(2));
- assertThat(indexDefinitions.get(0).getIndexDefinition(), instanceOf(Index.class));
- assertThat(indexDefinitions.get(1).getIndexDefinition(), instanceOf(GeospatialIndex.class));
+ assertThat(indexDefinitions).hasSize(2);
+ assertThat(indexDefinitions.get(0).getIndexDefinition()).isInstanceOf(Index.class);
+ assertThat(indexDefinitions.get(1).getIndexDefinition()).isInstanceOf(GeospatialIndex.class);
}
@Test // DATAMONGO-899
public void cyclicPropertyReferenceOverDBRefShouldNotBeTraversed() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(Inner.class);
- assertThat(indexDefinitions, hasSize(1));
- assertThat(indexDefinitions.get(0).getIndexDefinition().getIndexKeys(),
- equalTo(new org.bson.Document().append("outer", 1)));
+
+ assertThat(indexDefinitions).hasSize(1);
+ assertThat(indexDefinitions.get(0).getIndexDefinition().getIndexKeys())
+ .isEqualTo(new org.bson.Document().append("outer", 1));
}
@Test // DATAMONGO-899
public void associationsShouldNotBeTraversed() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(Outer.class);
- assertThat(indexDefinitions, empty());
+
+ assertThat(indexDefinitions).isEmpty();
}
@Test // DATAMONGO-926
@@ -829,7 +976,8 @@ public void shouldNotRunIntoStackOverflow() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
CycleStartingInBetween.class);
- assertThat(indexDefinitions, hasSize(1));
+
+ assertThat(indexDefinitions).hasSize(1);
}
@Test // DATAMONGO-926
@@ -838,7 +986,7 @@ public void indexShouldBeFoundEvenForCyclePropertyReferenceOnLevelZero() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(CycleLevelZero.class);
assertIndexPathAndCollection("indexedProperty", "cycleLevelZero", indexDefinitions.get(0));
assertIndexPathAndCollection("cyclicReference.indexedProperty", "cycleLevelZero", indexDefinitions.get(1));
- assertThat(indexDefinitions, hasSize(2));
+ assertThat(indexDefinitions).hasSize(2);
}
@Test // DATAMONGO-926
@@ -846,7 +994,7 @@ public void indexShouldBeFoundEvenForCyclePropertyReferenceOnLevelOne() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(CycleOnLevelOne.class);
assertIndexPathAndCollection("reference.indexedProperty", "cycleOnLevelOne", indexDefinitions.get(0));
- assertThat(indexDefinitions, hasSize(1));
+ assertThat(indexDefinitions).hasSize(1);
}
@Test // DATAMONGO-926
@@ -854,11 +1002,12 @@ public void indexBeResolvedCorrectlyWhenPropertiesOfDifferentTypesAreNamedEquall
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
NoCycleButIdenticallyNamedProperties.class);
+
+ assertThat(indexDefinitions).hasSize(3);
assertIndexPathAndCollection("foo", "noCycleButIdenticallyNamedProperties", indexDefinitions.get(0));
assertIndexPathAndCollection("reference.foo", "noCycleButIdenticallyNamedProperties", indexDefinitions.get(1));
assertIndexPathAndCollection("reference.deep.foo", "noCycleButIdenticallyNamedProperties",
indexDefinitions.get(2));
- assertThat(indexDefinitions, hasSize(3));
}
@Test // DATAMONGO-949
@@ -867,7 +1016,7 @@ public void shouldNotDetectCycleInSimilarlyNamedProperties() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
SimilarityHolingBean.class);
assertIndexPathAndCollection("norm", "similarityHolingBean", indexDefinitions.get(0));
- assertThat(indexDefinitions, hasSize(1));
+ assertThat(indexDefinitions).hasSize(1);
}
@Test // DATAMONGO-962
@@ -875,7 +1024,8 @@ public void shouldDetectSelfCycleViaCollectionTypeCorrectly() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
SelfCyclingViaCollectionType.class);
- assertThat(indexDefinitions, empty());
+
+ assertThat(indexDefinitions).isEmpty();
}
@Test // DATAMONGO-962
@@ -883,14 +1033,15 @@ public void shouldNotDetectCycleWhenTypeIsUsedMoreThanOnce() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
MultipleObjectsOfSameType.class);
- assertThat(indexDefinitions, empty());
+
+ assertThat(indexDefinitions).isEmpty();
}
@Test // DATAMONGO-962
@SuppressWarnings({ "rawtypes", "unchecked" })
public void shouldCatchCyclicReferenceExceptionOnRoot() {
- MongoPersistentEntity entity = new BasicMongoPersistentEntity(ClassTypeInformation.from(Object.class));
+ MongoPersistentEntity entity = new BasicMongoPersistentEntity<>(ClassTypeInformation.from(Object.class));
MongoPersistentProperty propertyMock = mock(MongoPersistentProperty.class);
when(propertyMock.isEntity()).thenReturn(true);
@@ -898,7 +1049,7 @@ public void shouldCatchCyclicReferenceExceptionOnRoot() {
when(propertyMock.getActualType()).thenThrow(
new MongoPersistentEntityIndexResolver.CyclicPropertyReferenceException("foo", Object.class, "bar"));
- MongoPersistentEntity selfCyclingEntity = new BasicMongoPersistentEntity(
+ MongoPersistentEntity selfCyclingEntity = new BasicMongoPersistentEntity<>(
ClassTypeInformation.from(SelfCyclingViaCollectionType.class));
new MongoPersistentEntityIndexResolver(prepareMappingContext(SelfCyclingViaCollectionType.class))
@@ -911,9 +1062,9 @@ public void shouldAllowMultiplePathsToDeeplyType() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
NoCycleManyPathsToDeepValueObject.class);
+ assertThat(indexDefinitions).hasSize(2);
assertIndexPathAndCollection("l3.valueObject.value", "rules", indexDefinitions.get(0));
assertIndexPathAndCollection("l2.l3.valueObject.value", "rules", indexDefinitions.get(1));
- assertThat(indexDefinitions, hasSize(2));
}
@Test // DATAMONGO-1025
@@ -921,8 +1072,9 @@ public void shouldUsePathIndexAsIndexNameForDocumentsHavingNamedNestedCompoundIn
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
DocumentWithNestedDocumentHavingNamedCompoundIndex.class);
- assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"),
- equalTo("propertyOfTypeHavingNamedCompoundIndex.c_index"));
+
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("name",
+ "propertyOfTypeHavingNamedCompoundIndex.c_index");
}
@Test // DATAMONGO-1025
@@ -930,8 +1082,8 @@ public void shouldUseIndexNameForNestedTypesWithNamedCompoundIndexDefinition() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
DocumentWithNestedTypeHavingNamedCompoundIndex.class);
- assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"),
- equalTo("propertyOfTypeHavingNamedCompoundIndex.c_index"));
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("name",
+ "propertyOfTypeHavingNamedCompoundIndex.c_index");
}
@Test // DATAMONGO-1025
@@ -939,8 +1091,9 @@ public void shouldUsePathIndexAsIndexNameForDocumentsHavingNamedNestedIndexFixed
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
DocumentWithNestedDocumentHavingNamedIndex.class);
- assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"),
- equalTo("propertyOfTypeHavingNamedIndex.property_index"));
+
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("name",
+ "propertyOfTypeHavingNamedIndex.property_index");
}
@Test // DATAMONGO-1025
@@ -948,8 +1101,9 @@ public void shouldUseIndexNameForNestedTypesWithNamedIndexDefinition() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
DocumentWithNestedTypeHavingNamedIndex.class);
- assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"),
- equalTo("propertyOfTypeHavingNamedIndex.property_index"));
+
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("name",
+ "propertyOfTypeHavingNamedIndex.property_index");
}
@Test // DATAMONGO-1025
@@ -957,7 +1111,7 @@ public void shouldUseIndexNameOnRootLevel() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
DocumentWithNamedIndex.class);
- assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"), equalTo("property_index"));
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("name", "property_index");
}
@Test // DATAMONGO-1087
@@ -966,9 +1120,9 @@ public void shouldAllowMultiplePropertiesOfSameTypeWithMatchingStartLettersOnRoo
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
MultiplePropertiesOfSameTypeWithMatchingStartLetters.class);
- assertThat(indexDefinitions, hasSize(2));
- assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"), equalTo("name.component"));
- assertThat((String) indexDefinitions.get(1).getIndexOptions().get("name"), equalTo("nameLast.component"));
+ assertThat(indexDefinitions).hasSize(2);
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("name", "name.component");
+ assertThat(indexDefinitions.get(1).getIndexOptions()).containsEntry("name", "nameLast.component");
}
@Test // DATAMONGO-1087
@@ -977,9 +1131,9 @@ public void shouldAllowMultiplePropertiesOfSameTypeWithMatchingStartLettersOnNes
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
MultiplePropertiesOfSameTypeWithMatchingStartLettersOnNestedProperty.class);
- assertThat(indexDefinitions, hasSize(2));
- assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"), equalTo("component.nameLast"));
- assertThat((String) indexDefinitions.get(1).getIndexOptions().get("name"), equalTo("component.name"));
+ assertThat(indexDefinitions).hasSize(2);
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("name", "component.nameLast");
+ assertThat(indexDefinitions.get(1).getIndexOptions()).containsEntry("name", "component.name");
}
@Test // DATAMONGO-1121
@@ -988,11 +1142,10 @@ public void shouldOnlyConsiderEntitiesAsPotentialCycleCandidates() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
OuterDocumentReferingToIndexedPropertyViaDifferentNonCyclingPaths.class);
- assertThat(indexDefinitions, hasSize(2));
- assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"), equalTo("path1.foo"));
- assertThat((String) indexDefinitions.get(1).getIndexOptions().get("name"),
- equalTo("path2.propertyWithIndexedStructure.foo"));
-
+ assertThat(indexDefinitions).hasSize(2);
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("name", "path1.foo");
+ assertThat(indexDefinitions.get(1).getIndexOptions()).containsEntry("name",
+ "path2.propertyWithIndexedStructure.foo");
}
@Test // DATAMONGO-1263
@@ -1001,9 +1154,9 @@ public void shouldConsiderGenericTypeArgumentsOfCollectionElements() {
List indexDefinitions = prepareMappingContextAndResolveIndexForType(
EntityWithGenericTypeWrapperAsElement.class);
- assertThat(indexDefinitions, hasSize(1));
- assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"),
- equalTo("listWithGeneircTypeElement.entity.property_index"));
+ assertThat(indexDefinitions).hasSize(1);
+ assertThat(indexDefinitions.get(0).getIndexOptions()).containsEntry("name",
+ "listWithGeneircTypeElement.entity.property_index");
}
@Document
@@ -1235,9 +1388,9 @@ private static void assertIndexPathAndCollection(String[] expectedPaths, String
IndexDefinitionHolder holder) {
for (String expectedPath : expectedPaths) {
- assertThat(holder.getIndexDefinition().getIndexKeys().containsKey(expectedPath), equalTo(true));
+ assertThat(holder.getIndexDefinition().getIndexKeys()).containsKey(expectedPath);
}
- assertThat(holder.getCollection(), equalTo(expectedCollection));
+ assertThat(holder.getCollection()).isEqualTo(expectedCollection);
}
}
diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc
index 56d2962065..a7098caa87 100644
--- a/src/main/asciidoc/new-features.adoc
+++ b/src/main/asciidoc/new-features.adoc
@@ -14,6 +14,7 @@
* Kotlin extension methods accepting `KClass` are deprecated now in favor of `reified` methods.
* Support of array filters in `Update` operations.
* <> from domain types.
+* SpEL support in for expressions in `@Indexed`.
[[new-features.2-1-0]]
== What's New in Spring Data MongoDB 2.1