diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index b4701f137..3da9c5a87 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -56,6 +56,7 @@ import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import com.mongodb.BasicDBList; @@ -71,6 +72,7 @@ import com.mongodb.DBRef; * @author Jon Brisbin * @author Patrik Wasik * @author Thomas Darimont + * @author Christoph Strobl */ public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware { @@ -942,13 +944,24 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App } public BasicDBList maybeConvertList(Iterable source) { + BasicDBList newDbl = new BasicDBList(); for (Object element : source) { - newDbl.add(convertToMongoType(element)); + Object convertedTypeDdo = convertToMongoType(element); + if (!isSimpleOrCollectionType(element.getClass())) { + this.getTypeMapper().writeType(element.getClass(), (DBObject) convertedTypeDdo); + } + newDbl.add(convertedTypeDdo); } + return newDbl; } + private boolean isSimpleOrCollectionType(Class type) { + return this.conversions.isSimpleType(type) || type.getClass().isArray() + || ClassUtils.isAssignable(Collection.class, type); + } + /** * Removes the type information from the conversion result. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java index 194fc189e..928c4df4a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java @@ -47,6 +47,7 @@ import com.mongodb.DBRef; * @author Oliver Gierke * @author Patryk Wasik * @author Thomas Darimont + * @author Christoph Strobl */ public class QueryMapper { @@ -109,17 +110,29 @@ public class QueryMapper { Object rawValue = query.get(key); String newKey = field.getMappedKey(); - if (isNestedKeyword(rawValue) && !field.isIdField()) { - Keyword keyword = new Keyword((DBObject) rawValue); - result.put(newKey, getMappedKeyword(field, keyword)); - } else { - result.put(newKey, getMappedValue(field, rawValue)); - } + result.put(newKey, getMappedObjectForField(field, rawValue)); } return result; } + /** + * Extracts the mapped object value for given field out of rawValue taking nested {@link Keyword}s into account + * + * @param field + * @param rawValue + * @return + */ + protected Object getMappedObjectForField(Field field, Object rawValue) { + + if (isNestedKeyword(rawValue) && !field.isIdField()) { + Keyword keyword = new Keyword((DBObject) rawValue); + return getMappedKeyword(field, keyword); + } + + return getMappedValue(field, rawValue); + } + /** * @param entity * @param key @@ -138,7 +151,7 @@ public class QueryMapper { * @param entity * @return */ - private DBObject getMappedKeyword(Keyword keyword, MongoPersistentEntity entity) { + protected DBObject getMappedKeyword(Keyword keyword, MongoPersistentEntity entity) { // $or/$nor if (keyword.isOrOrNor() || keyword.hasIterableValue()) { @@ -164,7 +177,7 @@ public class QueryMapper { * @param keyword * @return */ - private DBObject getMappedKeyword(Field property, Keyword keyword) { + protected DBObject getMappedKeyword(Field property, Keyword keyword) { boolean needsAssociationConversion = property.isAssociation() && !keyword.isExists(); Object value = keyword.getValue(); @@ -184,7 +197,7 @@ public class QueryMapper { * @param newKey the key the value will be bound to eventually * @return */ - private Object getMappedValue(Field documentField, Object value) { + protected Object getMappedValue(Field documentField, Object value) { if (documentField.isIdField()) { @@ -360,7 +373,7 @@ public class QueryMapper { * * @author Oliver Gierke */ - private static class Keyword { + static class Keyword { private static final String N_OR_PATTERN = "\\$.*or"; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java index b49814352..3733a4650 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java @@ -23,17 +23,23 @@ import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty.PropertyToFieldNameConverter; +import org.springframework.data.mongodb.core.query.Update.Modifier; +import org.springframework.data.mongodb.core.query.Update.Modifiers; import org.springframework.util.Assert; +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + /** * A subclass of {@link QueryMapper} that retains type information on the mongo types. * * @author Thomas Darimont * @author Oliver Gierke + * @author Christoph Strobl */ public class UpdateMapper extends QueryMapper { - private final MongoWriter converter; + private final MongoConverter converter; /** * Creates a new {@link UpdateMapper} using the given {@link MongoConverter}. @@ -59,6 +65,40 @@ public class UpdateMapper extends QueryMapper { entity.getTypeInformation()); } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.convert.QueryMapper#getMappedObjectForField(org.springframework.data.mongodb.core.convert.QueryMapper.Field, java.lang.Object) + */ + @Override + protected Object getMappedObjectForField(Field field, Object rawValue) { + + if (!isUpdateModifier(rawValue)) { + return super.getMappedObjectForField(field, rawValue); + } + + if (rawValue instanceof Modifier) { + return getMappedValue((Modifier) rawValue); + } else if (rawValue instanceof Modifiers) { + + DBObject modificationOperations = new BasicDBObject(); + for (Modifier modifier : ((Modifiers) rawValue).getModifiers()) { + modificationOperations.putAll(getMappedValue(modifier).toMap()); + } + + return modificationOperations; + } + + throw new IllegalArgumentException(String.format("Unable to map value of type '%s'!", rawValue.getClass())); + } + + private boolean isUpdateModifier(Object value) { + return (value instanceof Modifier || value instanceof Modifiers); + } + + private DBObject getMappedValue(Modifier modifier) { + return new BasicDBObject(modifier.getKey(), this.converter.convertToMongoType(modifier.getValue())); + } + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.convert.QueryMapper#createPropertyField(org.springframework.data.mongodb.core.mapping.MongoPersistentEntity, java.lang.String, org.springframework.data.mapping.context.MappingContext) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java index 73e5afc5e..0cb4c0ba9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 the original author or authors. + * Copyright 2010-2014 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. @@ -16,9 +16,12 @@ package org.springframework.data.mongodb.core.query; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import org.springframework.dao.InvalidDataAccessApiUsageException; @@ -32,6 +35,7 @@ import com.mongodb.DBObject; * @author Mark Pollack * @author Oliver Gierke * @author Becca Gaspard + * @author Christoph Strobl */ public class Update { @@ -39,7 +43,8 @@ public class Update { LAST, FIRST } - private HashMap modifierOps = new LinkedHashMap(); + private Map modifierOps = new LinkedHashMap(); + private Map pushCommandBuilders = new LinkedHashMap(1); /** * Static factory method to create an Update using the provided key @@ -139,9 +144,27 @@ public class Update { } /** - * Update using the $pushAll update modifier + * Update using {@code $push} modifier.
+ * Allows creation of {@code $push} command for single or multiple (using {@code $each}) values. * * @param key + * @return {@link PushOperatorBuilder} for given key + */ + public PushOperatorBuilder push(String key) { + + if (!pushCommandBuilders.containsKey(key)) { + pushCommandBuilders.put(key, new PushOperatorBuilder(key)); + } + return pushCommandBuilders.get(key); + } + + /** + * Update using the {@code $pushAll} update modifier.
+ * Note: In mongodb 2.4 the usage of {@code $pushAll} has been deprecated in favor of {@code $push $each}. + * {@link #push(String)}) returns a builder that can be used to populate the {@code $each} object. + * + * @see http://docs.mongodb.org/manual/reference/operator/update/pushAll/ + * @param key * @param values * @return */ @@ -249,4 +272,124 @@ public class Update { keyValueMap.put(key, value); } + + /** + * Modifiers holds a distinct collection of {@link Modifier} + * + * @author Christoph Strobl + */ + public static class Modifiers { + + private HashMap modifiers; + + public Modifiers() { + this.modifiers = new LinkedHashMap(1); + } + + public Collection getModifiers() { + return Collections.unmodifiableCollection(this.modifiers.values()); + } + + public void addModifier(Modifier modifier) { + this.modifiers.put(modifier.getKey(), modifier); + } + } + + /** + * Marker interface of nested commands. + * + * @author Christoph Strobl + */ + public static interface Modifier { + + /** + * @return the command to send eg. {@code $push} + */ + String getKey(); + + /** + * @return value to be sent with command + */ + Object getValue(); + } + + /** + * Implementation of {@link Modifier} representing {@code $each}. + * + * @author Christoph Strobl + */ + private static class Each implements Modifier { + + private Object[] values; + + public Each(Object... values) { + this.values = extractValues(values); + } + + private Object[] extractValues(Object[] values) { + + if (values == null || values.length == 0) { + return values; + } + + if (values.length == 1 && values[0] instanceof Collection) { + return ((Collection) values[0]).toArray(); + } + + Object[] convertedValues = new Object[values.length]; + for (int i = 0; i < values.length; i++) { + convertedValues[i] = values[i]; + } + + return convertedValues; + } + + @Override + public String getKey() { + return "$each"; + } + + @Override + public Object getValue() { + return this.values; + } + } + + /** + * Builder for creating {@code $push} modifiers + * + * @author Christop Strobl + */ + public class PushOperatorBuilder { + + private final String key; + private final Modifiers modifiers; + + PushOperatorBuilder(String key) { + this.key = key; + this.modifiers = new Modifiers(); + } + + /** + * Propagates {@code $each} to {@code $push} + * + * @param values + * @return + */ + public Update each(Object... values) { + + this.modifiers.addModifier(new Each(values)); + return Update.this.push(key, this.modifiers); + } + + /** + * Propagates {@link #value(Object)} to {@code $push} + * + * @param values + * @return + */ + public Update value(Object value) { + return Update.this.push(key, value); + } + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DBObjectTestUtils.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DBObjectTestUtils.java index b53623fc6..f35391e62 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DBObjectTestUtils.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DBObjectTestUtils.java @@ -70,7 +70,7 @@ public abstract class DBObjectTestUtils { } @SuppressWarnings("unchecked") - private static T getTypedValue(DBObject source, String key, Class type) { + public static T getTypedValue(DBObject source, String key, Class type) { Object value = source.get(key); assertThat(value, is(notNullValue())); 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 39f5456de..5202129e4 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 @@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import static org.junit.Assume.*; import static org.mockito.Mockito.*; import static org.springframework.data.mongodb.core.query.Criteria.*; import static org.springframework.data.mongodb.core.query.Query.*; @@ -35,7 +36,6 @@ import org.bson.types.ObjectId; import org.hamcrest.CoreMatchers; import org.joda.time.DateTime; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -168,6 +168,7 @@ public class MongoTemplateTests { template.dropCollection(BaseDoc.class); template.dropCollection(ObjectWithEnumValue.class); template.dropCollection(DocumentWithCollection.class); + template.dropCollection(DocumentWithCollectionOfSimpleType.class); } @Test @@ -887,7 +888,7 @@ public class MongoTemplateTests { l2.add(31); Query q3 = new Query(Criteria.where("age").in(l1, l2)); template.find(q3, PersonWithIdPropertyOfTypeObjectId.class); - Assert.fail("Should have trown an InvalidDocumentStoreApiUsageException"); + fail("Should have trown an InvalidDocumentStoreApiUsageException"); } catch (InvalidMongoDbApiUsageException e) {} } @@ -2202,8 +2203,8 @@ public class MongoTemplateTests { template.findAndModify(query, update, Document.class); Document retrieved = template.findOne(query, Document.class); - Assert.assertThat(retrieved.model, instanceOf(ModelA.class)); - Assert.assertThat(retrieved.model.value(), equalTo("value2")); + assertThat(retrieved.model, instanceOf(ModelA.class)); + assertThat(retrieved.model.value(), equalTo("value2")); } /** @@ -2234,10 +2235,56 @@ public class MongoTemplateTests { assertThat(result.model.get(0).value(), is(newModelValue)); } + /** + * @see DATAMONGO-812 + */ + @Test + public void updateMultiShouldAddValuesCorrectlyWhenUsingPushEachWithComplexTypes() { + + DocumentWithCollection document = new DocumentWithCollection(new ModelA("model-a")); + template.save(document); + Query query = query(where("id").is(document.id)); + assumeThat(template.findOne(query, DocumentWithCollection.class).model, hasSize(1)); + + Update update = new Update().push("model").each(new ModelA("model-b"), new ModelA("model-c")); + template.updateMulti(query, update, DocumentWithCollection.class); + + assertThat(template.findOne(query, DocumentWithCollection.class).model, hasSize(3)); + } + + /** + * @see DATAMONGO-812 + */ + @Test + public void updateMultiShouldAddValuesCorrectlyWhenUsingPushEachWithSimpleTypes() { + + DocumentWithCollectionOfSimpleType document = new DocumentWithCollectionOfSimpleType(); + document.values = Arrays.asList("spring"); + template.save(document); + + Query query = query(where("id").is(document.id)); + assumeThat(template.findOne(query, DocumentWithCollectionOfSimpleType.class).values, hasSize(1)); + + Update update = new Update().push("values").each("data", "mongodb"); + template.updateMulti(query, update, DocumentWithCollectionOfSimpleType.class); + + assertThat(template.findOne(query, DocumentWithCollectionOfSimpleType.class).values, hasSize(3)); + } + static class DocumentWithCollection { @Id public String id; public List model; + + DocumentWithCollection(Model... models) { + this.model = Arrays.asList(models); + } + } + + static class DocumentWithCollectionOfSimpleType { + + @Id String id; + List values; } static interface Model { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java index 3d80c35b5..49ba55c11 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2014 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. @@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core.convert; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; +import static org.springframework.data.mongodb.core.DBObjectTestUtils.*; import java.math.BigDecimal; import java.math.BigInteger; @@ -72,6 +73,7 @@ import com.mongodb.util.JSON; * * @author Oliver Gierke * @author Patrik Wasik + * @author Christoph Strobl */ @RunWith(MockitoJUnitRunner.class) public class MappingMongoConverterUnitTests { @@ -1026,7 +1028,7 @@ public class MappingMongoConverterUnitTests { Set readResult = converter.read(Set.class, (BasicDBList) result); assertThat(readResult.size(), is(1)); - assertThat(readResult.iterator().next(), is(instanceOf(Map.class))); + assertThat(readResult.iterator().next(), is(instanceOf(Address.class))); } /** @@ -1381,6 +1383,62 @@ public class MappingMongoConverterUnitTests { assertThat(aValue.get("c"), is((Object) "C")); } + @Test + public void convertsListToBasicDBListAndRetainsTypeInformationForComplexObjects() { + + Address address = new Address(); + address.city = "London"; + address.street = "Foo"; + + Object result = converter.convertToMongoType(Collections.singletonList(address)); + + assertThat(result, is(instanceOf(BasicDBList.class))); + + BasicDBList dbList = (BasicDBList) result; + assertThat(dbList, hasSize(1)); + assertThat(getTypedValue(getAsDBObject(dbList, 0), ("_class"), String.class), equalTo(Address.class.getName())); + } + + @Test + public void convertsListToBasicDBListWithoutTypeInformationForSimpleTypes() { + + Object result = converter.convertToMongoType(Collections.singletonList("foo")); + + assertThat(result, is(instanceOf(BasicDBList.class))); + + BasicDBList dbList = (BasicDBList) result; + assertThat(dbList, hasSize(1)); + assertThat(dbList.get(0), instanceOf(String.class)); + } + + @Test + public void convertsArrayToBasicDBListAndRetainsTypeInformationForComplexObjects() { + + Address address = new Address(); + address.city = "London"; + address.street = "Foo"; + + Object result = converter.convertToMongoType(new Address[] { address }); + + assertThat(result, is(instanceOf(BasicDBList.class))); + + BasicDBList dbList = (BasicDBList) result; + assertThat(dbList, hasSize(1)); + assertThat(getTypedValue(getAsDBObject(dbList, 0), ("_class"), String.class), equalTo(Address.class.getName())); + } + + @Test + public void convertsArrayToBasicDBListWithoutTypeInformationForSimpleTypes() { + + Object result = converter.convertToMongoType(new String[] { "foo" }); + + assertThat(result, is(instanceOf(BasicDBList.class))); + + BasicDBList dbList = (BasicDBList) result; + assertThat(dbList, hasSize(1)); + assertThat(dbList.get(0), instanceOf(String.class)); + } + static class GenericType { T content; } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java index bb84929cd..61323dbce 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java @@ -16,11 +16,14 @@ package org.springframework.data.mongodb.core.convert; import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.collection.IsMapContaining.*; import static org.junit.Assert.*; import static org.springframework.data.mongodb.core.DBObjectTestUtils.*; +import java.util.Arrays; import java.util.List; +import org.hamcrest.Matcher; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,6 +34,7 @@ import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.query.Update; +import com.mongodb.BasicDBList; import com.mongodb.DBObject; /** @@ -38,6 +42,7 @@ import com.mongodb.DBObject; * * @author Oliver Gierke * @author Christoph Strobl + * @author Thomas Darimont */ @RunWith(MockitoJUnitRunner.class) public class UpdateMapperUnitTests { @@ -45,12 +50,14 @@ public class UpdateMapperUnitTests { @Mock MongoDbFactory factory; MappingMongoConverter converter; MongoMappingContext context; + UpdateMapper mapper; @Before public void setUp() { this.context = new MongoMappingContext(); this.converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), context); + this.mapper = new UpdateMapper(converter); } /** @@ -60,7 +67,6 @@ public class UpdateMapperUnitTests { public void updateMapperRetainsTypeInformationForCollectionField() { Update update = new Update().push("list", new ConcreteChildClass("2", "BAR")); - UpdateMapper mapper = new UpdateMapper(converter); DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(ParentClass.class)); @@ -175,6 +181,91 @@ public class UpdateMapperUnitTests { assertThat(someObject.get("value"), is((Object) "bubu")); } + /** + * @see DATAMONGO-812 + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void updateMapperShouldConvertPushCorrectlyWhenCalledWithEachUsingSimpleTypes() { + + Update update = new Update().push("values").each("spring", "data", "mongodb"); + DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Model.class)); + + DBObject push = getAsDBObject(mappedObject, "$push"); + DBObject values = getAsDBObject(push, "values"); + BasicDBList each = getAsDBList(values, "$each"); + + assertThat(push.get("_class"), nullValue()); + assertThat(values.get("_class"), nullValue()); + + assertThat(each.toMap(), (Matcher) allOf(hasValue("spring"), hasValue("data"), hasValue("mongodb"))); + } + + /** + * @see DATAMONGO-812 + */ + @Test + public void updateMapperShouldConvertPushWhithoutAddingClassInformationWhenUsedWithEvery() { + + Update update = new Update().push("values").each("spring", "data", "mongodb"); + + DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Model.class)); + DBObject push = getAsDBObject(mappedObject, "$push"); + DBObject values = getAsDBObject(push, "values"); + + assertThat(push.get("_class"), nullValue()); + assertThat(values.get("_class"), nullValue()); + } + + /** + * @see DATAMONGO-812 + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void updateMapperShouldConvertPushCorrectlyWhenCalledWithEachUsingCustomTypes() { + + Update update = new Update().push("models").each(new ListModel("spring", "data", "mongodb")); + DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), + context.getPersistentEntity(ModelWrapper.class)); + + DBObject push = getAsDBObject(mappedObject, "$push"); + DBObject model = getAsDBObject(push, "models"); + BasicDBList each = getAsDBList(model, "$each"); + BasicDBList values = getAsDBList((DBObject) each.get(0), "values"); + + assertThat(values.toMap(), (Matcher) allOf(hasValue("spring"), hasValue("data"), hasValue("mongodb"))); + } + + /** + * @see DATAMONGO-812 + */ + @Test + public void updateMapperShouldRetainClassInformationForPushCorrectlyWhenCalledWithEachUsingCustomTypes() { + + Update update = new Update().push("models").each(new ListModel("spring", "data", "mongodb")); + DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), + context.getPersistentEntity(ModelWrapper.class)); + + DBObject push = getAsDBObject(mappedObject, "$push"); + DBObject model = getAsDBObject(push, "models"); + BasicDBList each = getAsDBList(model, "$each"); + + assertThat(((DBObject) each.get(0)).get("_class").toString(), equalTo(ListModel.class.getName())); + } + + /** + * @see DATAMONGO-812 + */ + @Test + public void testUpdateShouldAllowMultiplePushEachForDifferentFields() { + Update update = new Update().push("category").each("spring", "data").push("type").each("mongodb"); + DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class)); + + DBObject push = getAsDBObject(mappedObject, "$push"); + assertThat(getAsDBObject(push, "category").containsField("$each"), is(true)); + assertThat(getAsDBObject(push, "type").containsField("$each"), is(true)); + } + static interface Model {} static class ModelImpl implements Model { @@ -189,6 +280,20 @@ public class UpdateMapperUnitTests { Model model; } + static class ListModelWrapper { + + List models; + } + + static class ListModel { + + List values; + + public ListModel(String... values) { + this.values = Arrays.asList(values); + } + } + static class ParentClass { String id;