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;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@ -53,12 +54,13 @@ import java.lang.annotation.Target;
* *
* @author Julia Lee * @author Julia Lee
* @author Marcin Grzejszczak * @author Marcin Grzejszczak
* @since 4.4.0 * @since 4.4
*/ */
@Target({ ElementType.TYPE }) @Target({ ElementType.TYPE })
@Documented @Documented
@CompoundIndex @CompoundIndex
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Repeatable(CompoundWildcardIndexes.class)
public @interface CompoundWildcardIndex { public @interface CompoundWildcardIndex {
/** /**

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

@ -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 {
} }
}); });
if (entity.isAnnotationPresent(CompoundWildcardIndex.class)) { if (entity.isAnnotationPresent(CompoundWildcardIndexes.class)) {
CompoundWildcardIndex indexed = entity.getRequiredAnnotation(CompoundWildcardIndex.class); CompoundWildcardIndexes indexes = entity.getRequiredAnnotation(CompoundWildcardIndexes.class);
for (CompoundWildcardIndex compoundWildcardIndex : indexes.value()) {
if (!isWildcardFromRoot(indexed.wildcardFieldName()) && !ObjectUtils.isEmpty(indexed.wildcardProjection())) { 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 {
} }
} }
/**
* 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, private List<IndexDefinitionHolder> resolveIndexForEntity(MongoPersistentEntity<?> entity, String dotPath, Path path,
String collection, CycleGuard guard) { String collection, CycleGuard guard) {
@ -303,7 +296,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
MongoPersistentEntity<?> entity) { MongoPersistentEntity<?> entity) {
if ((!entity.isAnnotationPresent(CompoundIndexes.class) && !entity.isAnnotationPresent(CompoundIndex.class)) if ((!entity.isAnnotationPresent(CompoundIndexes.class) && !entity.isAnnotationPresent(CompoundIndex.class))
|| entity.isAnnotationPresent(CompoundWildcardIndex.class)) { || entity.isAnnotationPresent(CompoundWildcardIndex.class) || entity.isAnnotationPresent(CompoundWildcardIndexes.class)) {
return Collections.emptyList(); return Collections.emptyList();
} }
@ -313,14 +306,23 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
private List<IndexDefinitionHolder> potentiallyCreateWildcardIndexDefinitions(String dotPath, String collection, private List<IndexDefinitionHolder> potentiallyCreateWildcardIndexDefinitions(String dotPath, String collection,
MongoPersistentEntity<?> entity) { MongoPersistentEntity<?> entity) {
if (!entity.isAnnotationPresent(WildcardIndexed.class) if ((!entity.isAnnotationPresent(WildcardIndexed.class) && !entity.isAnnotationPresent(WildcardIndexes.class))
|| entity.isAnnotationPresent(CompoundWildcardIndex.class)) { || entity.isAnnotationPresent(CompoundWildcardIndex.class) || entity.isAnnotationPresent(CompoundWildcardIndexes.class)) {
return Collections.emptyList(); return Collections.emptyList();
} }
return Collections.singletonList(new IndexDefinitionHolder(dotPath, WildcardIndexes wildcardIndexes = entity.findAnnotation(WildcardIndexes.class);
createWildcardIndexDefinition(dotPath, collection, entity.getRequiredAnnotation(WildcardIndexed.class), entity), if (wildcardIndexes == null) {
collection)); 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( private Collection<? extends IndexDefinitionHolder> potentiallyCreateTextIndexDefinition(
@ -372,14 +374,26 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
private Collection<? extends IndexDefinitionHolder> potentiallyCreateCompoundWildcardDefinition( private Collection<? extends IndexDefinitionHolder> potentiallyCreateCompoundWildcardDefinition(
MongoPersistentEntity<?> entity, String collection) { 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(); return Collections.emptyList();
} }
CompoundWildcardIndex compoundWildcardIndex = entity.getRequiredAnnotation(CompoundWildcardIndex.class); List<IndexDefinitionHolder> definitions = new ArrayList<>();
IndexDefinitionHolder compoundWildcardIndexDefinition = createCompoundWildcardIndexDefinition(collection, if (indexesAnnotationPresent) {
compoundWildcardIndex, entity); CompoundWildcardIndexes annotation = entity.getRequiredAnnotation(CompoundWildcardIndexes.class);
return Collections.singletonList(compoundWildcardIndexDefinition); 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, 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;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@ -86,6 +87,7 @@ import org.springframework.data.mongodb.core.annotation.Collation;
@Documented @Documented
@Target({ ElementType.TYPE, ElementType.FIELD }) @Target({ ElementType.TYPE, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Repeatable(WildcardIndexes.class)
public @interface WildcardIndexed { public @interface WildcardIndexed {
/** /**

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

@ -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;
import java.util.Map; import java.util.Map;
import org.junit.Test; 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.runner.RunWith;
import org.junit.runners.Suite; import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses; import org.junit.runners.Suite.SuiteClasses;
@ -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.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.Unwrapped; import org.springframework.data.mongodb.core.mapping.Unwrapped;
import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.ClassTypeInformation;
import org.springframework.util.StringUtils;
/** /**
* Tests for {@link MongoPersistentEntityIndexResolver}. * Tests for {@link MongoPersistentEntityIndexResolver}.
@ -849,6 +852,19 @@ public class MongoPersistentEntityIndexResolverUnitTests {
indexDefinitions.get(0)); 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 @Test // GH-4471
public void compoundWildcardIndexOnEntityWithProjection() { public void compoundWildcardIndexOnEntityWithProjection() {
@ -942,6 +958,16 @@ public class MongoPersistentEntityIndexResolverUnitTests {
@CompoundWildcardIndex(wildcardFieldName = "foo", fields = "{'bar': 1, 'baz': 1}") @CompoundWildcardIndex(wildcardFieldName = "foo", fields = "{'bar': 1, 'baz': 1}")
class CompoundWildcardIndexOnFields {} 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 @Document
@CompoundWildcardIndex(wildcardFieldName = "foo", wildcardProjection = "{}", fields = "{'bar': 1}") @CompoundWildcardIndex(wildcardFieldName = "foo", wildcardProjection = "{}", fields = "{'bar': 1}")
class IncorrectCompoundWildcardIndexOnFieldWithProjection {} class IncorrectCompoundWildcardIndexOnFieldWithProjection {}
@ -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 @Test // GH-3225
public void resolvesWildcardTypeOfNestedProperty() { public void resolvesWildcardTypeOfNestedProperty() {
@ -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 @Document
class WildcardIndexedProjectionOnNestedPath { class WildcardIndexedProjectionOnNestedPath {

Loading…
Cancel
Save