diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegate.java index a0e5a6f2bb..088b27e70b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegate.java @@ -116,7 +116,7 @@ public ReferenceLookupDelegate( } if (!result.iterator().hasNext()) { - return null; + return property.isMap() ? Collections.emptyMap() : null; } Object resultValue = result.iterator().next(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java index 75c7cc4366..0add71d50e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.Collections; +import java.util.Map; import org.bson.Document; import org.bson.types.ObjectId; @@ -35,6 +36,8 @@ import org.mockito.quality.Strictness; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.model.SpELContext; import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.core.DocumentTestUtils; import org.springframework.data.mongodb.core.MongoExceptionTranslator; @@ -43,6 +46,9 @@ import com.mongodb.client.FindIterable; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; +import org.springframework.data.mongodb.core.mapping.DocumentReference; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; /** * Unit tests for {@link DefaultDbRefResolver}. @@ -58,6 +64,8 @@ class DefaultDbRefResolverUnitTests { @Mock MongoDatabase dbMock; @Mock MongoCollection collectionMock; @Mock FindIterable cursorMock; + @Mock MappingContext, MongoPersistentProperty> mappingContext; + @Mock SpELContext spELContext; private DefaultDbRefResolver resolver; @BeforeEach @@ -91,7 +99,7 @@ void bulkFetchShouldLoadDbRefsCorrectly() { } @Test // DATAMONGO-1194 - void bulkFetchShouldThrowExceptionWhenUsingDifferntCollectionsWithinSetOfReferences() { + void bulkFetchShouldThrowExceptionWhenUsingDifferentCollectionsWithinSetOfReferences() { DBRef ref1 = new DBRef("collection-1", new ObjectId()); DBRef ref2 = new DBRef("collection-2", new ObjectId()); @@ -134,4 +142,55 @@ void bulkFetchContainsDuplicates() { assertThat(resolver.bulkFetch(Arrays.asList(ref1, ref2))).containsExactly(document, document); } + + @Test // GH-5065 + void emptyMapWithDocumentReferenceAnnotationShouldDeserializeToAnEmptyMap() { + DocumentReference documentReference = mock(DocumentReference.class); + when(documentReference.lookup()).thenReturn("{ '_id' : ?#{#target} }"); + when(documentReference.sort()).thenReturn(""); + when(documentReference.lazy()).thenReturn(false); + MongoPersistentProperty property = mock(MongoPersistentProperty.class); + when(property.isCollectionLike()).thenReturn(false); + when(property.isMap()).thenReturn(true); + when(property.isDocumentReference()).thenReturn(true); + when(property.getDocumentReference()).thenReturn(documentReference); + DocumentReferenceSource source = mock(DocumentReferenceSource.class); + when(source.getTargetSource()).thenReturn(Document.parse("{}")); + ReferenceLookupDelegate lookupDelegate = new ReferenceLookupDelegate(mappingContext, spELContext); + + ReferenceResolver.MongoEntityReader entityReader = mock(ReferenceResolver.MongoEntityReader.class); + + Object target = resolver.resolveReference(property, source, lookupDelegate, entityReader); + + assertThat(target) + .isNotNull() + .isInstanceOf(Map.class); + } + + @Test // GH-5065 + void lazyLoadedEmptyMapWithDocumentReferenceAnnotationShouldDeserializeToAnEmptyMapWithANonnullValuesProperty() { + DocumentReference documentReference = mock(DocumentReference.class); + when(documentReference.lookup()).thenReturn("{ '_id' : ?#{#target} }"); + when(documentReference.sort()).thenReturn(""); + when(documentReference.lazy()).thenReturn(true); + MongoPersistentProperty property = mock(MongoPersistentProperty.class); + when(property.isCollectionLike()).thenReturn(false); + when(property.isMap()).thenReturn(true); + when(property.isDocumentReference()).thenReturn(true); + when(property.getDocumentReference()).thenReturn(documentReference); + //noinspection rawtypes,unchecked + when(property.getType()).thenReturn((Class) Map.class); + DocumentReferenceSource source = mock(DocumentReferenceSource.class); + when(source.getTargetSource()).thenReturn(Document.parse("{}")); + ReferenceLookupDelegate lookupDelegate = new ReferenceLookupDelegate(mappingContext, spELContext); + + ReferenceResolver.MongoEntityReader entityReader = mock(ReferenceResolver.MongoEntityReader.class); + + Object target = resolver.resolveReference(property, source, lookupDelegate, entityReader); + + assertThat(target) + .isNotNull() + .isInstanceOf(Map.class) + .asInstanceOf(MAP).values().isNotNull(); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java index be8dbced9e..696acd69f5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterTests.java @@ -34,6 +34,7 @@ import org.bson.Document; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.data.annotation.Id; @@ -41,6 +42,7 @@ import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; import org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter; import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.data.mongodb.core.mapping.DocumentReference; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.test.util.Client; @@ -54,13 +56,13 @@ * @author Christoph Strobl */ -public class MappingMongoConverterTests { +class MappingMongoConverterTests { private static final String DATABASE = "mapping-converter-tests"; private static @Client MongoClient client; - private MongoDatabaseFactory factory = new SimpleMongoClientDatabaseFactory(client, DATABASE); + private final MongoDatabaseFactory factory = new SimpleMongoClientDatabaseFactory(client, DATABASE); private MappingMongoConverter converter; private MongoMappingContext mappingContext; @@ -478,4 +480,478 @@ public String toString() { + ", localTime=" + this.getLocalTime() + ", localDateTime=" + this.getLocalDateTime() + ")"; } } + + @Nested // GH-5065 + class EmptyMapTests { + + @Test // GH-5065 + void controlTestToIllustrateThatTheEmptyMapProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblem() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + //EmptyMapDocument target = converter.read(EmptyMapDocument.class, document); + assertThat(converter.read(EmptyMapDocument.class, document).map).isNotNull().isEmpty(); + assertThat(converter.read(EmptyMapDocument.class, document).getMap()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void controlTestToIllustrateThatTheNullMapProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblem() { + org.bson.Document document = org.bson.Document.parse("{\"map\":null}"); + //EmptyMapDocument target = converter.read(EmptyMapDocument.class, document); + assertThat(converter.read(EmptyMapDocument.class, document).map).isNull(); + assertThat(converter.read(EmptyMapDocument.class, document).getMap()).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyMapWhenUsingDocumentReferenceAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document); + assertThat(target.map).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyMapWhenUsingDocumentReferenceAnnotationUsingGetMap() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document); + assertThat(target.getMap()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullMapWhenUsingDocumentReferenceAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"map\":null}"); + DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document); + assertThat(target.map).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullMapWhenUsingDocumentReferenceAnnotationUsingGetMap() { + org.bson.Document document = org.bson.Document.parse("{\"map\":null}"); + DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document); + assertThat(target.getMap()).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyMapWhenUsingDocumentReferenceAnnotationWithLazyEqualToTrue() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + LazyDocumentReferenceEmptyMapDocument target = converter.read(LazyDocumentReferenceEmptyMapDocument.class, document); + assertThat(target.map).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyMapWithAValidValuesPropertyWhenUsingDocumentReferenceAnnotationWithLazyEqualToTrue() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + LazyDocumentReferenceEmptyMapDocument target = converter.read(LazyDocumentReferenceEmptyMapDocument.class, document); + assertThat(target.map.values()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyMapWhenUsingDocumentReferenceAnnotationWithLazyEqualToTrueUsingGetMap() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + LazyDocumentReferenceEmptyMapDocument target = converter.read(LazyDocumentReferenceEmptyMapDocument.class, document); + assertThat(target.getMap()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyMapWithAValidValuesPropertyWhenUsingDocumentReferenceAnnotationWithLazyEqualToTrueUsingGetMap() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + LazyDocumentReferenceEmptyMapDocument target = converter.read(LazyDocumentReferenceEmptyMapDocument.class, document); + assertThat(target.getMap().values()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyMapWhenUsingDBRefAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + DBRefEmptyMapDocument target = converter.read(DBRefEmptyMapDocument.class, document); + assertThat(target.map).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyMapWhenUsingDBRefAnnotationUsingGetMap() { + org.bson.Document document = org.bson.Document.parse("{\"map\":{}}"); + DBRefEmptyMapDocument target = converter.read(DBRefEmptyMapDocument.class, document); + assertThat(target.getMap()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullMapWhenUsingDBRefAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"map\":null}"); + DBRefEmptyMapDocument target = converter.read(DBRefEmptyMapDocument.class, document); + assertThat(target.map).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullMapWhenUsingDBRefAnnotationUsingGetMap() { + org.bson.Document document = org.bson.Document.parse("{\"map\":null}"); + DBRefEmptyMapDocument target = converter.read(DBRefEmptyMapDocument.class, document); + assertThat(target.getMap()).isNull(); + } + + static class EmptyMapDocument { + + Map map; + + public EmptyMapDocument(Map map) { + this.map = map; + } + + Map getMap() { + return map; + } + } + + static class DocumentReferenceEmptyMapDocument { + + @DocumentReference + Map map; + + public DocumentReferenceEmptyMapDocument(Map map) { + this.map = map; + } + + Map getMap() { + return map; + } + } + + static class LazyDocumentReferenceEmptyMapDocument { + + @DocumentReference(lazy = true) + Map map; + + public LazyDocumentReferenceEmptyMapDocument(Map map) { + this.map = map; + } + + Map getMap() { + return map; + } + } + + static class DBRefEmptyMapDocument { + + @DBRef + Map map; + + public DBRefEmptyMapDocument(Map map) { + this.map = map; + } + + Map getMap() { + return map; + } + } + } + + @Nested // GH-5065 + class EmptyListTests { + + @Test // GH-5065 + void controlTestToIllustrateThatTheEmptyListProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblem() { + org.bson.Document document = org.bson.Document.parse("{\"list\":[]}"); + EmptyListDocument target = converter.read(EmptyListDocument.class, document); + assertThat(target.list).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void controlTestToIllustrateThatTheEmptyListProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblemUsingGetList() { + org.bson.Document document = org.bson.Document.parse("{\"list\":[]}"); + EmptyListDocument target = converter.read(EmptyListDocument.class, document); + assertThat(target.getList()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void controlTestToIllustrateThatTheNullListProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblem() { + org.bson.Document document = org.bson.Document.parse("{\"list\":null}"); + EmptyListDocument target = converter.read(EmptyListDocument.class, document); + assertThat(target.list).isNull(); + } + + @Test // GH-5065 + void controlTestToIllustrateThatTheNullListProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblemUsingGetList() { + org.bson.Document document = org.bson.Document.parse("{\"list\":null}"); + EmptyListDocument target = converter.read(EmptyListDocument.class, document); + assertThat(target.getList()).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyListWhenUsingDocumentReferenceAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"list\":[]}"); + DocumentReferenceEmptyListDocument target = converter.read(DocumentReferenceEmptyListDocument.class, document); + assertThat(target.list).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyListWhenUsingDocumentReferenceAnnotationUsingGetList() { + org.bson.Document document = org.bson.Document.parse("{\"list\":[]}"); + DocumentReferenceEmptyListDocument target = converter.read(DocumentReferenceEmptyListDocument.class, document); + assertThat(target.getList()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullListWhenUsingDocumentReferenceAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"list\":null}"); + DocumentReferenceEmptyListDocument target = converter.read(DocumentReferenceEmptyListDocument.class, document); + assertThat(target.list).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullListWhenUsingDocumentReferenceAnnotationUsingGetList() { + org.bson.Document document = org.bson.Document.parse("{\"list\":null}"); + DocumentReferenceEmptyListDocument target = converter.read(DocumentReferenceEmptyListDocument.class, document); + assertThat(target.getList()).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyListWhenUsingDocumentReferenceAnnotationWithLazyEqualToTrue() { + org.bson.Document document = org.bson.Document.parse("{\"list\":[]}"); + LazyDocumentReferenceEmptyListDocument target = converter.read(LazyDocumentReferenceEmptyListDocument.class, document); + assertThat(target.list).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyListWhenUsingDocumentReferenceAnnotationWithLazyEqualToTrueUsingGetList() { + org.bson.Document document = org.bson.Document.parse("{\"list\":[]}"); + LazyDocumentReferenceEmptyListDocument target = converter.read(LazyDocumentReferenceEmptyListDocument.class, document); + assertThat(target.getList()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyListWhenUsingDBRefAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"list\":[]}"); + DBRefEmptyListDocument target = converter.read(DBRefEmptyListDocument.class, document); + assertThat(target.list).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptyListWhenUsingDBRefAnnotationUsingGetList() { + org.bson.Document document = org.bson.Document.parse("{\"list\":[]}"); + DBRefEmptyListDocument target = converter.read(DBRefEmptyListDocument.class, document); + assertThat(target.getList()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullListWhenUsingDBRefAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"list\":null}"); + DBRefEmptyListDocument target = converter.read(DBRefEmptyListDocument.class, document); + assertThat(target.list).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullListWhenUsingDBRefAnnotationUsingGetList() { + org.bson.Document document = org.bson.Document.parse("{\"list\":null}"); + DBRefEmptyListDocument target = converter.read(DBRefEmptyListDocument.class, document); + assertThat(target.getList()).isNull(); + } + + static class EmptyListDocument { + + List list; + + public EmptyListDocument(List list) { + this.list = list; + } + + List getList() { + return list; + } + } + + static class DocumentReferenceEmptyListDocument { + + @DocumentReference + List list; + + public DocumentReferenceEmptyListDocument(List list) { + this.list = list; + } + + List getList() { + return list; + } + } + + static class LazyDocumentReferenceEmptyListDocument { + + @DocumentReference(lazy = true) + List list; + + public LazyDocumentReferenceEmptyListDocument(List list) { + this.list = list; + } + + List getList() { + return list; + } + } + + static class DBRefEmptyListDocument { + + @DBRef + List list; + + public DBRefEmptyListDocument(List list) { + this.list = list; + } + + List getList() { + return list; + } + } + } + + @Nested // GH-5065 + class EmptySetTests { + + @Test // GH-5065 + void controlTestToIllustrateThatTheEmptySetProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblem() { + org.bson.Document document = org.bson.Document.parse("{\"set\":[]}"); + EmptySetDocument target = converter.read(EmptySetDocument.class, document); + assertThat(target.set).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void controlTestToIllustrateThatTheEmptySetProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblemUsingGetSet() { + org.bson.Document document = org.bson.Document.parse("{\"set\":[]}"); + EmptySetDocument target = converter.read(EmptySetDocument.class, document); + assertThat(target.getSet()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void controlTestToIllustrateThatTheNullSetProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblem() { + org.bson.Document document = org.bson.Document.parse("{\"set\":null}"); + EmptySetDocument target = converter.read(EmptySetDocument.class, document); + assertThat(target.set).isNull(); + } + + @Test // GH-5065 + void controlTestToIllustrateThatTheNullSetProblemIsLimitedToDocumentReferencesAndNotAMoreGenericProblemUsingGetSet() { + org.bson.Document document = org.bson.Document.parse("{\"set\":null}"); + EmptySetDocument target = converter.read(EmptySetDocument.class, document); + assertThat(target.getSet()).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptySetWhenUsingDocumentReferenceAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"set\":[]}"); + DocumentReferenceEmptySetDocument target = converter.read(DocumentReferenceEmptySetDocument.class, document); + assertThat(target.set).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptySetWhenUsingDocumentReferenceAnnotationUsingGetSet() { + org.bson.Document document = org.bson.Document.parse("{\"set\":[]}"); + DocumentReferenceEmptySetDocument target = converter.read(DocumentReferenceEmptySetDocument.class, document); + assertThat(target.getSet()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullSetWhenUsingDocumentReferenceAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"set\":null}"); + DocumentReferenceEmptySetDocument target = converter.read(DocumentReferenceEmptySetDocument.class, document); + assertThat(target.set).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullSetWhenUsingDocumentReferenceAnnotationUsingGetSet() { + org.bson.Document document = org.bson.Document.parse("{\"set\":null}"); + DocumentReferenceEmptySetDocument target = converter.read(DocumentReferenceEmptySetDocument.class, document); + assertThat(target.getSet()).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptySetWhenUsingDocumentReferenceAnnotationWithLazyEqualToTrue() { + org.bson.Document document = org.bson.Document.parse("{\"set\":[]}"); + LazyDocumentReferenceEmptySetDocument target = converter.read(LazyDocumentReferenceEmptySetDocument.class, document); + assertThat(target.set).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptySetWhenUsingDocumentReferenceAnnotationWithLazyEqualToTrueUsingGetSet() { + org.bson.Document document = org.bson.Document.parse("{\"set\":[]}"); + LazyDocumentReferenceEmptySetDocument target = converter.read(LazyDocumentReferenceEmptySetDocument.class, document); + assertThat(target.getSet()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptySetWhenUsingDBRefAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"set\":[]}"); + DBRefEmptySetDocument target = converter.read(DBRefEmptySetDocument.class, document); + assertThat(target.set).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnEmptyObjectAsAnEmptySetWhenUsingDBRefAnnotationUsingGetSet() { + org.bson.Document document = org.bson.Document.parse("{\"set\":[]}"); + DBRefEmptySetDocument target = converter.read(DBRefEmptySetDocument.class, document); + assertThat(target.getSet()).isNotNull().isEmpty(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullSetWhenUsingDBRefAnnotation() { + org.bson.Document document = org.bson.Document.parse("{\"set\":null}"); + DBRefEmptySetDocument target = converter.read(DBRefEmptySetDocument.class, document); + assertThat(target.set).isNull(); + } + + @Test // GH-5065 + void converterShouldReadAnExplicitlyAssignedNullAsANullSetWhenUsingDBRefAnnotationUsingGetSet() { + org.bson.Document document = org.bson.Document.parse("{\"set\":null}"); + DBRefEmptySetDocument target = converter.read(DBRefEmptySetDocument.class, document); + assertThat(target.getSet()).isNull(); + } + + static class EmptySetDocument { + + Set set; + + public EmptySetDocument(Set set) { + this.set = set; + } + + Set getSet() { + return set; + } + } + + static class DocumentReferenceEmptySetDocument { + + @DocumentReference + Set set; + + public DocumentReferenceEmptySetDocument(Set set) { + this.set = set; + } + + Set getSet() { + return set; + } + } + + static class LazyDocumentReferenceEmptySetDocument { + + @DocumentReference(lazy = true) + Set set; + + public LazyDocumentReferenceEmptySetDocument(Set set) { + this.set = set; + } + + Set getSet() { + return set; + } + } + + static class DBRefEmptySetDocument { + + @DBRef + Set set; + + public DBRefEmptySetDocument(Set set) { + this.set = set; + } + + Set getSet() { + return set; + } + } + } + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java index 384cffaad4..a1eecab673 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegateUnitTests.java @@ -19,7 +19,9 @@ import static org.mockito.Mockito.*; import java.util.Collections; +import java.util.Map; +import org.bson.Document; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -31,7 +33,6 @@ import org.springframework.data.mongodb.core.mapping.DocumentReference; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; -import org.springframework.expression.EvaluationContext; /** * Unit tests for {@link ReferenceLookupDelegate}. @@ -43,7 +44,6 @@ class ReferenceLookupDelegateUnitTests { @Mock MappingContext, MongoPersistentProperty> mappingContext; @Mock SpELContext spELContext; - @Mock EvaluationContext evaluationContext; @Mock MongoEntityReader entityReader; private ReferenceLookupDelegate lookupDelegate; @@ -94,4 +94,28 @@ void shouldResolveEmptyMapOnEmptyTargetCollection() { lookupDelegate.readReference(property, Collections.emptyMap(), lookupFunction, entityReader); verify(lookupFunction, never()).apply(any(), any()); } + + @Test // GH-5065 + void emptyMapWithDocumentReferenceAnnotationShouldDeserializeToAnEmptyMap() { + DocumentReference documentReference = mock(DocumentReference.class); + when(documentReference.lookup()).thenReturn("{ '_id' : ?#{#target} }"); + when(documentReference.sort()).thenReturn(""); + MongoPersistentProperty property = mock(MongoPersistentProperty.class); + when(property.isMap()).thenReturn(true); + when(property.isDocumentReference()).thenReturn(true); + when(property.getDocumentReference()).thenReturn(documentReference); + when(property.isCollectionLike()).thenReturn(false); + DocumentReferenceSource source = mock(DocumentReferenceSource.class); + when(source.getTargetSource()).thenReturn(Document.parse("{}")); + + // Placeholder mock. It should never get called. + ReferenceLookupDelegate.LookupFunction lookupFunction = mock(ReferenceLookupDelegate.LookupFunction.class); + + Object target = lookupDelegate.readReference(property, source, lookupFunction, entityReader); + + assertThat(target) + .isNotNull() + .isInstanceOf(Map.class); + } + }