From 49eee40f7e1df9fa824d1ab5a4cd8b34ba1d2efe Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 24 Jan 2014 10:41:26 +0100 Subject: [PATCH] DATAMONGO-812 - Add support for $push $each since $pushAll is deprecated. $pushAll has been deprecated in MongoDB 2.4. Instead of calling pushAll one can use push in combination with each. The abstraction for pushAll will remain in code for now but may be removed in a subsequent version. Original pull request: #112. --- .../core/convert/MappingMongoConverter.java | 15 +- .../mongodb/core/convert/QueryMapper.java | 33 ++-- .../mongodb/core/convert/UpdateMapper.java | 42 ++++- .../data/mongodb/core/query/Update.java | 149 +++++++++++++++++- .../data/mongodb/core/DBObjectTestUtils.java | 2 +- .../data/mongodb/core/MongoTemplateTests.java | 55 ++++++- .../MappingMongoConverterUnitTests.java | 62 +++++++- .../core/convert/UpdateMapperUnitTests.java | 107 ++++++++++++- 8 files changed, 442 insertions(+), 23 deletions(-) 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;