Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public ReferenceLookupDelegate(
}

if (!result.iterator().hasNext()) {
return null;
return property.isMap() ? Collections.emptyMap() : null;
}

Object resultValue = result.iterator().next();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@

import java.util.Arrays;
import java.util.Collections;
import java.util.Map;

import org.bson.Document;
import org.bson.types.ObjectId;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
Expand All @@ -35,6 +37,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;
Expand All @@ -43,6 +47,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}.
Expand All @@ -58,6 +65,8 @@ class DefaultDbRefResolverUnitTests {
@Mock MongoDatabase dbMock;
@Mock MongoCollection<Document> collectionMock;
@Mock FindIterable<Document> cursorMock;
@Mock MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
@Mock SpELContext spELContext;
private DefaultDbRefResolver resolver;

@BeforeEach
Expand Down Expand Up @@ -91,7 +100,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());
Expand Down Expand Up @@ -134,4 +143,75 @@ void bulkFetchContainsDuplicates() {

assertThat(resolver.bulkFetch(Arrays.asList(ref1, ref2))).containsExactly(document, document);
}

@Test // GH-5065
@DisplayName("GH-5065: Empty Map with @DocumentReference annotation should deserialize to an empty map.")
void resolveEmptyMapIsNotNull() {
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);

verify(property, times(3)).isMap();
verify(property, times(2)).isDocumentReference();
verify(property, times(2)).getDocumentReference();
verify(property, times(3)).isCollectionLike();
verify(documentReference, times(1)).lookup();
verify(documentReference, times(1)).sort();
verify(documentReference, times(1)).lazy();
verify(source, times(3)).getTargetSource();
verifyNoMoreInteractions(documentReference, property, source); // Make sure we only call the properties we mocked.
assertThat(target)
.isNotNull()
.isInstanceOf(Map.class);
}

@Test // GH-5065
@DisplayName("GH-5065: Lazy loaded empty Map with @DocumentReference annotation should deserialize to an empty map with a non-null values property.")
void resolveLazyLoadedEmptyMapIsNotNull() {
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);

verify(property, times(1)).isMap();
verify(property, times(1)).isDocumentReference();
verify(property, times(1)).getDocumentReference();
verify(property, times(1)).isCollectionLike();
verify(property, times(1)).getType();
verify(documentReference, times(1)).lazy();
verify(source, times(1)).getTargetSource();
verifyNoMoreInteractions(documentReference, property, source); // Make sure we only call the properties we mocked.

assertThat(target)
.isNotNull()
.isInstanceOf(Map.class)
.asInstanceOf(MAP).values().isNotNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,16 @@

import org.bson.Document;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.MongoDatabaseFactory;
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;

Expand Down Expand Up @@ -478,4 +481,130 @@ public String toString() {
+ ", localTime=" + this.getLocalTime() + ", localDateTime=" + this.getLocalDateTime() + ")";
}
}

@Nested // GH-5065
@DisplayName("GH-5065: Empty Map Tests")
class EmptyMapTests {

@Test // GH-5065
@DisplayName("Test control: Passing test to indicate that the problem is not a more generic issue with maps in general.")
void readsEmptyMapCorrectly() {
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
@DisplayName("Test control: Converter should read an explicitly assigned null as a null map.")
void readsExplicitlyNullMapCorrectly() {
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
@DisplayName("Converter should read an empty object as an empty map when using @DocumentReference.")
void readsEmptyMapWithDocumentReferenceCorrectly() {
org.bson.Document document = org.bson.Document.parse("{\"map\":{}}");
//DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document);
assertThat(converter.read(DocumentReferenceEmptyMapDocument.class, document).map).isNotNull().isEmpty();
assertThat(converter.read(DocumentReferenceEmptyMapDocument.class, document).getMap()).isNotNull().isEmpty();
}

@Test // GH-5065
@DisplayName("Converter should read an explicitly assigned null as a null map when using @DocumentReference.")
void readsExplicitlyNullMapWithDocumentReferenceCorrectly() {
org.bson.Document document = org.bson.Document.parse("{\"map\":null}");
//DocumentReferenceEmptyMapDocument target = converter.read(DocumentReferenceEmptyMapDocument.class, document);
assertThat(converter.read(DocumentReferenceEmptyMapDocument.class, document).map).isNull();
assertThat(converter.read(DocumentReferenceEmptyMapDocument.class, document).getMap()).isNull();
}

@Test // GH-5065
@DisplayName("Converter should read an empty object as an empty map with a valid values property when using @DocumentReference(lazy = true).")
void readsEmptyMapWithLazyLoadedDocumentReferenceCorrectly() {
org.bson.Document document = org.bson.Document.parse("{\"map\":{}}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would also be interesting to have test for { 'map' : null } expecting null for the target.map.

//LazyDocumentReferenceEmptyMapDocument target = converter.read(LazyDocumentReferenceEmptyMapDocument.class, document);
assertThat(converter.read(LazyDocumentReferenceEmptyMapDocument.class, document).map).isNotNull().isEmpty();
assertThat(converter.read(LazyDocumentReferenceEmptyMapDocument.class, document).getMap()).isNotNull().isEmpty();
assertThat(converter.read(LazyDocumentReferenceEmptyMapDocument.class, document).map.values()).isNotNull().isEmpty();
assertThat(converter.read(LazyDocumentReferenceEmptyMapDocument.class, document).getMap().values()).isNotNull().isEmpty();
}

@Test // GH-5065
@DisplayName("Converter should read an empty object as an empty map when using @DBRef.")
void readsEmptyMapWithDBRefCorrectly() {
org.bson.Document document = org.bson.Document.parse("{\"map\":{}}");
//DBRefEmptyMapDocument target = converter.read(DBRefEmptyMapDocument.class, document);
assertThat(converter.read(DBRefEmptyMapDocument.class, document).map).isNotNull().isEmpty();
assertThat(converter.read(DBRefEmptyMapDocument.class, document).getMap()).isNotNull().isEmpty();
}

@Test // GH-5065
@DisplayName("Converter should read an explicitly assigned null as a null map when using @DBRef.")
void readsExplicitlyNullMapWithDBRefCorrectly() {
org.bson.Document document = org.bson.Document.parse("{\"map\":null}");
//DBRefEmptyMapDocument target = converter.read(DBRefEmptyMapDocument.class, document);
assertThat(converter.read(DBRefEmptyMapDocument.class, document).map).isNull();
assertThat(converter.read(DBRefEmptyMapDocument.class, document).getMap()).isNull();
}

static class EmptyMapDocument {

Map<String, String> map;

public EmptyMapDocument(Map<String, String> map) {
this.map = map;
}

Map<String, String> getMap() {
return map;
}
}

static class DocumentReferenceEmptyMapDocument {

@DocumentReference
Map<String, String> map;

public DocumentReferenceEmptyMapDocument(Map<String, String> map) {
this.map = map;
}

Map<String, String> getMap() {
return map;
}
}

static class LazyDocumentReferenceEmptyMapDocument {

@DocumentReference(lazy = true)
Map<String, String> map;

public LazyDocumentReferenceEmptyMapDocument(Map<String, String> map) {
this.map = map;
}

Map<String, String> getMap() {
return map;
}
}
}

static class DBRefEmptyMapDocument {

@DBRef
Map<String, String> map;

public DBRefEmptyMapDocument(Map<String, String> map) {
this.map = map;
}

Map<String, String> getMap() {
return map;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@
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.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
Expand Down Expand Up @@ -94,4 +97,35 @@ void shouldResolveEmptyMapOnEmptyTargetCollection() {
lookupDelegate.readReference(property, Collections.emptyMap(), lookupFunction, entityReader);
verify(lookupFunction, never()).apply(any(), any());
}

@Test // GH-5065
@DisplayName("GH-5065: Empty Map with @DocumentReference annotation should deserialize to an empty map.")
void shouldResolveEmptyMapOnEmptyDocumentReferenceMapProperty() {
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("{}"));
ReferenceLookupDelegate.LookupFunction lookupFunction = mock(ReferenceLookupDelegate.LookupFunction.class);

Object target = lookupDelegate.readReference(property, source, lookupFunction, entityReader);

verify(lookupFunction, never()).apply(any(), any()); // Since we mocked it, make sure it is never called.
verify(property, times(2)).isMap();
verify(property, times(1)).isDocumentReference();
verify(property, times(1)).getDocumentReference();
verify(property, times(2)).isCollectionLike();
verify(documentReference, times(1)).lookup();
verify(documentReference, times(1)).sort();
verifyNoMoreInteractions(documentReference, property, source); // Make sure we only call the properties we mocked.
assertThat(target)
.isNotNull()
.isInstanceOf(Map.class);
}

}