Browse Source

Added repeatable options for annotations

issue/4471
Marcin Grzejszczak 1 year ago
parent
commit
ad5cddbbf8
No known key found for this signature in database
GPG Key ID: 9663E23C6E20556A
  1. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundWildcardIndex.java
  2. 41
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundWildcardIndexes.java
  3. 90
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolver.java
  4. 2
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/WildcardIndexed.java
  5. 41
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/WildcardIndexes.java
  6. 68
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java

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

@ -19,6 +19,7 @@ import org.springframework.core.annotation.AliasFor; @@ -19,6 +19,7 @@ import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@ -53,12 +54,13 @@ import java.lang.annotation.Target; @@ -53,12 +54,13 @@ import java.lang.annotation.Target;
*
* @author Julia Lee
* @author Marcin Grzejszczak
* @since 4.4.0
* @since 4.4
*/
@Target({ ElementType.TYPE })
@Documented
@CompoundIndex
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(CompoundWildcardIndexes.class)
public @interface CompoundWildcardIndex {
/**

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

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
/*
* Copyright 2011-2024 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.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Container annotation that allows to collect multiple {@link CompoundWildcardIndex} annotations.
* <p>
* Can be used natively, declaring several nested {@link CompoundWildcardIndex} annotations. Can also be used in conjunction
* with Java 8's support for <em>repeatable annotations</em>, where {@link CompoundWildcardIndex} can simply be declared several
* times on the same {@linkplain ElementType#TYPE type}, implicitly generating this container annotation.
*
* @author Marcin Grzejszczak
* @since 4.4
*/
@Target({ ElementType.TYPE })
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface CompoundWildcardIndexes {
CompoundWildcardIndex[] value();
}

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

@ -157,20 +157,30 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { @@ -157,20 +157,30 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
}
});
if (entity.isAnnotationPresent(CompoundWildcardIndex.class)) {
CompoundWildcardIndex indexed = entity.getRequiredAnnotation(CompoundWildcardIndex.class);
if (!isWildcardFromRoot(indexed.wildcardFieldName()) && !ObjectUtils.isEmpty(indexed.wildcardProjection())) {
if (entity.isAnnotationPresent(CompoundWildcardIndexes.class)) {
CompoundWildcardIndexes indexes = entity.getRequiredAnnotation(CompoundWildcardIndexes.class);
for (CompoundWildcardIndex compoundWildcardIndex : indexes.value()) {
checkSingleIndex(compoundWildcardIndex);
}
throw new MappingException(
String.format("CompoundWildcardIndex.wildcardProjection is only allowed on \"$**\"; Offending property: %s",
indexed.wildcardFieldName()));
}
if (entity.isAnnotationPresent(CompoundWildcardIndex.class)) {
checkSingleIndex(entity.getRequiredAnnotation(CompoundWildcardIndex.class));
}
}
if (isWildcardFromRoot(indexed.wildcardFieldName()) && ObjectUtils.isEmpty(indexed.wildcardProjection())) {
private static void checkSingleIndex(CompoundWildcardIndex indexed) {
throw new MappingException("CompoundWildcardIndex.wildcardProjection is required on \"$**\"");
}
if (!isWildcardFromRoot(indexed.wildcardFieldName()) && !ObjectUtils.isEmpty(indexed.wildcardProjection())) {
throw new MappingException(
String.format("CompoundWildcardIndex.wildcardProjection is only allowed on \"$**\"; Offending property: %s",
indexed.wildcardFieldName()));
}
if (isWildcardFromRoot(indexed.wildcardFieldName()) && ObjectUtils.isEmpty(indexed.wildcardProjection())) {
throw new MappingException("CompoundWildcardIndex.wildcardProjection is required on \"$**\"");
}
}
@ -204,23 +214,6 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { @@ -204,23 +214,6 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
}
}
/**
* Recursively resolve and inspect properties of given {@literal type} for indexes to be created.
*
* @param type
* @param dotPath The {@literal "dot} path.
* @param path {@link PersistentProperty} path for cycle detection.
* @param collection
* @param guard
* @return List of {@link IndexDefinitionHolder} representing indexes for given type and its referenced property
* types. Will never be {@code null}.
*/
private List<IndexDefinitionHolder> resolveIndexForClass(TypeInformation<?> type, String dotPath, Path path,
String collection, CycleGuard guard) {
return resolveIndexForEntity(mappingContext.getRequiredPersistentEntity(type), dotPath, path, collection, guard);
}
private List<IndexDefinitionHolder> resolveIndexForEntity(MongoPersistentEntity<?> entity, String dotPath, Path path,
String collection, CycleGuard guard) {
@ -303,7 +296,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { @@ -303,7 +296,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
MongoPersistentEntity<?> entity) {
if ((!entity.isAnnotationPresent(CompoundIndexes.class) && !entity.isAnnotationPresent(CompoundIndex.class))
|| entity.isAnnotationPresent(CompoundWildcardIndex.class)) {
|| entity.isAnnotationPresent(CompoundWildcardIndex.class) || entity.isAnnotationPresent(CompoundWildcardIndexes.class)) {
return Collections.emptyList();
}
@ -313,14 +306,23 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { @@ -313,14 +306,23 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
private List<IndexDefinitionHolder> potentiallyCreateWildcardIndexDefinitions(String dotPath, String collection,
MongoPersistentEntity<?> entity) {
if (!entity.isAnnotationPresent(WildcardIndexed.class)
|| entity.isAnnotationPresent(CompoundWildcardIndex.class)) {
if ((!entity.isAnnotationPresent(WildcardIndexed.class) && !entity.isAnnotationPresent(WildcardIndexes.class))
|| entity.isAnnotationPresent(CompoundWildcardIndex.class) || entity.isAnnotationPresent(CompoundWildcardIndexes.class)) {
return Collections.emptyList();
}
return Collections.singletonList(new IndexDefinitionHolder(dotPath,
createWildcardIndexDefinition(dotPath, collection, entity.getRequiredAnnotation(WildcardIndexed.class), entity),
collection));
WildcardIndexes wildcardIndexes = entity.findAnnotation(WildcardIndexes.class);
if (wildcardIndexes == null) {
return Collections.singletonList(new IndexDefinitionHolder(dotPath,
createWildcardIndexDefinition(dotPath, collection, entity.getRequiredAnnotation(WildcardIndexed.class), entity),
collection));
}
List<IndexDefinitionHolder> holders = new ArrayList<>();
for (WildcardIndexed indexed : wildcardIndexes.value()) {
holders.add(new IndexDefinitionHolder(dotPath,
createWildcardIndexDefinition(dotPath, collection, indexed, entity), collection));
}
return holders;
}
private Collection<? extends IndexDefinitionHolder> potentiallyCreateTextIndexDefinition(
@ -372,14 +374,26 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver { @@ -372,14 +374,26 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
private Collection<? extends IndexDefinitionHolder> potentiallyCreateCompoundWildcardDefinition(
MongoPersistentEntity<?> entity, String collection) {
if (!entity.isAnnotationPresent(CompoundWildcardIndex.class)) {
boolean singleIndexAnnotationPresent = entity.isAnnotationPresent(CompoundWildcardIndex.class);
boolean indexesAnnotationPresent = entity.isAnnotationPresent(CompoundWildcardIndexes.class);
if (!singleIndexAnnotationPresent && !indexesAnnotationPresent) {
return Collections.emptyList();
}
CompoundWildcardIndex compoundWildcardIndex = entity.getRequiredAnnotation(CompoundWildcardIndex.class);
IndexDefinitionHolder compoundWildcardIndexDefinition = createCompoundWildcardIndexDefinition(collection,
compoundWildcardIndex, entity);
return Collections.singletonList(compoundWildcardIndexDefinition);
List<IndexDefinitionHolder> definitions = new ArrayList<>();
if (indexesAnnotationPresent) {
CompoundWildcardIndexes annotation = entity.getRequiredAnnotation(CompoundWildcardIndexes.class);
for (CompoundWildcardIndex index : annotation.value()) {
definitions.add(createCompoundWildcardIndexDefinition(collection, index, entity));
}
}
if (singleIndexAnnotationPresent) {
CompoundWildcardIndex compoundWildcardIndex = entity.getRequiredAnnotation(CompoundWildcardIndex.class);
definitions.add(createCompoundWildcardIndexDefinition(collection,
compoundWildcardIndex, entity));
}
return definitions;
}
private void appendTextIndexInformation(DotPath dotPath, Path path, TextIndexDefinitionBuilder indexDefinitionBuilder,

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

@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core.index; @@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core.index;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@ -86,6 +87,7 @@ import org.springframework.data.mongodb.core.annotation.Collation; @@ -86,6 +87,7 @@ import org.springframework.data.mongodb.core.annotation.Collation;
@Documented
@Target({ ElementType.TYPE, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(WildcardIndexes.class)
public @interface WildcardIndexed {
/**

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

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
/*
* Copyright 2011-2024 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.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Container annotation that allows to collect multiple {@link WildcardIndexed} annotations.
* <p>
* Can be used natively, declaring several nested {@link WildcardIndexed} annotations. Can also be used in conjunction
* with Java 8's support for <em>repeatable annotations</em>, where {@link WildcardIndexed} can simply be declared several
* times on the same {@linkplain ElementType#TYPE type}, implicitly generating this container annotation.
*
* @author Marcin Grzejszczak
* @since 4.4
*/
@Target({ ElementType.TYPE })
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface WildcardIndexes {
WildcardIndexed[] value();
}

68
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java

@ -29,6 +29,8 @@ import java.util.List; @@ -29,6 +29,8 @@ import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@ -55,6 +57,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; @@ -55,6 +57,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.Unwrapped;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.util.StringUtils;
/**
* Tests for {@link MongoPersistentEntityIndexResolver}.
@ -849,6 +852,19 @@ public class MongoPersistentEntityIndexResolverUnitTests { @@ -849,6 +852,19 @@ public class MongoPersistentEntityIndexResolverUnitTests {
indexDefinitions.get(0));
}
@ParameterizedTest // GH-4471
@ValueSource(classes = {RepeatableCompoundWildcardIndex.class, RepeatableCompoundWildcardIndexThroughIndexes.class})
public void compoundWildcardIndexOnSingleField(Class<?> clazz) {
List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(clazz);
assertThat(indexDefinitions).hasSize(2);
assertIndexPathAndCollection(new String[] { "foo1.$**", "bar1", "baz1" }, StringUtils.uncapitalize(clazz.getSimpleName()),
indexDefinitions.get(0));
assertIndexPathAndCollection(new String[] { "foo2.$**", "bar2", "baz2" }, StringUtils.uncapitalize(clazz.getSimpleName()),
indexDefinitions.get(1));
}
@Test // GH-4471
public void compoundWildcardIndexOnEntityWithProjection() {
@ -942,6 +958,16 @@ public class MongoPersistentEntityIndexResolverUnitTests { @@ -942,6 +958,16 @@ public class MongoPersistentEntityIndexResolverUnitTests {
@CompoundWildcardIndex(wildcardFieldName = "foo", fields = "{'bar': 1, 'baz': 1}")
class CompoundWildcardIndexOnFields {}
@Document
@CompoundWildcardIndex(wildcardFieldName = "foo1", fields = "{'bar1': 1, 'baz1': 1}")
@CompoundWildcardIndex(wildcardFieldName = "foo2", fields = "{'bar2': 1, 'baz2': 1}")
class RepeatableCompoundWildcardIndex {}
@Document
@CompoundWildcardIndexes({@CompoundWildcardIndex(wildcardFieldName = "foo1", fields = "{'bar1': 1, 'baz1': 1}"),
@CompoundWildcardIndex(wildcardFieldName = "foo2", fields = "{'bar2': 1, 'baz2': 1}")})
class RepeatableCompoundWildcardIndexThroughIndexes {}
@Document
@CompoundWildcardIndex(wildcardFieldName = "foo", wildcardProjection = "{}", fields = "{'bar': 1}")
class IncorrectCompoundWildcardIndexOnFieldWithProjection {}
@ -1570,6 +1596,26 @@ public class MongoPersistentEntityIndexResolverUnitTests { @@ -1570,6 +1596,26 @@ public class MongoPersistentEntityIndexResolverUnitTests {
});
}
@ParameterizedTest // GH-4471
@ValueSource(classes = { WithRepeatableWildcardIndex.class, WithWildcardIndexes.class})
public void resolvesRepeatableWildcards(Class<?> clazz) {
List<IndexDefinitionHolder> indices = prepareMappingContextAndResolveIndexForType(clazz);
assertThat(indices).hasSize(2);
assertThat(indices.get(0)).satisfies(it -> {
assertThat(it.getIndexKeys()).containsEntry("$**", 1);
assertThat(it.getIndexOptions()).containsEntry("name", "foo")
.containsEntry("collation", new org.bson.Document("locale", "en_US"))
.containsEntry("partialFilterExpression", new org.bson.Document("$eq", 1));
});
assertThat(indices.get(1)).satisfies(it -> {
assertThat(it.getIndexKeys()).containsEntry("$**", 1);
assertThat(it.getIndexOptions()).containsEntry("name", "bar")
.containsEntry("collation", new org.bson.Document("locale", "en_UK"))
.containsEntry("partialFilterExpression", new org.bson.Document("$eq", 0));
});
}
@Test // GH-3225
public void resolvesWildcardTypeOfNestedProperty() {
@ -1924,6 +1970,28 @@ public class MongoPersistentEntityIndexResolverUnitTests { @@ -1924,6 +1970,28 @@ public class MongoPersistentEntityIndexResolverUnitTests {
}
@WildcardIndexed(name = "foo", partialFilter = "{ '$eq' : 1 }", collation = "en_US")
@WildcardIndexed(name = "bar", partialFilter = "{ '$eq' : 0 }", collation = "en_UK")
@Document
class WithRepeatableWildcardIndex {
Map<String, String> value;
Map<String, String> value2;
}
@WildcardIndexes({ @WildcardIndexed(name = "foo", partialFilter = "{ '$eq' : 1 }", collation = "en_US"),
@WildcardIndexed(name = "bar", partialFilter = "{ '$eq' : 0 }", collation = "en_UK") })
@Document
class WithWildcardIndexes {
Map<String, String> value;
Map<String, String> value2;
}
@Document
class WildcardIndexedProjectionOnNestedPath {

Loading…
Cancel
Save