/*
* Copyright 2013-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.DocumentTestUtils.*;
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.bson.Document;
import org.hamcrest.collection.IsIterableContainingInOrder;
import org.hamcrest.core.Is;
import org.hamcrest.core.IsEqual;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.annotation.Id;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.DocumentTestUtils;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.core.query.Update.Position;
import com.mongodb.DBRef;
/**
* Unit tests for {@link UpdateMapper}.
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Thomas Darimont
* @author Mark Paluch
* @author Pavel Vodrazka
*/
@RunWith(MockitoJUnitRunner.class)
public class UpdateMapperUnitTests {
@Mock MongoDbFactory factory;
MappingMongoConverter converter;
MongoMappingContext context;
UpdateMapper mapper;
private Converter<NestedEntity, Document> writingConverterSpy;
@Before
@SuppressWarnings("unchecked")
public void setUp() {
this.writingConverterSpy = Mockito.spy(new NestedEntityWriteConverter());
CustomConversions conversions = new MongoCustomConversions(Collections.singletonList(writingConverterSpy));
this.context = new MongoMappingContext();
this.context.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
this.context.initialize();
this.converter = new MappingMongoConverter(new DefaultDbRefResolver(factory), context);
this.converter.setCustomConversions(conversions);
this.converter.afterPropertiesSet();
this.mapper = new UpdateMapper(converter);
}
@Test // DATAMONGO-721
public void updateMapperRetainsTypeInformationForCollectionField() {
Update update = new Update().push("list", new ConcreteChildClass("2", "BAR"));
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ParentClass.class));
Document push = getAsDocument(mappedObject, "$push");
Document list = getAsDocument(push, "aliased");
assertTypeHint(list, ConcreteChildClass.class);
}
@Test // DATAMONGO-807
public void updateMapperShouldRetainTypeInformationForNestedEntities() {
Update update = Update.update("model", new ModelImpl(1));
UpdateMapper mapper = new UpdateMapper(converter);
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ModelWrapper.class));
Document set = getAsDocument(mappedObject, "$set");
Document modelDocument = (Document) set.get("model");
assertTypeHint(modelDocument, ModelImpl.class);
}
@Test // DATAMONGO-807
public void updateMapperShouldNotPersistTypeInformationForKnownSimpleTypes() {
Update update = Update.update("model.value", 1);
UpdateMapper mapper = new UpdateMapper(converter);
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ModelWrapper.class));
Document set = getAsDocument(mappedObject, "$set");
assertThat(set.get("_class"), nullValue());
}
@Test // DATAMONGO-807
public void updateMapperShouldNotPersistTypeInformationForNullValues() {
Update update = Update.update("model", null);
UpdateMapper mapper = new UpdateMapper(converter);
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ModelWrapper.class));
Document set = getAsDocument(mappedObject, "$set");
assertThat(set.get("_class"), nullValue());
}
@Test // DATAMONGO-407
public void updateMapperShouldRetainTypeInformationForNestedCollectionElements() {
Update update = Update.update("list.$", new ConcreteChildClass("42", "bubu"));
UpdateMapper mapper = new UpdateMapper(converter);
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ParentClass.class));
Document set = getAsDocument(mappedObject, "$set");
Document modelDocument = getAsDocument(set, "aliased.$");
assertTypeHint(modelDocument, ConcreteChildClass.class);
}
@Test // DATAMONGO-407
public void updateMapperShouldSupportNestedCollectionElementUpdates() {
Update update = Update.update("list.$.value", "foo").set("list.$.otherValue", "bar");
UpdateMapper mapper = new UpdateMapper(converter);
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ParentClass.class));
Document set = getAsDocument(mappedObject, "$set");
assertThat(set.get("aliased.$.value"), is("foo"));
assertThat(set.get("aliased.$.otherValue"), is("bar"));
}
@Test // DATAMONGO-407
public void updateMapperShouldWriteTypeInformationForComplexNestedCollectionElementUpdates() {
Update update = Update.update("list.$.value", "foo").set("list.$.someObject", new ConcreteChildClass("42", "bubu"));
UpdateMapper mapper = new UpdateMapper(converter);
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ParentClass.class));
Document document = getAsDocument(mappedObject, "$set");
assertThat(document.get("aliased.$.value"), is("foo"));
Document someObject = getAsDocument(document, "aliased.$.someObject");
assertThat(someObject, is(notNullValue()));
assertThat(someObject.get("value"), is("bubu"));
assertTypeHint(someObject, ConcreteChildClass.class);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test // DATAMONGO-812
public void updateMapperShouldConvertPushCorrectlyWhenCalledWithEachUsingSimpleTypes() {
Update update = new Update().push("values").each("spring", "data", "mongodb");
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Model.class));
Document push = getAsDocument(mappedObject, "$push");
Document values = getAsDocument(push, "values");
List<Object> each = getAsDBList(values, "$each");
assertThat(push.get("_class"), nullValue());
assertThat(values.get("_class"), nullValue());
assertThat(each, IsIterableContainingInOrder.contains("spring", "data", "mongodb"));
}
@Test // DATAMONGO-812
public void updateMapperShouldConvertPushWhithoutAddingClassInformationWhenUsedWithEvery() {
Update update = new Update().push("values").each("spring", "data", "mongodb");
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Model.class));
Document push = getAsDocument(mappedObject, "$push");
Document values = getAsDocument(push, "values");
assertThat(push.get("_class"), nullValue());
assertThat(values.get("_class"), nullValue());
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test // DATAMONGO-812
public void updateMapperShouldConvertPushCorrectlyWhenCalledWithEachUsingCustomTypes() {
Update update = new Update().push("models").each(new ListModel("spring", "data", "mongodb"));
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ModelWrapper.class));
Document push = getAsDocument(mappedObject, "$push");
Document model = getAsDocument(push, "models");
List<Object> each = getAsDBList(model, "$each");
List<Object> values = getAsDBList((Document) each.get(0), "values");
assertThat(values, IsIterableContainingInOrder.contains("spring", "data", "mongodb"));
}
@Test // DATAMONGO-812
public void updateMapperShouldRetainClassInformationForPushCorrectlyWhenCalledWithEachUsingCustomTypes() {
Update update = new Update().push("models").each(new ListModel("spring", "data", "mongodb"));
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ModelWrapper.class));
Document push = getAsDocument(mappedObject, "$push");
Document model = getAsDocument(push, "models");
List<Document> each = getAsDBList(model, "$each");
assertTypeHint(each.get(0), ListModel.class);
}
@Test // DATAMONGO-812
public void testUpdateShouldAllowMultiplePushEachForDifferentFields() {
Update update = new Update().push("category").each("spring", "data").push("type").each("mongodb");
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class));
Document push = getAsDocument(mappedObject, "$push");
assertThat(getAsDocument(push, "category").containsKey("$each"), is(true));
assertThat(getAsDocument(push, "type").containsKey("$each"), is(true));
}
@Test // DATAMONGO-943
public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositiveIndexParameter() {
Update update = new Update().push("key").atPosition(2).each(Arrays.asList("Arya", "Arry", "Weasel"));
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class));
Document push = getAsDocument(mappedObject, "$push");
Document key = getAsDocument(push, "key");
assertThat(key.containsKey("$position"), is(true));
assertThat(key.get("$position"), is(2));
assertThat(getAsDocument(push, "key").containsKey("$each"), is(true));
}
@Test // DATAMONGO-943
public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositionFirst() {
Update update = new Update().push("key").atPosition(Position.FIRST).each(Arrays.asList("Arya", "Arry", "Weasel"));
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class));
Document push = getAsDocument(mappedObject, "$push");
Document key = getAsDocument(push, "key");
assertThat(key.containsKey("$position"), is(true));
assertThat(key.get("$position"), is(0));
assertThat(getAsDocument(push, "key").containsKey("$each"), is(true));
}
@Test // DATAMONGO-943
public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositionLast() {
Update update = new Update().push("key").atPosition(Position.LAST).each(Arrays.asList("Arya", "Arry", "Weasel"));
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class));
Document push = getAsDocument(mappedObject, "$push");
Document key = getAsDocument(push, "key");
assertThat(key.containsKey("$position"), is(false));
assertThat(getAsDocument(push, "key").containsKey("$each"), is(true));
}
@Test // DATAMONGO-943
public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositionNull() {
Update update = new Update().push("key").atPosition(null).each(Arrays.asList("Arya", "Arry", "Weasel"));
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class));
Document push = getAsDocument(mappedObject, "$push");
Document key = getAsDocument(push, "key");
assertThat(key.containsKey("$position"), is(false));
assertThat(getAsDocument(push, "key").containsKey("$each"), is(true));
}
@Test // DATAMONGO-832
public void updatePushEachWithSliceShouldRenderCorrectly() {
Update update = new Update().push("key").slice(5).each(Arrays.asList("Arya", "Arry", "Weasel"));
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class));
Document push = getAsDocument(mappedObject, "$push");
Document key = getAsDocument(push, "key");
assertThat(key.containsKey("$slice"), is(true));
assertThat(key.get("$slice"), is(5));
assertThat(key.containsKey("$each"), is(true));
}
@Test // DATAMONGO-832
public void updatePushEachWithSliceShouldRenderWhenUsingMultiplePushCorrectly() {
Update update = new Update().push("key").slice(5).each(Arrays.asList("Arya", "Arry", "Weasel")).push("key-2")
.slice(-2).each("The Beggar King", "Viserys III Targaryen");
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class));
Document push = getAsDocument(mappedObject, "$push");
Document key = getAsDocument(push, "key");
assertThat(key.containsKey("$slice"), is(true));
assertThat((Integer) key.get("$slice"), is(5));
assertThat(key.containsKey("$each"), is(true));
Document key2 = getAsDocument(push, "key-2");
assertThat(key2.containsKey("$slice"), is(true));
assertThat((Integer) key2.get("$slice"), is(-2));
assertThat(key2.containsKey("$each"), is(true));
}
@Test // DATAMONGO-1141
public void updatePushEachWithValueSortShouldRenderCorrectly() {
Update update = new Update().push("scores").sort(Direction.DESC).each(42, 23, 68);
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ParentClass.class));
Document push = getAsDocument(mappedObject, "$push");
Document key = getAsDocument(push, "scores");
assertThat(key.containsKey("$sort"), is(true));
assertThat((Integer) key.get("$sort"), is(-1));
assertThat(key.containsKey("$each"), is(true));
}
@Test // DATAMONGO-1141
public void updatePushEachWithDocumentSortShouldRenderCorrectly() {
Update update = new Update().push("list")
.sort(Sort.by(new Order(Direction.ASC, "value"), new Order(Direction.ASC, "field")))
.each(Collections.emptyList());
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithList.class));
Document push = getAsDocument(mappedObject, "$push");
Document key = getAsDocument(push, "list");
assertThat(key.containsKey("$sort"), is(true));
assertThat((Document) key.get("$sort"), equalTo(new Document("renamed-value", 1).append("field", 1)));
assertThat(key.containsKey("$each"), is(true));
}
@Test // DATAMONGO-1141
public void updatePushEachWithSortShouldRenderCorrectlyWhenUsingMultiplePush() {
Update update = new Update().push("authors").sort(Direction.ASC).each("Harry").push("chapters")
.sort(Sort.by(Direction.ASC, "order")).each(Collections.emptyList());
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class));
Document push = getAsDocument(mappedObject, "$push");
Document key1 = getAsDocument(push, "authors");
assertThat(key1.containsKey("$sort"), is(true));
assertThat((Integer) key1.get("$sort"), is(1));
assertThat(key1.containsKey("$each"), is(true));
Document key2 = getAsDocument(push, "chapters");
assertThat(key2.containsKey("$sort"), is(true));
assertThat((Document) key2.get("$sort"), equalTo(new Document("order", 1)));
assertThat(key2.containsKey("$each"), is(true));
}
@Test // DATAMONGO-410
public void testUpdateMapperShouldConsiderCustomWriteTarget() {
List<NestedEntity> someValues = Arrays.asList(new NestedEntity("spring"), new NestedEntity("data"),
new NestedEntity("mongodb"));
NestedEntity[] array = new NestedEntity[someValues.size()];
Update update = new Update().pushAll("collectionOfNestedEntities", someValues.toArray(array));
mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(DomainEntity.class));
verify(writingConverterSpy, times(3)).convert(Mockito.any(NestedEntity.class));
}
@Test // DATAMONGO-404
public void createsDbRefForEntityIdOnPulls() {
Update update = new Update().pull("dbRefAnnotatedList.id", "2");
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DocumentWithDBRefCollection.class));
Document pullClause = getAsDocument(mappedObject, "$pull");
assertThat(pullClause.get("dbRefAnnotatedList"), is(new DBRef("entity", "2")));
}
@Test // DATAMONGO-404
public void createsDbRefForEntityOnPulls() {
Entity entity = new Entity();
entity.id = "5";
Update update = new Update().pull("dbRefAnnotatedList", entity);
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DocumentWithDBRefCollection.class));
Document pullClause = getAsDocument(mappedObject, "$pull");
assertThat(pullClause.get("dbRefAnnotatedList"), is(new DBRef("entity", entity.id)));
}
@Test(expected = MappingException.class) // DATAMONGO-404
public void rejectsInvalidFieldReferenceForDbRef() {
Update update = new Update().pull("dbRefAnnotatedList.name", "NAME");
mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(DocumentWithDBRefCollection.class));
}
@Test // DATAMONGO-404
public void rendersNestedDbRefCorrectly() {
Update update = new Update().pull("nested.dbRefAnnotatedList.id", "2");
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(Wrapper.class));
Document pullClause = getAsDocument(mappedObject, "$pull");
assertThat(pullClause.containsKey("mapped.dbRefAnnotatedList"), is(true));
}
@Test // DATAMONGO-468
public void rendersUpdateOfDbRefPropertyWithDomainObjectCorrectly() {
Entity entity = new Entity();
entity.id = "5";
Update update = new Update().set("dbRefProperty", entity);
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DocumentWithDBRefCollection.class));
Document setClause = getAsDocument(mappedObject, "$set");
assertThat(setClause.get("dbRefProperty"), is(new DBRef("entity", entity.id)));
}
@Test // DATAMONGO-862
public void rendersUpdateAndPreservesKeyForPathsNotPointingToProperty() {
Update update = new Update().set("listOfInterface.$.value", "expected-value");
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ParentClass.class));
Document setClause = getAsDocument(mappedObject, "$set");
assertThat(setClause.containsKey("listOfInterface.$.value"), is(true));
}
@Test // DATAMONGO-863
public void doesNotConvertRawDocuments() {
Update update = new Update();
update.pull("options",
new Document("_id", new Document("$in", converter.convertToMongoType(Arrays.asList(1L, 2L)))));
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ParentClass.class));
Document setClause = getAsDocument(mappedObject, "$pull");
Document options = getAsDocument(setClause, "options");
Document idClause = getAsDocument(options, "_id");
List<Object> inClause = getAsDBList(idClause, "$in");
assertThat(inClause, IsIterableContainingInOrder.contains(1L, 2L));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test // DATAMONG0-471
public void testUpdateShouldApply$addToSetCorrectlyWhenUsedWith$each() {
Update update = new Update().addToSet("values").each("spring", "data", "mongodb");
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ListModel.class));
Document addToSet = getAsDocument(mappedObject, "$addToSet");
Document values = getAsDocument(addToSet, "values");
List<Object> each = getAsDBList(values, "$each");
assertThat(each, IsIterableContainingInOrder.contains("spring", "data", "mongodb"));
}
@Test // DATAMONG0-471
public void testUpdateShouldRetainClassTypeInformationWhenUsing$addToSetWith$eachForCustomTypes() {
Update update = new Update().addToSet("models").each(new ModelImpl(2014), new ModelImpl(1), new ModelImpl(28));
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ModelWrapper.class));
Document addToSet = getAsDocument(mappedObject, "$addToSet");
Document values = getAsDocument(addToSet, "models");
List each = getAsDBList(values, "$each");
for (Object updateValue : each) {
assertTypeHint((Document) updateValue, ModelImpl.class);
}
}
@Test // DATAMONGO-897
public void updateOnDbrefPropertyOfInterfaceTypeWithoutExplicitGetterForIdShouldBeMappedCorrectly() {
Update update = new Update().set("referencedDocument", new InterfaceDocumentDefinitionImpl("1", "Foo"));
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DocumentWithReferenceToInterfaceImpl.class));
Document $set = DocumentTestUtils.getAsDocument(mappedObject, "$set");
Object model = $set.get("referencedDocument");
DBRef expectedDBRef = new DBRef("interfaceDocumentDefinitionImpl", "1");
assertThat(model, allOf(instanceOf(DBRef.class), IsEqual.equalTo(expectedDBRef)));
}
@Test // DATAMONGO-847
public void updateMapperConvertsNestedQueryCorrectly() {
Update update = new Update().pull("list", Query.query(Criteria.where("value").in("foo", "bar")));
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ParentClass.class));
Document $pull = DocumentTestUtils.getAsDocument(mappedUpdate, "$pull");
Document list = DocumentTestUtils.getAsDocument($pull, "aliased");
Document value = DocumentTestUtils.getAsDocument(list, "value");
List<Object> $in = DocumentTestUtils.getAsDBList(value, "$in");
assertThat($in, IsIterableContainingInOrder.contains("foo", "bar"));
}
@Test // DATAMONGO-847
public void updateMapperConvertsPullWithNestedQuerfyOnDBRefCorrectly() {
Update update = new Update().pull("dbRefAnnotatedList", Query.query(Criteria.where("id").is("1")));
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DocumentWithDBRefCollection.class));
Document $pull = DocumentTestUtils.getAsDocument(mappedUpdate, "$pull");
Document list = DocumentTestUtils.getAsDocument($pull, "dbRefAnnotatedList");
assertThat(list, equalTo(new org.bson.Document().append("_id", "1")));
}
@Test // DATAMONGO-1077
public void shouldNotRemovePositionalParameter() {
Update update = new Update();
update.unset("dbRefAnnotatedList.$");
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DocumentWithDBRefCollection.class));
Document $unset = DocumentTestUtils.getAsDocument(mappedUpdate, "$unset");
assertThat($unset, equalTo(new org.bson.Document().append("dbRefAnnotatedList.$", 1)));
}
@Test // DATAMONGO-1210
public void mappingEachOperatorShouldNotAddTypeInfoForNonInterfaceNonAbstractTypes() {
Update update = new Update().addToSet("nestedDocs").each(new NestedDocument("nested-1"),
new NestedDocument("nested-2"));
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DocumentWithNestedCollection.class));
assertThat(mappedUpdate, isBsonObject().notContaining("$addToSet.nestedDocs.$each.[0]._class"));
assertThat(mappedUpdate, isBsonObject().notContaining("$addToSet.nestedDocs.$each.[1]._class"));
}
@Test // DATAMONGO-1210
public void mappingEachOperatorShouldAddTypeHintForInterfaceTypes() {
Update update = new Update().addToSet("models").each(new ModelImpl(1), new ModelImpl(2));
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ListModelWrapper.class));
assertThat(mappedUpdate, isBsonObject().containing("$addToSet.models.$each.[0]._class", ModelImpl.class.getName()));
assertThat(mappedUpdate, isBsonObject().containing("$addToSet.models.$each.[1]._class", ModelImpl.class.getName()));
}
@Test // DATAMONGO-1210
public void mappingEachOperatorShouldAddTypeHintForAbstractTypes() {
Update update = new Update().addToSet("list").each(new ConcreteChildClass("foo", "one"),
new ConcreteChildClass("bar", "two"));
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ParentClass.class));
assertThat(mappedUpdate,
isBsonObject().containing("$addToSet.aliased.$each.[0]._class", ConcreteChildClass.class.getName()));
assertThat(mappedUpdate,
isBsonObject().containing("$addToSet.aliased.$each.[1]._class", ConcreteChildClass.class.getName()));
}
@Test // DATAMONGO-1210
public void mappingShouldOnlyRemoveTypeHintFromTopLevelTypeInCaseOfNestedDocument() {
WrapperAroundInterfaceType wait = new WrapperAroundInterfaceType();
wait.interfaceType = new ModelImpl(1);
Update update = new Update().addToSet("listHoldingConcretyTypeWithInterfaceTypeAttribute").each(wait);
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DomainTypeWithListOfConcreteTypesHavingSingleInterfaceTypeAttribute.class));
assertThat(mappedUpdate,
isBsonObject().notContaining("$addToSet.listHoldingConcretyTypeWithInterfaceTypeAttribute.$each.[0]._class"));
assertThat(mappedUpdate,
isBsonObject().containing(
"$addToSet.listHoldingConcretyTypeWithInterfaceTypeAttribute.$each.[0].interfaceType._class",
ModelImpl.class.getName()));
}
@Test // DATAMONGO-1210
public void mappingShouldRetainTypeInformationOfNestedListWhenUpdatingConcreteyParentType() {
ListModelWrapper lmw = new ListModelWrapper();
lmw.models = Collections.singletonList(new ModelImpl(1));
Update update = new Update().set("concreteTypeWithListAttributeOfInterfaceType", lmw);
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(DomainTypeWrappingConcreteyTypeHavingListOfInterfaceTypeAttributes.class));
assertThat(mappedUpdate, isBsonObject().notContaining("$set.concreteTypeWithListAttributeOfInterfaceType._class"));
assertThat(mappedUpdate, isBsonObject()
.containing("$set.concreteTypeWithListAttributeOfInterfaceType.models.[0]._class", ModelImpl.class.getName()));
}
@Test // DATAMONGO-1236
public void mappingShouldRetainTypeInformationForObjectValues() {
Update update = new Update().set("value", new NestedDocument("kaladin"));
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithObject.class));
assertThat(mappedUpdate, isBsonObject().containing("$set.value.name", "kaladin"));
assertThat(mappedUpdate, isBsonObject().containing("$set.value._class", NestedDocument.class.getName()));
}
@Test // DATAMONGO-1236
public void mappingShouldNotRetainTypeInformationForConcreteValues() {
Update update = new Update().set("concreteValue", new NestedDocument("shallan"));
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithObject.class));
assertThat(mappedUpdate, isBsonObject().containing("$set.concreteValue.name", "shallan"));
assertThat(mappedUpdate, isBsonObject().notContaining("$set.concreteValue._class"));
}
@Test // DATAMONGO-1236
public void mappingShouldRetainTypeInformationForObjectValuesWithAlias() {
Update update = new Update().set("value", new NestedDocument("adolin"));
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithAliasedObject.class));
assertThat(mappedUpdate, isBsonObject().containing("$set.renamed-value.name", "adolin"));
assertThat(mappedUpdate, isBsonObject().containing("$set.renamed-value._class", NestedDocument.class.getName()));
}
@Test // DATAMONGO-1236
public void mappingShouldRetrainTypeInformationWhenValueTypeOfMapDoesNotMatchItsDeclaration() {
Map<Object, Object> map = Collections.singletonMap("szeth", new NestedDocument("son-son-vallano"));
Update update = new Update().set("map", map);
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithObjectMap.class));
assertThat(mappedUpdate, isBsonObject().containing("$set.map.szeth.name", "son-son-vallano"));
assertThat(mappedUpdate, isBsonObject().containing("$set.map.szeth._class", NestedDocument.class.getName()));
}
@Test // DATAMONGO-1236
public void mappingShouldNotContainTypeInformationWhenValueTypeOfMapMatchesDeclaration() {
Map<Object, NestedDocument> map = Collections.singletonMap("jasnah", new NestedDocument("kholin"));
Update update = new Update().set("concreteMap", map);
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithObjectMap.class));
assertThat(mappedUpdate, isBsonObject().containing("$set.concreteMap.jasnah.name", "kholin"));
assertThat(mappedUpdate, isBsonObject().notContaining("$set.concreteMap.jasnah._class"));
}
@Test // DATAMONGO-1250
@SuppressWarnings("unchecked")
public void mapsUpdateWithBothReadingAndWritingConverterRegistered() {
CustomConversions conversions = new MongoCustomConversions(Arrays.asList(
ClassWithEnum.AllocationToStringConverter.INSTANCE, ClassWithEnum.StringToAllocationConverter.INSTANCE));
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
mappingContext.afterPropertiesSet();
MappingMongoConverter converter = new MappingMongoConverter(mock(DbRefResolver.class), mappingContext);
converter.setCustomConversions(conversions);
converter.afterPropertiesSet();
UpdateMapper mapper = new UpdateMapper(converter);
Update update = new Update().set("allocation", ClassWithEnum.Allocation.AVAILABLE);
Document result = mapper.getMappedObject(update.getUpdateObject(),
mappingContext.getPersistentEntity(ClassWithEnum.class));
assertThat(result, isBsonObject().containing("$set.allocation", ClassWithEnum.Allocation.AVAILABLE.code));
}
@Test // DATAMONGO-1251
public void mapsNullValueCorrectlyForSimpleTypes() {
Update update = new Update().set("value", null);
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ConcreteChildClass.class));
Document $set = DocumentTestUtils.getAsDocument(mappedUpdate, "$set");
assertThat($set.containsKey("value"), is(true));
assertThat($set.get("value"), nullValue());
}
@Test // DATAMONGO-1251
public void mapsNullValueCorrectlyForJava8Date() {
Update update = new Update().set("date", null);
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ClassWithJava8Date.class));
Document $set = DocumentTestUtils.getAsDocument(mappedUpdate, "$set");
assertThat($set.containsKey("date"), is(true));
assertThat($set.get("value"), nullValue());
}
@Test // DATAMONGO-1251
public void mapsNullValueCorrectlyForCollectionTypes() {
Update update = new Update().set("values", null);
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ListModel.class));
Document $set = DocumentTestUtils.getAsDocument(mappedUpdate, "$set");
assertThat($set.containsKey("values"), is(true));
assertThat($set.get("value"), nullValue());
}
@Test // DATAMONGO-1251
public void mapsNullValueCorrectlyForPropertyOfNestedDocument() {
Update update = new Update().set("concreteValue.name", null);
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithObject.class));
Document $set = DocumentTestUtils.getAsDocument(mappedUpdate, "$set");
assertThat($set.containsKey("concreteValue.name"), is(true));
assertThat($set.get("concreteValue.name"), nullValue());
}
@Test // DATAMONGO-1288
public void mapsAtomicIntegerToIntegerCorrectly() {
Update update = new Update().set("intValue", new AtomicInteger(10));
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(SimpleValueHolder.class));
Document $set = DocumentTestUtils.getAsDocument(mappedUpdate, "$set");
assertThat($set.get("intValue"), Is.is(10));
}
@Test // DATAMONGO-1288
public void mapsAtomicIntegerToPrimitiveIntegerCorrectly() {
Update update = new Update().set("primIntValue", new AtomicInteger(10));
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(SimpleValueHolder.class));
Document $set = DocumentTestUtils.getAsDocument(mappedUpdate, "$set");
assertThat($set.get("primIntValue"), Is.is(10));
}
@Test // DATAMONGO-1404
public void mapsMinCorrectly() {
Update update = new Update().min("minfield", 10);
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(SimpleValueHolder.class));
assertThat(mappedUpdate, isBsonObject().containing("$min", new Document("minfield", 10)));
}
@Test // DATAMONGO-1404
public void mapsMaxCorrectly() {
Update update = new Update().max("maxfield", 999);
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(SimpleValueHolder.class));
assertThat(mappedUpdate, isBsonObject().containing("$max", new Document("maxfield", 999)));
}
@Test // DATAMONGO-1423
@SuppressWarnings("unchecked")
public void mappingShouldConsiderCustomConvertersForEnumMapKeys() {
CustomConversions conversions = new MongoCustomConversions(Arrays.asList(
ClassWithEnum.AllocationToStringConverter.INSTANCE, ClassWithEnum.StringToAllocationConverter.INSTANCE));
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
mappingContext.afterPropertiesSet();
MappingMongoConverter converter = new MappingMongoConverter(mock(DbRefResolver.class), mappingContext);
converter.setCustomConversions(conversions);
converter.afterPropertiesSet();
UpdateMapper mapper = new UpdateMapper(converter);
Update update = new Update().set("enumAsMapKey", Collections.singletonMap(ClassWithEnum.Allocation.AVAILABLE, 100));
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
mappingContext.getPersistentEntity(ClassWithEnum.class));
Document $set = DocumentTestUtils.getAsDocument(mappedUpdate, "$set");
assertThat($set.containsKey("enumAsMapKey"), is(true));
Document enumAsMapKey = $set.get("enumAsMapKey", Document.class);
assertThat(enumAsMapKey.get("AVAILABLE"), is(100));
}
@Test // DATAMONGO-1176
public void mappingShouldPrepareUpdateObjectForMixedOperatorsAndFields() {
Document document = new Document("key", "value").append("$set", new Document("a", "b").append("x", "y"));
Document mappedObject = mapper.getMappedObject(document, context.getPersistentEntity(SimpleValueHolder.class));
assertThat(mappedObject.get("$set"), is(equalTo(new Document("a", "b").append("x", "y").append("key", "value"))));
assertThat(mappedObject.size(), is(1));
}
@Test // DATAMONGO-1176
public void mappingShouldReturnReplaceObject() {
Document document = new Document("key", "value").append("a", "b").append("x", "y");
Document mappedObject = mapper.getMappedObject(document, context.getPersistentEntity(SimpleValueHolder.class));
assertThat(mappedObject.get("key"), is(equalTo("value")));
assertThat(mappedObject.get("a"), is(equalTo("b")));
assertThat(mappedObject.get("x"), is(equalTo("y")));
assertThat(mappedObject.size(), is(3));
}
@Test // DATAMONGO-1176
public void mappingShouldReturnUpdateObject() {
Document document = new Document("$push", new Document("x", "y")).append("$set", new Document("a", "b"));
Document mappedObject = mapper.getMappedObject(document, context.getPersistentEntity(SimpleValueHolder.class));
assertThat(mappedObject.get("$push"), is(equalTo(new Document("x", "y"))));
assertThat(mappedObject.get("$set"), is(equalTo(new Document("a", "b"))));
assertThat(mappedObject.size(), is(2));
}
@Test // DATAMONGO-1486
public void mappingShouldConvertMapKeysToString() {
Update update = new Update().set("map", Collections.singletonMap(25, "#StarTrek50"));
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithObjectMap.class));
Document mapToSet = getAsDocument(getAsDocument(mappedUpdate, "$set"), "map");
for (Object key : mapToSet.keySet()) {
assertThat(key, is(instanceOf(String.class)));
}
}
static class DomainTypeWrappingConcreteyTypeHavingListOfInterfaceTypeAttributes {
ListModelWrapper concreteTypeWithListAttributeOfInterfaceType;
}
static class DomainTypeWithListOfConcreteTypesHavingSingleInterfaceTypeAttribute {
List<WrapperAroundInterfaceType> listHoldingConcretyTypeWithInterfaceTypeAttribute;
}
static class WrapperAroundInterfaceType {
Model interfaceType;
}
@org.springframework.data.mongodb.core.mapping.Document(collection = "DocumentWithReferenceToInterface")
interface DocumentWithReferenceToInterface {
String getId();
InterfaceDocumentDefinitionWithoutId getReferencedDocument();
}
interface InterfaceDocumentDefinitionWithoutId {
String getValue();
}
static class InterfaceDocumentDefinitionImpl implements InterfaceDocumentDefinitionWithoutId {
@Id String id;
String value;
public InterfaceDocumentDefinitionImpl(String id, String value) {
this.id = id;
this.value = value;
}
@Override
public String getValue() {
return this.value;
}
}
static class DocumentWithReferenceToInterfaceImpl implements DocumentWithReferenceToInterface {
private @Id String id;
@org.springframework.data.mongodb.core.mapping.DBRef //
private InterfaceDocumentDefinitionWithoutId referencedDocument;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public void setModel(InterfaceDocumentDefinitionWithoutId referencedDocument) {
this.referencedDocument = referencedDocument;
}
@Override
public InterfaceDocumentDefinitionWithoutId getReferencedDocument() {
return this.referencedDocument;
}
}
interface Model {}
static class ModelImpl implements Model {
public int value;
public ModelImpl(int value) {
this.value = value;
}
}
public class ModelWrapper {
Model model;
public ModelWrapper() {}
public ModelWrapper(Model model) {
this.model = model;
}
}
static class ListModelWrapper {
List<Model> models;
}
static class ListModel {
List<String> values;
public ListModel(String... values) {
this.values = Arrays.asList(values);
}
}
static class ParentClass {
String id;
@Field("aliased") //
List<? extends AbstractChildClass> list;
@Field //
List<Model> listOfInterface;
public ParentClass(String id, List<? extends AbstractChildClass> list) {
this.id = id;
this.list = list;
}
}
static abstract class AbstractChildClass {
String id;
String value;
String otherValue;
AbstractChildClass someObject;
public AbstractChildClass(String id, String value) {
this.id = id;
this.value = value;
this.otherValue = "other_" + value;
}
}
static class ConcreteChildClass extends AbstractChildClass {
public ConcreteChildClass(String id, String value) {
super(id, value);
}
}
static class DomainEntity {
List<NestedEntity> collectionOfNestedEntities;
public List<NestedEntity> getCollectionOfNestedEntities() {
return collectionOfNestedEntities;
}
}
static class NestedEntity {
String name;
public NestedEntity(String name) {
super();
this.name = name;
}
}
@WritingConverter
static class NestedEntityWriteConverter implements Converter<NestedEntity, Document> {
@Override
public Document convert(NestedEntity source) {
return new Document();
}
}
static class DocumentWithDBRefCollection {
@Id public String id;
@org.springframework.data.mongodb.core.mapping.DBRef //
public List<Entity> dbRefAnnotatedList;
@org.springframework.data.mongodb.core.mapping.DBRef //
public Entity dbRefProperty;
}
static class Entity {
@Id public String id;
String name;
}
static class Wrapper {
@Field("mapped") DocumentWithDBRefCollection nested;
}
static class DocumentWithNestedCollection {
List<NestedDocument> nestedDocs;
}
static class NestedDocument {
String name;
public NestedDocument(String name) {
super();
this.name = name;
}
}
static class EntityWithObject {
Object value;
NestedDocument concreteValue;
}
static class EntityWithList {
List<EntityWithAliasedObject> list;
}
static class EntityWithAliasedObject {
@Field("renamed-value") Object value;
Object field;
}
static class EntityWithObjectMap {
Map<Object, Object> map;
Map<Object, NestedDocument> concreteMap;
}
static class ClassWithEnum {
Allocation allocation;
Map<Allocation, String> enumAsMapKey;
enum Allocation {
AVAILABLE("V"), ALLOCATED("A");
String code;
Allocation(String code) {
this.code = code;
}
public static Allocation of(String code) {
for (Allocation value : values()) {
if (value.code.equals(code)) {
return value;
}
}
throw new IllegalArgumentException();
}
}
enum AllocationToStringConverter implements Converter<Allocation, String> {
INSTANCE;
@Override
public String convert(Allocation source) {
return source.code;
}
}
enum StringToAllocationConverter implements Converter<String, Allocation> {
INSTANCE;
@Override
public Allocation convert(String source) {
return Allocation.of(source);
}
}
}
static class ClassWithJava8Date {
LocalDate date;
}
static class SimpleValueHolder {
Integer intValue;
int primIntValue;
}
}