diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java index 4d5349f7e7..db1fa0bf80 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java @@ -115,7 +115,7 @@ private static Converter getIndexDefinitionIndexO ops = ops.collation(fromDocument(indexOptions.get("collation", Document.class))); } - if(indexOptions.containsKey("wildcardProjection")) { + if (indexOptions.containsKey("wildcardProjection")) { ops.wildcardProjection(indexOptions.get("wildcardProjection", Document.class)); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java index 7883da2270..843584b29d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java @@ -29,7 +29,17 @@ public final class IndexField { enum Type { - GEO, TEXT, DEFAULT, HASH, WILDCARD; + GEO, TEXT, DEFAULT, + + /** + * @since 2.2 + */ + HASH, + + /** + * @since 3.3 + */ + WILDCARD; } private final String key; @@ -78,7 +88,8 @@ static IndexField hashed(String key) { } /** - * Creates a {@literal wildcard} {@link IndexField} for the given key. + * Creates a {@literal wildcard} {@link IndexField} for the given key. The {@code key} must follow the + * {@code fieldName.$**} notation. * * @param key must not be {@literal null} or empty. * @return new instance of {@link IndexField}. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java index f8370b1bc6..51b4aa48cf 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java @@ -100,7 +100,7 @@ public static IndexInfo indexInfoOf(Document sourceDocument) { if (ObjectUtils.nullSafeEquals("hashed", value)) { indexFields.add(IndexField.hashed(key)); - } else if (key.contains("$**")) { + } else if (key.endsWith("$**")) { indexFields.add(IndexField.wildcard(key)); } else { 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 78f895e077..5fdb1cbc40 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 @@ -119,6 +119,8 @@ public List resolveIndexForEntity(MongoPersistentEntity String .format("Entity %s is not a collection root. Make sure to annotate it with @Document!", root.getName())); + verifyWildcardIndexedProjection(root); + List indexInformation = new ArrayList<>(); String collection = root.getCollection(); indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions("", collection, root)); @@ -133,6 +135,24 @@ public List resolveIndexForEntity(MongoPersistentEntity entity) { + + entity.doWithAll(it -> { + + if (it.isAnnotationPresent(WildcardIndexed.class)) { + + WildcardIndexed indexed = it.getRequiredAnnotation(WildcardIndexed.class); + + if (!ObjectUtils.isEmpty(indexed.wildcardProjection())) { + + throw new MappingException(String.format( + "WildcardIndexed.wildcardProjection cannot be used on nested paths. Offending property: %s.%s", + entity.getName(), it.getName())); + } + } + }); + } + private void potentiallyAddIndexForProperty(MongoPersistentEntity root, MongoPersistentProperty persistentProperty, List indexes, CycleGuard guard) { @@ -257,7 +277,7 @@ private List potentiallyCreateCompoundIndexDefinitions(St private List potentiallyCreateWildcardIndexDefinitions(String dotPath, String collection, MongoPersistentEntity entity) { - if (entity.findAnnotation(WildcardIndexed.class) == null) { + if (!entity.isAnnotationPresent(WildcardIndexed.class)) { return Collections.emptyList(); } @@ -429,7 +449,7 @@ protected IndexDefinitionHolder createWildcardIndexDefinition(String dotPath, St WildcardIndex indexDefinition = new WildcardIndex(dotPath); - if (StringUtils.hasText(index.wildcardProjection())) { + if (StringUtils.hasText(index.wildcardProjection()) && ObjectUtils.isEmpty(dotPath)) { indexDefinition.wildcardProjection(evaluateWildcardProjection(index.wildcardProjection(), entity)); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/WildcardIndex.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/WildcardIndex.java index ab1cda6183..b07c3b1bc9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/WildcardIndex.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/WildcardIndex.java @@ -51,7 +51,7 @@ public class WildcardIndex extends Index { private @Nullable String fieldName; - private Map wildcardProjection = new LinkedHashMap<>(); + private final Map wildcardProjection = new LinkedHashMap<>(); /** * Create a new instance of {@link WildcardIndex} using {@code $**}. @@ -97,7 +97,7 @@ public WildcardIndex named(String name) { /** * Unique option is not supported. * - * @throws UnsupportedOperationException + * @throws UnsupportedOperationException not supported for wildcard indexes. */ @Override public Index unique() { @@ -107,7 +107,7 @@ public Index unique() { /** * ttl option is not supported. * - * @throws UnsupportedOperationException + * @throws UnsupportedOperationException not supported for wildcard indexes. */ @Override public Index expire(long seconds) { @@ -117,7 +117,7 @@ public Index expire(long seconds) { /** * ttl option is not supported. * - * @throws UnsupportedOperationException + * @throws UnsupportedOperationException not supported for wildcard indexes. */ @Override public Index expire(long value, TimeUnit timeUnit) { @@ -127,7 +127,7 @@ public Index expire(long value, TimeUnit timeUnit) { /** * ttl option is not supported. * - * @throws UnsupportedOperationException + * @throws UnsupportedOperationException not supported for wildcard indexes. */ @Override public Index expire(Duration duration) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/WildcardIndexed.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/WildcardIndexed.java index 5f32aaf45c..d1b18e85bf 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/WildcardIndexed.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/WildcardIndexed.java @@ -38,7 +38,7 @@ * * db.product.createIndex({ "$**" : 1 } , {}) * - * + * * {@literal wildcardProjection} can be used to specify keys to in-/exclude in the index. * *
@@ -65,7 +65,7 @@
  * 
  * @Document
  * public class User {
- * 
+ *
  *     private @Id String id;
  *
  *     @WildcardIndexed
@@ -89,9 +89,9 @@
 	 * 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.
- * - * @return + * provided name will be prefixed with the path leading to the entity. + * + * @return empty by default. */ String name() default ""; @@ -115,8 +115,8 @@ /** * Explicitly specify sub fields to be in-/excluded as a {@link org.bson.Document#parse(String) prasable} String. *
- * NOTE: Can only be done on root level documents. - * + * NOTE: Can only be applied on root level documents. + * * @return empty by default. */ String wildcardProjection() default ""; 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 0a06561b67..30f6a9bfc5 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 @@ -32,10 +32,12 @@ 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.dao.InvalidDataAccessApiUsageException; import org.springframework.data.annotation.Id; import org.springframework.data.geo.Point; +import org.springframework.data.mapping.MappingException; import org.springframework.data.mongodb.core.DocumentTestUtils; import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.IndexDefinitionHolder; import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolverUnitTests.CompoundIndexResolutionTests; @@ -1333,6 +1335,20 @@ public void resolvesWildcardOnRoot() { assertThat(indices).hasSize(1); assertThat(indices.get(0)).satisfies(it -> { assertThat(it.getIndexKeys()).containsEntry("$**", 1); + assertThat(it.getIndexOptions()).isEmpty(); + }); + } + + @Test // GH-3225 + public void resolvesWildcardWithProjectionOnRoot() { + + List indices = prepareMappingContextAndResolveIndexForType( + WithWildCardIndexHavingProjectionOnEntity.class); + assertThat(indices).hasSize(1); + assertThat(indices.get(0)).satisfies(it -> { + assertThat(it.getIndexKeys()).containsEntry("$**", 1); + assertThat(it.getIndexOptions()).containsEntry("wildcardProjection", + org.bson.Document.parse("{'_id' : 1, 'value' : 0}")); }); } @@ -1365,6 +1381,15 @@ public void resolvesWildcardTypeOfNestedProperty() { assertThat(indices).hasSize(1); assertThat(indices.get(0)).satisfies(it -> { assertThat(it.getIndexKeys()).containsEntry("value.$**", 1); + assertThat(it.getIndexOptions()).hasSize(1).containsKey("name"); + }); + } + + @Test // GH-3225 + public void rejectsWildcardProjectionOnNestedPaths() { + + assertThatExceptionOfType(MappingException.class).isThrownBy(() -> { + prepareMappingContextAndResolveIndexForType(WildcardIndexedProjectionOnNestedPath.class); }); } @@ -1647,10 +1672,16 @@ class WithWildCardIndexOnProperty { } + @Document + class WildcardIndexedProjectionOnNestedPath { + + @WildcardIndexed(wildcardProjection = "{}") String foo; + } + @Document class WithWildCardOnEntityOfNested { - WithWildCardIndexOnEntity value; + WithWildCardIndexHavingProjectionOnEntity value; } diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index a74594bff0..74458b9971 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -6,6 +6,7 @@ * Extended support for <> entities. * Include/exclude `null` properties on write to `Document` through `@Field(write=…)`. +* Support for <>. [[new-features.3.2]] == What's New in Spring Data MongoDB 3.2 diff --git a/src/main/asciidoc/reference/mapping.adoc b/src/main/asciidoc/reference/mapping.adoc index 7caf1093b9..e301826697 100644 --- a/src/main/asciidoc/reference/mapping.adoc +++ b/src/main/asciidoc/reference/mapping.adoc @@ -782,9 +782,9 @@ db.user.createIndex({ "userMetadata.$**" : 1 }, {}) ---- ==== -The `@WildcardIndex` annotation allows a declarative index setup an can be added on either a type or property. +The `@WildcardIndex` annotation allows a declarative index setup that can used either with a document type or property. -If placed on a type that is a root level domain entity (one having an `@Document` annotation) will advise the index creator to create a +If placed on a type that is a root level domain entity (one annotated with `@Document`) , the index resolver will create a wildcard index for it. .Wildcard index on domain type @@ -794,7 +794,7 @@ wildcard index for it. @Document @WildcardIndexed public class Product { - ... + // … } ---- [source,javascript] @@ -828,7 +828,8 @@ db.user.createIndex( ==== Wildcard indexes can also be expressed by adding the annotation directly to the field. -Please note that `wildcardProjection` is not allowed on nested paths. +Please note that `wildcardProjection` is not allowed on nested paths such as properties. +Projections on types annotated with `@WildcardIndexed` are omitted during index creation. .Wildcard index on property ====