diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapper.java index 606221deb4..402b478720 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapper.java @@ -47,6 +47,7 @@ * @author Thomas Darimont * @author Christoph Strobl * @author Mark Paluch + * @author Roman Puchkovskiy */ public class DefaultMongoTypeMapper extends DefaultTypeMapper implements MongoTypeMapper { @@ -154,7 +155,9 @@ public void writeTypeRestrictions(Document result, @Nullable Set> restr } } - accessor.writeTypeTo(result, new Document("$in", restrictedMappedTypes)); + if (!restrictedMappedTypes.isEmpty()) { + accessor.writeTypeTo(result, new Document("$in", restrictedMappedTypes)); + } } /* diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java index f7c308a2f2..111b5b48e2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java @@ -15,6 +15,7 @@ */ package org.springframework.data.mongodb.core; +import static java.util.Collections.*; import static org.assertj.core.api.Assertions.*; import static org.springframework.data.mongodb.core.query.Criteria.*; import static org.springframework.data.mongodb.core.query.Query.*; @@ -39,6 +40,7 @@ import java.util.stream.IntStream; import org.bson.types.ObjectId; +import org.jetbrains.annotations.NotNull; import org.joda.time.DateTime; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -57,19 +59,27 @@ import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.annotation.Version; import org.springframework.data.auditing.IsNewAwareAuditingHandler; +import org.springframework.data.convert.MappingContextTypeInformationMapper; +import org.springframework.data.domain.Example; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.mapping.MappingException; import org.springframework.data.mongodb.InvalidMongoDbApiUsageException; import org.springframework.data.mongodb.MongoDatabaseFactory; +import org.springframework.data.mongodb.core.convert.DbRefResolver; +import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper; import org.springframework.data.mongodb.core.convert.LazyLoadingProxy; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; import org.springframework.data.mongodb.core.geo.GeoJsonPoint; import org.springframework.data.mongodb.core.index.Index; import org.springframework.data.mongodb.core.index.IndexField; import org.springframework.data.mongodb.core.index.IndexInfo; import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.MongoId; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener; import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent; import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent; @@ -117,6 +127,7 @@ * @author Mark Paluch * @author Laszlo Csontos * @author duozhilin + * @author Roman Puchkovskiy */ @ExtendWith(MongoClientExtension.class) public class MongoTemplateTests { @@ -2185,7 +2196,7 @@ public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentW entry.put("key2", new ModelA("value2")); Query query = query(where("id").is(doc.id)); - Update update = Update.update("models", Collections.singletonList(entry)); + Update update = Update.update("models", singletonList(entry)); assertThat(template.findOne(query, DocumentWithNestedCollection.class)).isNotNull(); @@ -3474,7 +3485,7 @@ public void onBeforeSave(BeforeSaveEvent event) { Document document = new Document(); - template.insertAll(Collections.singletonList(document)); + template.insertAll(singletonList(document)); assertThat(document.id).isNotNull(); } @@ -3486,7 +3497,7 @@ public void afterSaveEventContainsSavedObjectUsingInsertAll() { AtomicReference saved = createAfterSaveReference(); ImmutableVersioned source = new ImmutableVersioned(); - template.insertAll(Collections.singletonList(source)); + template.insertAll(singletonList(source)); assertThat(saved.get()).isNotNull(); assertThat(saved.get()).isNotSameAs(source); @@ -3511,7 +3522,7 @@ public void afterSaveEventContainsSavedObjectUsingInsert() { @Test // DATAMONGO-1509 public void findsByGenericNestedListElements() { - List modelList = Collections.singletonList(new ModelA("value")); + List modelList = singletonList(new ModelA("value")); DocumentWithCollection dwc = new DocumentWithCollection(modelList); template.insert(dwc); @@ -3683,6 +3694,40 @@ public void sortOnIdFieldWithExplicitTypeShouldWork() { assertThat(template.find(new BasicQuery("{}").with(Sort.by("id")), WithIdAndFieldAnnotation.class)).isNotEmpty(); } + @Test // DATAMONGO-2620 + void alikeQueryShouldFindProperlyWhenNoClassAttributeIsSaved() { + MongoTemplate templateSavingNoTypeInfoInDb = createTemplateSavingNoTypeInfo(); + + Person john = new Person(); + john.setFirstName("John"); + + Person savedPerson = templateSavingNoTypeInfoInDb.save(john); + + Criteria alikeCriteria = new Criteria().alike(Example.of(savedPerson)); + List foundPeople = templateSavingNoTypeInfoInDb.find(new Query(alikeCriteria), Person.class); + + assertThat(foundPeople).hasSize(1); + assertThat(foundPeople.get(0).getId()).isEqualTo(savedPerson.getId()); + } + + @NotNull + private MongoTemplate createTemplateSavingNoTypeInfo() { + MongoMappingContext mappingContext = new MongoMappingContext(); + mappingContext.setInitialEntitySet(singleton(Person.class)); + mappingContext.setAutoIndexCreation(false); + mappingContext.afterPropertiesSet(); + + MappingContextTypeInformationMapper typeInformationMapper = new MappingContextTypeInformationMapper( + mappingContext); + DefaultMongoTypeMapper typeMapper = new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, + singletonList(typeInformationMapper)); + + MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext); + converter.setTypeMapper(typeMapper); + + return new MongoTemplate(new SimpleMongoClientDatabaseFactory(client, DB_NAME), converter); + } + private AtomicReference createAfterSaveReference() { AtomicReference saved = new AtomicReference<>(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java index 6b125629c7..ea83006dc1 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java @@ -15,6 +15,8 @@ */ package org.springframework.data.mongodb.core; +import static java.util.Collections.*; +import static java.util.stream.Collectors.*; import static org.assertj.core.api.Assertions.*; import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; import static org.springframework.data.mongodb.core.query.Criteria.*; @@ -24,6 +26,13 @@ import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Wither; +import org.jetbrains.annotations.NotNull; +import org.springframework.data.convert.MappingContextTypeInformationMapper; +import org.springframework.data.domain.Example; +import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import reactor.core.Disposable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -40,7 +49,6 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; import java.util.stream.IntStream; import org.bson.BsonDocument; @@ -93,6 +101,7 @@ * * @author Mark Paluch * @author Christoph Strobl + * @author Roman Puchkovskiy */ @ExtendWith({ MongoClientExtension.class, MongoServerCondition.class }) public class ReactiveMongoTemplateTests { @@ -1393,7 +1402,7 @@ void changeStreamEventsShouldBeEmittedCorrectly() throws InterruptedException { Thread.sleep(500); // just give it some time to link receive all events try { - assertThat(documents.stream().map(ChangeStreamEvent::getBody).collect(Collectors.toList())).hasSize(3) + assertThat(documents.stream().map(ChangeStreamEvent::getBody).collect(toList())).hasSize(3) .allMatch(val -> val instanceof Document); } finally { disposable.dispose(); @@ -1425,7 +1434,7 @@ void changeStreamEventsShouldBeConvertedCorrectly() throws InterruptedException Thread.sleep(500); // just give it some time to link receive all events try { - assertThat(documents.stream().map(ChangeStreamEvent::getBody).collect(Collectors.toList())) + assertThat(documents.stream().map(ChangeStreamEvent::getBody).collect(toList())) .containsExactly(person1, person2, person3); } finally { disposable.dispose(); @@ -1458,7 +1467,7 @@ void changeStreamEventsShouldBeFilteredCorrectly() throws InterruptedException { Thread.sleep(500); // just give it some time to link receive all events try { - assertThat(documents.stream().map(ChangeStreamEvent::getBody).collect(Collectors.toList())) + assertThat(documents.stream().map(ChangeStreamEvent::getBody).collect(toList())) .containsExactly(person1, person3); } finally { disposable.dispose(); @@ -1501,7 +1510,7 @@ void mapsReservedWordsCorrectly() throws InterruptedException { Thread.sleep(500); // just give it some time to link receive all events try { - assertThat(documents.stream().map(ChangeStreamEvent::getBody).collect(Collectors.toList())) + assertThat(documents.stream().map(ChangeStreamEvent::getBody).collect(toList())) .containsExactly(replacement); } finally { disposable.dispose(); @@ -1543,7 +1552,7 @@ void changeStreamEventsShouldBeResumedCorrectly() throws InterruptedException { Thread.sleep(500); // just give it some time to link receive all events try { - assertThat(resumeDocuments.stream().map(ChangeStreamEvent::getBody).collect(Collectors.toList())) + assertThat(resumeDocuments.stream().map(ChangeStreamEvent::getBody).collect(toList())) .containsExactly(person2, person3); } finally { disposable.dispose(); @@ -1556,7 +1565,7 @@ void removeShouldConsiderLimit() { List samples = IntStream.range(0, 100) // .mapToObj(i -> new Sample("id-" + i, i % 2 == 0 ? "stark" : "lannister")) // - .collect(Collectors.toList()); + .collect(toList()); template.insertAll(samples) // .as(StepVerifier::create) // @@ -1573,7 +1582,7 @@ void removeShouldConsiderSkipAndSort() { List samples = IntStream.range(0, 100) // .mapToObj(i -> new Sample("id-" + i, i % 2 == 0 ? "stark" : "lannister")) // - .collect(Collectors.toList()); + .collect(toList()); template.insertAll(samples).as(StepVerifier::create).expectNextCount(100).verifyComplete(); @@ -1653,7 +1662,7 @@ void watchesDatabaseCorrectly() throws InterruptedException { Thread.sleep(500); // just give it some time to link receive all events try { - assertThat(documents.stream().map(ChangeStreamEvent::getBody).collect(Collectors.toList())) + assertThat(documents.stream().map(ChangeStreamEvent::getBody).collect(toList())) .containsExactly(person1, person2, person3); } finally { disposable.dispose(); @@ -1704,7 +1713,7 @@ void resumesAtTimestampCorrectly() throws InterruptedException { Thread.sleep(500); // just give it some time to link receive all events try { - assertThat(resumeDocuments.stream().map(ChangeStreamEvent::getBody).collect(Collectors.toList())) + assertThat(resumeDocuments.stream().map(ChangeStreamEvent::getBody).collect(toList())) .containsExactly(person2, person3); } finally { disposable.dispose(); @@ -1777,6 +1786,41 @@ public void onAfterSave(AfterSaveEvent event) { return saved; } + @Test // DATAMONGO-2620 + void alikeQueryShouldFindProperlyWhenNoClassAttributeIsSaved() { + ReactiveMongoTemplate templateSavingNoTypeInfoInDb = createTemplateSavingNoTypeInfo(); + + Person john = new Person(); + john.setFirstName("John"); + + Person savedPerson = templateSavingNoTypeInfoInDb.save(john).block(); + + Criteria alikeCriteria = new Criteria().alike(Example.of(savedPerson)); + List foundPeople = templateSavingNoTypeInfoInDb.find(new Query(alikeCriteria), Person.class) + .toStream().collect(toList()); + + assertThat(foundPeople).hasSize(1); + assertThat(foundPeople.get(0).getId()).isEqualTo(savedPerson.getId()); + } + + @NotNull + private ReactiveMongoTemplate createTemplateSavingNoTypeInfo() { + MongoMappingContext mappingContext = new MongoMappingContext(); + mappingContext.setInitialEntitySet(singleton(Person.class)); + mappingContext.setAutoIndexCreation(false); + mappingContext.afterPropertiesSet(); + + MappingContextTypeInformationMapper typeInformationMapper = new MappingContextTypeInformationMapper( + mappingContext); + DefaultMongoTypeMapper typeMapper = new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, + singletonList(typeInformationMapper)); + + MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext); + converter.setTypeMapper(typeMapper); + + return new ReactiveMongoTemplate(new SimpleReactiveMongoDatabaseFactory(client, DB_NAME), converter); + } + @AllArgsConstructor @Wither static class ImmutableVersioned { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapperUnitTests.java index f09265ba6f..01b43db963 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapperUnitTests.java @@ -15,7 +15,9 @@ */ package org.springframework.data.mongodb.core.convert; +import static java.util.Collections.*; import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import java.util.Arrays; import java.util.Collections; @@ -25,8 +27,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import org.springframework.data.convert.ConfigurableTypeInformationMapper; import org.springframework.data.convert.SimpleTypeInformationMapper; +import org.springframework.data.convert.TypeInformationMapper; +import org.springframework.data.mapping.Alias; import org.springframework.data.mongodb.core.DocumentTestUtils; import org.springframework.data.util.TypeInformation; @@ -34,6 +39,7 @@ * Unit tests for {@link DefaultMongoTypeMapper}. * * @author Oliver Gierke + * @author Roman Puchkovskiy */ public class DefaultMongoTypeMapperUnitTests { @@ -184,6 +190,19 @@ public void returnsCorrectTypeKey() { assertThat(typeMapper.isTypeKey(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY)).isFalse(); } + @Test // DATAMONGO-2620 + void givenTypeAliasesAreNotAvailable_whenWritingTypeRestrictions_thenDoesNotAddInConditionWithEmptySet() { + TypeInformationMapper informationMapperMaintainingNoAliasInfo = mock(TypeInformationMapper.class); + when(informationMapperMaintainingNoAliasInfo.createAliasFor(any())).thenReturn(Alias.NONE); + typeMapper = new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, + singletonList(informationMapperMaintainingNoAliasInfo)); + + Document document = new Document(); + typeMapper.writeTypeRestrictions(document, singleton(Integer.class)); + + assertThat(document).isEmpty(); + } + private void readsTypeFromField(Document document, Class type) { TypeInformation typeInfo = typeMapper.readType(document);