Browse Source

DATAMONGO-2135 - Default to intermediate List for properties typed to Collection.

We now defensively create a List rather than a LinkedHashSet (which Spring's CollectionFactory.createCollection(…) defaults to) to make sure we're not accidentally dropping values that are considered equal according to their Java class definition.
pull/662/head
Oliver Drotbohm 7 years ago
parent
commit
01c1ec2e5b
  1. 20
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java
  2. 29
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java

20
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java

@ -28,7 +28,6 @@ import java.util.Set;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
@ -531,7 +530,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
: new BasicDBObject(); : new BasicDBObject();
addCustomTypeKeyIfNecessary(ClassTypeInformation.from(prop.getRawType()), obj, propDbObj); addCustomTypeKeyIfNecessary(ClassTypeInformation.from(prop.getRawType()), obj, propDbObj);
MongoPersistentEntity<?> entity = isSubtype(prop.getType(), obj.getClass()) MongoPersistentEntity<?> entity = isSubTypeOf(obj.getClass(), prop.getType())
? mappingContext.getPersistentEntity(obj.getClass()) ? mappingContext.getPersistentEntity(obj.getClass())
: mappingContext.getPersistentEntity(type); : mappingContext.getPersistentEntity(type);
@ -539,10 +538,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
accessor.put(prop, propDbObj); accessor.put(prop, propDbObj);
} }
private boolean isSubtype(Class<?> left, Class<?> right) {
return left.isAssignableFrom(right) && !left.equals(right);
}
/** /**
* Returns given object as {@link Collection}. Will return the {@link Collection} as is if the source is a * Returns given object as {@link Collection}. Will return the {@link Collection} as is if the source is a
* {@link Collection} already, will convert an array into a {@link Collection} or simply create a single element * {@link Collection} already, will convert an array into a {@link Collection} or simply create a single element
@ -910,7 +905,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
TypeInformation<?> componentType = targetType.getComponentType(); TypeInformation<?> componentType = targetType.getComponentType();
Class<?> rawComponentType = componentType == null ? null : componentType.getType(); Class<?> rawComponentType = componentType == null ? null : componentType.getType();
collectionType = Collection.class.isAssignableFrom(collectionType) ? collectionType : List.class; collectionType = isSubTypeOf(collectionType, Collection.class) ? collectionType : List.class;
Collection<Object> items = targetType.getType().isArray() ? new ArrayList<Object>() Collection<Object> items = targetType.getType().isArray() ? new ArrayList<Object>()
: CollectionFactory.createCollection(collectionType, rawComponentType, sourceValue.size()); : CollectionFactory.createCollection(collectionType, rawComponentType, sourceValue.size());
@ -1360,6 +1355,17 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return true; return true;
} }
/**
* Returns whether the given type is a sub type of the given reference, i.e. assignable but not the exact same type.
*
* @param type must not be {@literal null}.
* @param reference must not be {@literal null}.
* @return
*/
private static boolean isSubTypeOf(Class<?> type, Class<?> reference) {
return !type.equals(reference) && reference.isAssignableFrom(type);
}
/** /**
* Marker class used to indicate we have a non root document object here that might be used within an update - so we * Marker class used to indicate we have a non root document object here that might be used within an update - so we
* need to preserve type hints for potential nested elements but need to remove it on top level. * need to preserve type hints for potential nested elements but need to remove it on top level.

29
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java

@ -21,6 +21,7 @@ import static org.junit.Assert.*;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.DBObjectTestUtils.*; import static org.springframework.data.mongodb.core.DBObjectTestUtils.*;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import java.math.BigDecimal; import java.math.BigDecimal;
@ -1862,6 +1863,23 @@ public class MappingMongoConverterUnitTests {
assertThat(target.get("_class"), is(nullValue())); assertThat(target.get("_class"), is(nullValue()));
} }
@Test // DATAMONGO-2135
public void addsEqualObjectsToCollection() {
DBObject itemDocument = new BasicDBObject("itemKey", "123");
BasicDBList items = new BasicDBList();
items.add(itemDocument);
items.add(itemDocument);
items.add(itemDocument);
DBObject orderDocument = new BasicDBObject("items", items);
Order order = converter.read(Order.class, orderDocument);
assertThat(order.items, hasSize(3));
}
static class GenericType<T> { static class GenericType<T> {
T content; T content;
} }
@ -2232,4 +2250,15 @@ public class MappingMongoConverterUnitTests {
static class DocWithInterfacedEnum { static class DocWithInterfacedEnum {
SomeInterface property; SomeInterface property;
} }
// DATAMONGO-2135
@EqualsAndHashCode // equality check by fields
static class SomeItem {
String itemKey;
}
static class Order {
Collection<SomeItem> items = new ArrayList<SomeItem>();
}
} }

Loading…
Cancel
Save