Polishing.

Reformat code. Tweak javadoc. Reject wildcard projection usage on properties with a MappingException. Omit wildcard projections when declared on document types that are used as subdocument.

See #3225
Original pull request: #3671.
This commit is contained in:
Mark Paluch
2021-07-14 15:03:39 +02:00
parent d57c5a9529
commit f3b90c2b8a
9 changed files with 87 additions and 23 deletions

View File

@@ -115,7 +115,7 @@ abstract class IndexConverters {
ops = ops.collation(fromDocument(indexOptions.get("collation", Document.class)));
}
if(indexOptions.containsKey("wildcardProjection")) {
if (indexOptions.containsKey("wildcardProjection")) {
ops.wildcardProjection(indexOptions.get("wildcardProjection", Document.class));
}

View File

@@ -29,7 +29,17 @@ import org.springframework.util.ObjectUtils;
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 @@ public final class IndexField {
}
/**
* 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}.

View File

@@ -100,7 +100,7 @@ public class IndexInfo {
if (ObjectUtils.nullSafeEquals("hashed", value)) {
indexFields.add(IndexField.hashed(key));
} else if (key.contains("$**")) {
} else if (key.endsWith("$**")) {
indexFields.add(IndexField.wildcard(key));
} else {

View File

@@ -119,6 +119,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
Assert.notNull(document, () -> String
.format("Entity %s is not a collection root. Make sure to annotate it with @Document!", root.getName()));
verifyWildcardIndexedProjection(root);
List<IndexDefinitionHolder> indexInformation = new ArrayList<>();
String collection = root.getCollection();
indexInformation.addAll(potentiallyCreateCompoundIndexDefinitions("", collection, root));
@@ -133,6 +135,24 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
return indexInformation;
}
private void verifyWildcardIndexedProjection(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<IndexDefinitionHolder> indexes, CycleGuard guard) {
@@ -257,7 +277,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
private List<IndexDefinitionHolder> 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 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
WildcardIndex indexDefinition = new WildcardIndex(dotPath);
if (StringUtils.hasText(index.wildcardProjection())) {
if (StringUtils.hasText(index.wildcardProjection()) && ObjectUtils.isEmpty(dotPath)) {
indexDefinition.wildcardProjection(evaluateWildcardProjection(index.wildcardProjection(), entity));
}

View File

@@ -51,7 +51,7 @@ import org.springframework.util.StringUtils;
public class WildcardIndex extends Index {
private @Nullable String fieldName;
private Map<String, Object> wildcardProjection = new LinkedHashMap<>();
private final Map<String, Object> wildcardProjection = new LinkedHashMap<>();
/**
* Create a new instance of {@link WildcardIndex} using {@code $**}.
@@ -97,7 +97,7 @@ public class WildcardIndex extends Index {
/**
* Unique option is not supported.
*
* @throws UnsupportedOperationException
* @throws UnsupportedOperationException not supported for wildcard indexes.
*/
@Override
public Index unique() {
@@ -107,7 +107,7 @@ public class WildcardIndex extends Index {
/**
* 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 class WildcardIndex extends Index {
/**
* 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 class WildcardIndex extends Index {
/**
* ttl option is not supported.
*
* @throws UnsupportedOperationException
* @throws UnsupportedOperationException not supported for wildcard indexes.
*/
@Override
public Index expire(Duration duration) {

View File

@@ -38,7 +38,7 @@ import java.lang.annotation.Target;
*
* db.product.createIndex({ "$**" : 1 } , {})
* </pre>
*
*
* {@literal wildcardProjection} can be used to specify keys to in-/exclude in the index.
*
* <pre class="code">
@@ -65,7 +65,7 @@ import java.lang.annotation.Target;
* <pre class="code">
* &#64;Document
* public class User {
*
*
* private &#64;Id String id;
*
* &#64;WildcardIndexed
@@ -89,9 +89,9 @@ public @interface WildcardIndexed {
* expression}. <br />
* <br />
* 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. <br />
*
* @return
* provided name will be prefixed with the path leading to the entity.
*
* @return empty by default.
*/
String name() default "";
@@ -115,8 +115,8 @@ public @interface WildcardIndexed {
/**
* Explicitly specify sub fields to be in-/excluded as a {@link org.bson.Document#parse(String) prasable} String.
* <br />
* <strong>NOTE: </strong>Can only be done on root level documents.
*
* <strong>NOTE: </strong>Can only be applied on root level documents.
*
* @return empty by default.
*/
String wildcardProjection() default "";

View File

@@ -32,10 +32,12 @@ import org.junit.Test;
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 class MongoPersistentEntityIndexResolverUnitTests {
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<IndexDefinitionHolder> 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 class MongoPersistentEntityIndexResolverUnitTests {
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 @@ public class MongoPersistentEntityIndexResolverUnitTests {
}
@Document
class WildcardIndexedProjectionOnNestedPath {
@WildcardIndexed(wildcardProjection = "{}") String foo;
}
@Document
class WithWildCardOnEntityOfNested {
WithWildCardIndexOnEntity value;
WithWildCardIndexHavingProjectionOnEntity value;
}

View File

@@ -6,6 +6,7 @@
* Extended support for <<mapping-usage.document-references, referencing>> entities.
* Include/exclude `null` properties on write to `Document` through `@Field(write=…)`.
* Support for <<mapping-usage-indexes.wildcard-index>>.
[[new-features.3.2]]
== What's New in Spring Data MongoDB 3.2

View File

@@ -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
====