/* * Copyright 2014-2017 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 * * http://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 static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import static org.springframework.data.mongodb.test.util.IsBsonObject.*; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Collections; import java.util.List; 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.data.annotation.Id; import org.springframework.data.geo.Point; 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; import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolverUnitTests.GeoSpatialIndexResolutionTests; import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolverUnitTests.IndexResolutionTests; import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolverUnitTests.MixedIndexResolutionTests; import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolverUnitTests.TextIndexedResolutionTests; import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.Language; 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.util.ClassTypeInformation; /** * @author Christoph Strobl * @author Mark Paluch */ @RunWith(Suite.class) @SuiteClasses({ IndexResolutionTests.class, GeoSpatialIndexResolutionTests.class, CompoundIndexResolutionTests.class, TextIndexedResolutionTests.class, MixedIndexResolutionTests.class }) public class MongoPersistentEntityIndexResolverUnitTests { /** * Test resolution of {@link Indexed}. * * @author Christoph Strobl */ public static class IndexResolutionTests { @Test // DATAMONGO-899 public void indexPathOnLevelZeroIsResolvedCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( IndexOnLevelZero.class); assertThat(indexDefinitions, hasSize(1)); assertIndexPathAndCollection("indexedProperty", "Zero", indexDefinitions.get(0)); } @Test // DATAMONGO-899 public void indexPathOnLevelOneIsResolvedCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelOne.class); assertThat(indexDefinitions, hasSize(1)); assertIndexPathAndCollection("zero.indexedProperty", "One", indexDefinitions.get(0)); } @Test // DATAMONGO-899 public void depplyNestedIndexPathIsResolvedCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelTwo.class); assertThat(indexDefinitions, hasSize(1)); assertIndexPathAndCollection("one.zero.indexedProperty", "Two", indexDefinitions.get(0)); } @Test // DATAMONGO-899 public void resolvesIndexPathNameForNamedPropertiesCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( IndexOnLevelOneWithExplicitlyNamedField.class); assertThat(indexDefinitions, hasSize(1)); assertIndexPathAndCollection("customZero.customFieldName", "indexOnLevelOneWithExplicitlyNamedField", indexDefinitions.get(0)); } @Test // DATAMONGO-899 public void resolvesIndexDefinitionCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( IndexOnLevelZero.class); IndexDefinition indexDefinition = indexDefinitions.get(0).getIndexDefinition(); assertThat(indexDefinition.getIndexOptions(), equalTo(new org.bson.Document().append("name", "indexedProperty"))); } @Test // DATAMONGO-899 public void resolvesIndexDefinitionOptionsCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( 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))); } @Test // DATAMONGO-1297 public void resolvesIndexOnDbrefWhenDefined() { List<IndexDefinitionHolder> 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))); } @Test // DATAMONGO-1297 public void resolvesIndexOnDbrefWhenDefinedOnNestedElement() { List<IndexDefinitionHolder> 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))); } @Test // DATAMONGO-1163 public void resolveIndexDefinitionInMetaAnnotatedFields() { List<IndexDefinitionHolder> 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"))); } @Test // DATAMONGO-1373 public void resolveIndexDefinitionInComposedAnnotatedFields() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( IndexedDocumentWithComposedAnnotations.class); 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")); } @Test // DATAMONGO-1373 public void resolveIndexDefinitionInCustomComposedAnnotatedFields() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( IndexedDocumentWithComposedAnnotations.class); 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")); } @Document(collection = "Zero") static class IndexOnLevelZero { @Indexed String indexedProperty; } @Document(collection = "One") static class IndexOnLevelOne { IndexOnLevelZero zero; } @Document(collection = "Two") static class IndexOnLevelTwo { IndexOnLevelOne one; } @Document(collection = "WithOptionsOnIndexedProperty") static class WithOptionsOnIndexedProperty { @Indexed(background = true, direction = IndexDirection.DESCENDING, dropDups = true, expireAfterSeconds = 10, sparse = true, unique = true) // String indexedProperty; } @Document static class IndexOnLevelOneWithExplicitlyNamedField { @Field("customZero") IndexOnLevelZeroWithExplicityNamedField zero; } static class IndexOnLevelZeroWithExplicityNamedField { @Indexed @Field("customFieldName") String namedProperty; } @Document static class WrapperOfWithDbRef { WithDbRef nested; } @Document static class WithDbRef { @Indexed // @DBRef // NoIndex indexedDbRef; } @Document(collection = "no-index") static class NoIndex { @Id String id; } @Document static class IndexedDocumentWithComposedAnnotations { @Id String id; @CustomIndexedAnnotation String fieldWithDifferentIndexName; @ComposedIndexedAnnotation String fieldWithMyIndexName; } @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD }) @ComposedIndexedAnnotation(indexName = "different_name", beUnique = false) static @interface CustomIndexedAnnotation { } @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE }) @Indexed static @interface ComposedIndexedAnnotation { @AliasFor(annotation = Indexed.class, attribute = "unique") boolean beUnique() default true; @AliasFor(annotation = Indexed.class, attribute = "sparse") boolean beSparse() default true; @AliasFor(annotation = Indexed.class, attribute = "name") String indexName() default "my_index_name"; } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @org.springframework.data.mongodb.core.mapping.Field static @interface ComposedFieldAnnotation { @AliasFor(annotation = org.springframework.data.mongodb.core.mapping.Field.class, attribute = "value") String name() default "_id"; } } @Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) @Indexed @interface IndexedFieldAnnotation { } @Document static class IndexOnMetaAnnotatedField { @Field("_name") @IndexedFieldAnnotation String lastname; } /** * Test resolution of {@link GeoSpatialIndexed}. * * @author Christoph Strobl */ public static class GeoSpatialIndexResolutionTests { @Test // DATAMONGO-899 public void geoSpatialIndexPathOnLevelZeroIsResolvedCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( GeoSpatialIndexOnLevelZero.class); assertThat(indexDefinitions, hasSize(1)); assertIndexPathAndCollection("geoIndexedProperty", "Zero", indexDefinitions.get(0)); } @Test // DATAMONGO-899 public void geoSpatialIndexPathOnLevelOneIsResolvedCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( GeoSpatialIndexOnLevelOne.class); assertThat(indexDefinitions, hasSize(1)); assertIndexPathAndCollection("zero.geoIndexedProperty", "One", indexDefinitions.get(0)); } @Test // DATAMONGO-899 public void depplyNestedGeoSpatialIndexPathIsResolvedCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( GeoSpatialIndexOnLevelTwo.class); assertThat(indexDefinitions, hasSize(1)); assertIndexPathAndCollection("one.zero.geoIndexedProperty", "Two", indexDefinitions.get(0)); } @Test // DATAMONGO-899 public void resolvesIndexDefinitionOptionsCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( WithOptionsOnGeoSpatialIndexProperty.class); 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))); } @Test // DATAMONGO-1373 public void resolvesComposedAnnotationIndexDefinitionOptionsCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( GeoSpatialIndexedDocumentWithComposedAnnotation.class); 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)); } @Document(collection = "Zero") static class GeoSpatialIndexOnLevelZero { @GeoSpatialIndexed Point geoIndexedProperty; } @Document(collection = "One") static class GeoSpatialIndexOnLevelOne { GeoSpatialIndexOnLevelZero zero; } @Document(collection = "Two") static class GeoSpatialIndexOnLevelTwo { GeoSpatialIndexOnLevelOne one; } @Document(collection = "WithOptionsOnGeoSpatialIndexProperty") static class WithOptionsOnGeoSpatialIndexProperty { @GeoSpatialIndexed(bits = 2, max = 100, min = 1, type = GeoSpatialIndexType.GEO_2D) // Point location; } @Document(collection = "WithComposedAnnotation") static class GeoSpatialIndexedDocumentWithComposedAnnotation { @ComposedGeoSpatialIndexed // Point location; } @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD }) @GeoSpatialIndexed @interface ComposedGeoSpatialIndexed { @AliasFor(annotation = GeoSpatialIndexed.class, attribute = "name") String indexName() default "my_geo_index_name"; @AliasFor(annotation = GeoSpatialIndexed.class, attribute = "additionalField") String theAdditionalFieldINeedToDefine() default "What light?"; @AliasFor(annotation = GeoSpatialIndexed.class, attribute = "bucketSize") double size() default 2; @AliasFor(annotation = GeoSpatialIndexed.class, attribute = "type") GeoSpatialIndexType indexType() default GeoSpatialIndexType.GEO_HAYSTACK; } } /** * Test resolution of {@link CompoundIndexes}. * * @author Christoph Strobl */ public static class CompoundIndexResolutionTests { @Test // DATAMONGO-899 public void compoundIndexPathOnLevelZeroIsResolvedCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( CompoundIndexOnLevelZero.class); assertThat(indexDefinitions, hasSize(1)); assertIndexPathAndCollection(new String[] { "foo", "bar" }, "CompoundIndexOnLevelZero", indexDefinitions.get(0)); } @Test // DATAMONGO-899 public void compoundIndexOptionsResolvedCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( 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))); } @Test // DATAMONGO-909 public void compoundIndexOnSuperClassResolvedCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( 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))); } @Test // DATAMONGO-827 public void compoundIndexDoesNotSpecifyNameWhenUsingGenerateName() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( 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))); } @Test // DATAMONGO-929 public void compoundIndexPathOnLevelOneIsResolvedCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( CompoundIndexOnLevelOne.class); assertThat(indexDefinitions, hasSize(1)); assertIndexPathAndCollection(new String[] { "zero.foo", "zero.bar" }, "CompoundIndexOnLevelOne", indexDefinitions.get(0)); } @Test // DATAMONGO-929 public void emptyCompoundIndexPathOnLevelOneIsResolvedCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( CompoundIndexOnLevelOneWithEmptyIndexDefinition.class); assertThat(indexDefinitions, hasSize(1)); assertIndexPathAndCollection(new String[] { "zero" }, "CompoundIndexOnLevelZeroWithEmptyIndexDef", indexDefinitions.get(0)); } @Test // DATAMONGO-929 public void singleCompoundIndexPathOnLevelZeroIsResolvedCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( SingleCompoundIndex.class); assertThat(indexDefinitions, hasSize(1)); assertIndexPathAndCollection(new String[] { "foo", "bar" }, "CompoundIndexOnLevelZero", indexDefinitions.get(0)); } @Test // DATAMONGO-1373 public void singleCompoundIndexUsingComposedAnnotationsOnTypeResolvedCorrectly() { List<IndexDefinitionHolder> 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)); } @Document(collection = "CompoundIndexOnLevelOne") static class CompoundIndexOnLevelOne { CompoundIndexOnLevelZero zero; } @Document(collection = "CompoundIndexOnLevelZeroWithEmptyIndexDef") static class CompoundIndexOnLevelOneWithEmptyIndexDefinition { CompoundIndexOnLevelZeroWithEmptyIndexDef zero; } @Document(collection = "CompoundIndexOnLevelZero") @CompoundIndexes({ @CompoundIndex(name = "compound_index", def = "{'foo': 1, 'bar': -1}", background = true, dropDups = true, sparse = true, unique = true) }) static class CompoundIndexOnLevelZero {} @CompoundIndexes({ @CompoundIndex(name = "compound_index", background = true, dropDups = true, sparse = true, unique = true) }) static class CompoundIndexOnLevelZeroWithEmptyIndexDef {} @Document(collection = "CompoundIndexOnLevelZero") @CompoundIndex(name = "compound_index", def = "{'foo': 1, 'bar': -1}", background = true, dropDups = true, sparse = true, unique = true) static class SingleCompoundIndex {} static class IndexDefinedOnSuperClass extends CompoundIndexOnLevelZero { } @Document(collection = "ComountIndexWithAutogeneratedName") @CompoundIndexes({ @CompoundIndex(useGeneratedName = true, def = "{'foo': 1, 'bar': -1}", background = true, dropDups = true, sparse = true, unique = true) }) static class ComountIndexWithAutogeneratedName { } @Document(collection = "WithComposedAnnotation") @ComposedCompoundIndex static class CompoundIndexDocumentWithComposedAnnotation { } @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE }) @CompoundIndex @interface ComposedCompoundIndex { @AliasFor(annotation = CompoundIndex.class, attribute = "def") String fields() default "{'foo': 1, 'bar': -1}"; @AliasFor(annotation = CompoundIndex.class, attribute = "background") boolean inBackground() default true; @AliasFor(annotation = CompoundIndex.class, attribute = "name") String indexName() default "my_compound_index_name"; @AliasFor(annotation = CompoundIndex.class, attribute = "useGeneratedName") boolean useGeneratedName() default false; @AliasFor(annotation = CompoundIndex.class, attribute = "unique") boolean isUnique() default true; } } public static class TextIndexedResolutionTests { @Test // DATAMONGO-937 public void shouldResolveSingleFieldTextIndexCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( TextIndexOnSinglePropertyInRoot.class); assertThat(indexDefinitions.size(), equalTo(1)); assertIndexPathAndCollection("bar", "textIndexOnSinglePropertyInRoot", indexDefinitions.get(0)); } @Test // DATAMONGO-937 public void shouldResolveMultiFieldTextIndexCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( TextIndexOnMutiplePropertiesInRoot.class); assertThat(indexDefinitions.size(), equalTo(1)); assertIndexPathAndCollection(new String[] { "foo", "bar" }, "textIndexOnMutiplePropertiesInRoot", indexDefinitions.get(0)); } @Test // DATAMONGO-937 public void shouldResolveTextIndexOnElementCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( TextIndexOnNestedRoot.class); assertThat(indexDefinitions.size(), equalTo(1)); assertIndexPathAndCollection(new String[] { "nested.foo" }, "textIndexOnNestedRoot", indexDefinitions.get(0)); } @Test // DATAMONGO-937 public void shouldResolveTextIndexOnElementWithWeightCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( TextIndexOnNestedWithWeightRoot.class); assertThat(indexDefinitions.size(), equalTo(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)); } @Test // DATAMONGO-937 public void shouldResolveTextIndexOnElementWithMostSpecificWeightCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( TextIndexOnNestedWithMostSpecificValueRoot.class); assertThat(indexDefinitions.size(), equalTo(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)); } @Test // DATAMONGO-937 public void shouldSetDefaultLanguageCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( DocumentWithDefaultLanguage.class); assertThat(indexDefinitions.get(0).getIndexOptions().get("default_language"), is((Object) "spanish")); } @Test // DATAMONGO-937, DATAMONGO-1049 public void shouldResolveTextIndexLanguageOverrideCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( DocumentWithLanguageOverride.class); assertThat(indexDefinitions.get(0).getIndexOptions().get("language_override"), is((Object) "lang")); } @Test // DATAMONGO-1049 public void shouldIgnoreTextIndexLanguageOverrideOnNestedElements() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( DocumentWithLanguageOverrideOnNestedElement.class); assertThat(indexDefinitions.get(0).getIndexOptions().get("language_override"), is(nullValue())); } @Test // DATAMONGO-1049 public void shouldNotCreateIndexDefinitionWhenOnlyLanguageButNoTextIndexPresent() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( DocumentWithNoTextIndexPropertyButReservedFieldLanguage.class); assertThat(indexDefinitions, is(empty())); } @Test // DATAMONGO-1049 public void shouldNotCreateIndexDefinitionWhenOnlyAnnotatedLanguageButNoTextIndexPresent() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( DocumentWithNoTextIndexPropertyButReservedFieldLanguageAnnotated.class); assertThat(indexDefinitions, is(empty())); } @Test // DATAMONGO-1049 public void shouldPreferExplicitlyAnnotatedLanguageProperty() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( DocumentWithOverlappingLanguageProps.class); assertThat(indexDefinitions.get(0).getIndexOptions().get("language_override"), is((Object) "lang")); } @Test // DATAMONGO-1373 public void shouldResolveComposedAnnotationCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( TextIndexedDocumentWithComposedAnnotation.class); org.bson.Document weights = DocumentTestUtils.getAsDocument(indexDefinitions.get(0).getIndexOptions(), "weights"); assertThat(weights, isBsonObject().containing("foo", 99f)); } @Document static class TextIndexOnSinglePropertyInRoot { String foo; @TextIndexed String bar; } @Document static class TextIndexOnMutiplePropertiesInRoot { @TextIndexed String foo; @TextIndexed(weight = 5) String bar; } @Document static class TextIndexOnNestedRoot { String bar; @TextIndexed TextIndexOnNested nested; } static class TextIndexOnNested { String foo; } @Document static class TextIndexOnNestedWithWeightRoot { @TextIndexed(weight = 5) TextIndexOnNested nested; } @Document static class TextIndexOnNestedWithMostSpecificValueRoot { @TextIndexed(weight = 5) TextIndexOnNestedWithMostSpecificValue nested; } static class TextIndexOnNestedWithMostSpecificValue { String foo; @TextIndexed(weight = 10) String bar; } @Document(language = "spanish") static class DocumentWithDefaultLanguage { @TextIndexed String foo; } @Document static class DocumentWithLanguageOverrideOnNestedElement { DocumentWithLanguageOverride nested; } @Document static class DocumentWithLanguageOverride { @TextIndexed String foo; @Language String lang; } @Document static class DocumentWithNoTextIndexPropertyButReservedFieldLanguage { String language; } @Document static class DocumentWithNoTextIndexPropertyButReservedFieldLanguageAnnotated { @Field("language") String lang; } @Document static class DocumentWithOverlappingLanguageProps { @TextIndexed String foo; String language; @Language String lang; } @Document static class TextIndexedDocumentWithComposedAnnotation { @ComposedTextIndexedAnnotation String foo; String lang; } @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE }) @TextIndexed static @interface ComposedTextIndexedAnnotation { @AliasFor(annotation = TextIndexed.class, attribute = "weight") float heavyweight() default 99f; } } public static class MixedIndexResolutionTests { @Test // DATAMONGO-899 public void multipleIndexesResolvedCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(MixedIndexRoot.class); assertThat(indexDefinitions, hasSize(2)); assertThat(indexDefinitions.get(0).getIndexDefinition(), instanceOf(Index.class)); assertThat(indexDefinitions.get(1).getIndexDefinition(), instanceOf(GeospatialIndex.class)); } @Test // DATAMONGO-899 public void cyclicPropertyReferenceOverDBRefShouldNotBeTraversed() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(Inner.class); assertThat(indexDefinitions, hasSize(1)); assertThat(indexDefinitions.get(0).getIndexDefinition().getIndexKeys(), equalTo(new org.bson.Document().append("outer", 1))); } @Test // DATAMONGO-899 public void associationsShouldNotBeTraversed() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(Outer.class); assertThat(indexDefinitions, empty()); } @Test // DATAMONGO-926 public void shouldNotRunIntoStackOverflow() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( CycleStartingInBetween.class); assertThat(indexDefinitions, hasSize(1)); } @Test // DATAMONGO-926 public void indexShouldBeFoundEvenForCyclePropertyReferenceOnLevelZero() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CycleLevelZero.class); assertIndexPathAndCollection("indexedProperty", "cycleLevelZero", indexDefinitions.get(0)); assertIndexPathAndCollection("cyclicReference.indexedProperty", "cycleLevelZero", indexDefinitions.get(1)); assertThat(indexDefinitions, hasSize(2)); } @Test // DATAMONGO-926 public void indexShouldBeFoundEvenForCyclePropertyReferenceOnLevelOne() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType(CycleOnLevelOne.class); assertIndexPathAndCollection("reference.indexedProperty", "cycleOnLevelOne", indexDefinitions.get(0)); assertThat(indexDefinitions, hasSize(1)); } @Test // DATAMONGO-926 public void indexBeResolvedCorrectlyWhenPropertiesOfDifferentTypesAreNamedEqually() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( NoCycleButIdenticallyNamedProperties.class); 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 public void shouldNotDetectCycleInSimilarlyNamedProperties() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( SimilarityHolingBean.class); assertIndexPathAndCollection("norm", "similarityHolingBean", indexDefinitions.get(0)); assertThat(indexDefinitions, hasSize(1)); } @Test // DATAMONGO-962 public void shouldDetectSelfCycleViaCollectionTypeCorrectly() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( SelfCyclingViaCollectionType.class); assertThat(indexDefinitions, empty()); } @Test // DATAMONGO-962 public void shouldNotDetectCycleWhenTypeIsUsedMoreThanOnce() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( MultipleObjectsOfSameType.class); assertThat(indexDefinitions, empty()); } @Test // DATAMONGO-962 @SuppressWarnings({ "rawtypes", "unchecked" }) public void shouldCatchCyclicReferenceExceptionOnRoot() { MongoPersistentEntity entity = new BasicMongoPersistentEntity<Object>(ClassTypeInformation.from(Object.class)); MongoPersistentProperty propertyMock = mock(MongoPersistentProperty.class); when(propertyMock.isEntity()).thenReturn(true); when(propertyMock.getOwner()).thenReturn(entity); when(propertyMock.getActualType()).thenThrow( new MongoPersistentEntityIndexResolver.CyclicPropertyReferenceException("foo", Object.class, "bar")); MongoPersistentEntity<SelfCyclingViaCollectionType> selfCyclingEntity = new BasicMongoPersistentEntity<SelfCyclingViaCollectionType>( ClassTypeInformation.from(SelfCyclingViaCollectionType.class)); new MongoPersistentEntityIndexResolver(prepareMappingContext(SelfCyclingViaCollectionType.class)) .resolveIndexForEntity(selfCyclingEntity); } @Test // DATAMONGO-1025 public void shouldUsePathIndexAsIndexNameForDocumentsHavingNamedNestedCompoundIndexFixedOnCollection() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( DocumentWithNestedDocumentHavingNamedCompoundIndex.class); assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"), equalTo("propertyOfTypeHavingNamedCompoundIndex.c_index")); } @Test // DATAMONGO-1025 public void shouldUseIndexNameForNestedTypesWithNamedCompoundIndexDefinition() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( DocumentWithNestedTypeHavingNamedCompoundIndex.class); assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"), equalTo("propertyOfTypeHavingNamedCompoundIndex.c_index")); } @Test // DATAMONGO-1025 public void shouldUsePathIndexAsIndexNameForDocumentsHavingNamedNestedIndexFixedOnCollection() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( DocumentWithNestedDocumentHavingNamedIndex.class); assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"), equalTo("propertyOfTypeHavingNamedIndex.property_index")); } @Test // DATAMONGO-1025 public void shouldUseIndexNameForNestedTypesWithNamedIndexDefinition() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( DocumentWithNestedTypeHavingNamedIndex.class); assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"), equalTo("propertyOfTypeHavingNamedIndex.property_index")); } @Test // DATAMONGO-1025 public void shouldUseIndexNameOnRootLevel() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( DocumentWithNamedIndex.class); assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"), equalTo("property_index")); } @Test // DATAMONGO-1087 public void shouldAllowMultiplePropertiesOfSameTypeWithMatchingStartLettersOnRoot() { List<IndexDefinitionHolder> 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")); } @Test // DATAMONGO-1087 public void shouldAllowMultiplePropertiesOfSameTypeWithMatchingStartLettersOnNestedProperty() { List<IndexDefinitionHolder> 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")); } @Test // DATAMONGO-1121 public void shouldOnlyConsiderEntitiesAsPotentialCycleCandidates() { List<IndexDefinitionHolder> 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")); } @Test // DATAMONGO-1263 public void shouldConsiderGenericTypeArgumentsOfCollectionElements() { List<IndexDefinitionHolder> indexDefinitions = prepareMappingContextAndResolveIndexForType( EntityWithGenericTypeWrapperAsElement.class); assertThat(indexDefinitions, hasSize(1)); assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"), equalTo("listWithGeneircTypeElement.entity.property_index")); } @Document static class MixedIndexRoot { @Indexed String first; NestedGeoIndex nestedGeo; } static class NestedGeoIndex { @GeoSpatialIndexed Point location; } @Document static class Outer { @DBRef Inner inner; } @Document static class Inner { @Indexed Outer outer; } @Document static class CycleLevelZero { @Indexed String indexedProperty; CycleLevelZero cyclicReference; } @Document static class CycleOnLevelOne { CycleOnLevelOneReferenced reference; } static class CycleOnLevelOneReferenced { @Indexed String indexedProperty; CycleOnLevelOne cyclicReference; } @Document public static class CycleStartingInBetween { CycleOnLevelOne referenceToCycleStart; } @Document static class NoCycleButIdenticallyNamedProperties { @Indexed String foo; NoCycleButIdenticallyNamedPropertiesNested reference; } static class NoCycleButIdenticallyNamedPropertiesNested { @Indexed String foo; NoCycleButIndenticallNamedPropertiesDeeplyNested deep; } static class NoCycleButIndenticallNamedPropertiesDeeplyNested { @Indexed String foo; } @Document static class SimilarityHolingBean { @Indexed @Field("norm") String normalProperty; @Field("similarityL") private List<SimilaritySibling> listOfSimilarilyNamedEntities = null; } static class SimilaritySibling { @Field("similarity") private String similarThoughNotEqualNamedProperty; } @Document static class MultipleObjectsOfSameType { SelfCyclingViaCollectionType cycleOne; SelfCyclingViaCollectionType cycleTwo; } @Document static class SelfCyclingViaCollectionType { List<SelfCyclingViaCollectionType> cyclic; } @Document @CompoundIndex(name = "c_index", def = "{ foo:1, bar:1 }") static class DocumentWithNamedCompoundIndex { String property; } @Document static class DocumentWithNamedIndex { @Indexed(name = "property_index") String property; } static class TypeWithNamedIndex { @Indexed(name = "property_index") String property; } @Document static class DocumentWithNestedDocumentHavingNamedCompoundIndex { DocumentWithNamedCompoundIndex propertyOfTypeHavingNamedCompoundIndex; } @CompoundIndex(name = "c_index", def = "{ foo:1, bar:1 }") static class TypeWithNamedCompoundIndex { String property; } @Document static class DocumentWithNestedTypeHavingNamedCompoundIndex { TypeWithNamedCompoundIndex propertyOfTypeHavingNamedCompoundIndex; } @Document static class DocumentWithNestedDocumentHavingNamedIndex { DocumentWithNamedIndex propertyOfTypeHavingNamedIndex; } @Document static class DocumentWithNestedTypeHavingNamedIndex { TypeWithNamedIndex propertyOfTypeHavingNamedIndex; } @Document public class MultiplePropertiesOfSameTypeWithMatchingStartLetters { public class NameComponent { @Indexed String component; } NameComponent name; NameComponent nameLast; } @Document public class MultiplePropertiesOfSameTypeWithMatchingStartLettersOnNestedProperty { public class NameComponent { @Indexed String nameLast; @Indexed String name; } NameComponent component; } @Document public static class OuterDocumentReferingToIndexedPropertyViaDifferentNonCyclingPaths { NoCycleButIndenticallNamedPropertiesDeeplyNested path1; AlternatePathToNoCycleButIndenticallNamedPropertiesDeeplyNestedDocument path2; } public static class AlternatePathToNoCycleButIndenticallNamedPropertiesDeeplyNestedDocument { NoCycleButIndenticallNamedPropertiesDeeplyNested propertyWithIndexedStructure; } static class GenericEntityWrapper<T> { T entity; } @Document static class EntityWithGenericTypeWrapperAsElement { List<GenericEntityWrapper<DocumentWithNamedIndex>> listWithGeneircTypeElement; } } private static List<IndexDefinitionHolder> prepareMappingContextAndResolveIndexForType(Class<?> type) { MongoMappingContext mappingContext = prepareMappingContext(type); MongoPersistentEntityIndexResolver resolver = new MongoPersistentEntityIndexResolver(mappingContext); return resolver.resolveIndexForEntity(mappingContext.getRequiredPersistentEntity(type)); } private static MongoMappingContext prepareMappingContext(Class<?> type) { MongoMappingContext mappingContext = new MongoMappingContext(); mappingContext.setInitialEntitySet(Collections.singleton(type)); mappingContext.initialize(); return mappingContext; } private static void assertIndexPathAndCollection(String expectedPath, String expectedCollection, IndexDefinitionHolder holder) { assertIndexPathAndCollection(new String[] { expectedPath }, expectedCollection, holder); } private static void assertIndexPathAndCollection(String[] expectedPaths, String expectedCollection, IndexDefinitionHolder holder) { for (String expectedPath : expectedPaths) { assertThat(holder.getIndexDefinition().getIndexKeys().containsKey(expectedPath), equalTo(true)); } assertThat(holder.getCollection(), equalTo(expectedCollection)); } }