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 032c0e83f..47f1f96f5 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 @@ -1380,8 +1380,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App return (T) dbref; } - Object object = dbref == null ? null : path.getPathItem(dbref.getId(), dbref.getCollectionName()); - return (T) (object != null ? object : readAndConvertDBRef(dbref, type, path, rawType)); + T object = dbref == null ? null : path.getPathItem(dbref.getId(), dbref.getCollectionName(), (Class) rawType); + return object != null ? object : readAndConvertDBRef(dbref, type, path, rawType); } private T readAndConvertDBRef(DBRef dbref, TypeInformation type, ObjectPath path, final Class rawType) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ObjectPath.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ObjectPath.java index 5334c5761..f08a736e7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ObjectPath.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ObjectPath.java @@ -20,6 +20,8 @@ import java.util.List; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -33,11 +35,12 @@ import org.springframework.util.StringUtils; * @author Thomas Darimont * @author Oliver Gierke * @author Mark Paluch + * @author Christoph Strobl * @since 1.6 */ class ObjectPath { - public static final ObjectPath ROOT = new ObjectPath(); + static final ObjectPath ROOT = new ObjectPath(); private final ObjectPathItem[] items; @@ -67,9 +70,9 @@ class ObjectPath { * @param object must not be {@literal null}. * @param entity must not be {@literal null}. * @param id must not be {@literal null}. - * @return + * @return new instance of {@link ObjectPath}. */ - public ObjectPath push(Object object, MongoPersistentEntity entity, Object id) { + ObjectPath push(Object object, MongoPersistentEntity entity, Object id) { Assert.notNull(object, "Object must not be null!"); Assert.notNull(entity, "MongoPersistentEntity must not be null!"); @@ -79,14 +82,16 @@ class ObjectPath { } /** - * Returns the object with the given id and stored in the given collection if it's contained in the {@link ObjectPath} - * . + * Returns the object with the given id and stored in the given collection if it's contained in the + * {@link ObjectPath}. * * @param id must not be {@literal null}. * @param collection must not be {@literal null} or empty. * @return + * @deprecated use {@link #getPathItem(Object, String, Class)}. */ - public Object getPathItem(Object id, String collection) { + @Deprecated + Object getPathItem(Object id, String collection) { Assert.notNull(id, "Id must not be null!"); Assert.hasText(collection, "Collection name must not be null!"); @@ -95,11 +100,7 @@ class ObjectPath { Object object = item.getObject(); - if (object == null) { - continue; - } - - if (item.getIdValue() == null) { + if (object == null || item.getIdValue() == null) { continue; } @@ -111,12 +112,45 @@ class ObjectPath { return null; } + /** + * Get the object with given {@literal id}, stored in the {@literal collection} that is assignable to the given + * {@literal type} or {@literal null} if no match found. + * + * @param id must not be {@literal null}. + * @param collection must not be {@literal null} or empty. + * @param type must not be {@literal null}. + * @return {@literal null} when no match found. + * @since 2.0 + */ + T getPathItem(Object id, String collection, Class type) { + + Assert.notNull(id, "Id must not be null!"); + Assert.hasText(collection, "Collection name must not be null!"); + Assert.notNull(type, "Type must not be null!"); + + for (ObjectPathItem item : items) { + + Object object = item.getObject(); + + if (object == null || item.getIdValue() == null) { + continue; + } + + if (collection.equals(item.getCollection()) && id.equals(item.getIdValue()) + && ClassUtils.isAssignable(type, object.getClass())) { + return (T) object; + } + } + + return null; + } + /** * Returns the current object of the {@link ObjectPath} or {@literal null} if the path is empty. * * @return */ - public Object getCurrentObject() { + Object getCurrentObject() { return items.length == 0 ? null : items[items.length - 1].getObject(); } @@ -131,10 +165,10 @@ class ObjectPath { return "[empty]"; } - List strings = new ArrayList(items.length); + List strings = new ArrayList<>(items.length); for (ObjectPathItem item : items) { - strings.add(item.object.toString()); + strings.add(ObjectUtils.nullSafeToString(item.object)); } return StringUtils.collectionToDelimitedString(strings, " -> "); @@ -166,15 +200,15 @@ class ObjectPath { this.collection = collection; } - public Object getObject() { + Object getObject() { return object; } - public Object getIdValue() { + Object getIdValue() { return idValue; } - public String getCollection() { + String getCollection() { return collection; } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateDbRefTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateDbRefTests.java new file mode 100644 index 000000000..673626412 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateDbRefTests.java @@ -0,0 +1,110 @@ +/* + * Copyright 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; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.mongodb.core.query.Criteria.*; +import static org.springframework.data.mongodb.core.query.Query.*; + +import lombok.Data; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.mongodb.MongoClient; + +/** + * {@link org.springframework.data.mongodb.core.mapping.DBRef} related integration tests for + * {@link org.springframework.data.mongodb.core.MongoTemplate}. + * + * @author Christoph Strobl + */ +public class MongoTemplateDbRefTests { + + MongoTemplate template; + + @Before + public void setUp() { + + template = new MongoTemplate(new MongoClient(), "mongo-template-dbref-tests"); + + template.dropCollection(RefCycleLoadingIntoDifferentTypeRoot.class); + template.dropCollection(RefCycleLoadingIntoDifferentTypeIntermediate.class); + template.dropCollection(RefCycleLoadingIntoDifferentTypeRootView.class); + } + + @Test // DATAMONGO-1703 + public void shouldLoadRefIntoDifferentTypeCorrectly() { + + // init root + RefCycleLoadingIntoDifferentTypeRoot root = new RefCycleLoadingIntoDifferentTypeRoot(); + root.id = "root-1"; + root.content = "jon snow"; + template.save(root); + + // init one and set view id ref to root.id + RefCycleLoadingIntoDifferentTypeIntermediate intermediate = new RefCycleLoadingIntoDifferentTypeIntermediate(); + intermediate.id = "one-1"; + intermediate.refToRootView = new RefCycleLoadingIntoDifferentTypeRootView(); + intermediate.refToRootView.id = root.id; + + template.save(intermediate); + + // add one ref to root + root.refToIntermediate = intermediate; + template.save(root); + + RefCycleLoadingIntoDifferentTypeRoot loaded = template.findOne(query(where("id").is(root.id)), + RefCycleLoadingIntoDifferentTypeRoot.class); + + assertThat(loaded.content).isEqualTo("jon snow"); + assertThat(loaded.getRefToIntermediate()).isInstanceOf(RefCycleLoadingIntoDifferentTypeIntermediate.class); + assertThat(loaded.getRefToIntermediate().getRefToRootView()) + .isInstanceOf(RefCycleLoadingIntoDifferentTypeRootView.class); + assertThat(loaded.getRefToIntermediate().getRefToRootView().getContent()).isEqualTo("jon snow"); + } + + @Data + @Document(collection = "cycle-with-different-type-root") + static class RefCycleLoadingIntoDifferentTypeRoot { + + @Id String id; + String content; + @DBRef RefCycleLoadingIntoDifferentTypeIntermediate refToIntermediate; + } + + @Data + @Document(collection = "cycle-with-different-type-intermediate") + static class RefCycleLoadingIntoDifferentTypeIntermediate { + + @Id String id; + @DBRef RefCycleLoadingIntoDifferentTypeRootView refToRootView; + } + + @Data + @Document(collection = "cycle-with-different-type-root") + static class RefCycleLoadingIntoDifferentTypeRootView { + + @Id String id; + String content; + } + +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ObjectPathUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ObjectPathUnitTests.java new file mode 100644 index 000000000..4a4abe8b6 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ObjectPathUnitTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 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.assertj.core.api.Assertions.*; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.util.ClassTypeInformation; + +/** + * @author Christoph Strobl + */ +public class ObjectPathUnitTests { + + MongoPersistentEntity one; + MongoPersistentEntity two; + MongoPersistentEntity three; + + @Before + public void setUp() { + + one = new BasicMongoPersistentEntity(ClassTypeInformation.from(EntityOne.class)); + two = new BasicMongoPersistentEntity(ClassTypeInformation.from(EntityTwo.class)); + three = new BasicMongoPersistentEntity(ClassTypeInformation.from(EntityThree.class)); + } + + @Test // DATAMONGO-1703 + public void getPathItemShouldReturnMatch() { + + ObjectPath path = ObjectPath.ROOT.push(new EntityOne(), one, "id-1"); + + assertThat(path.getPathItem("id-1", "one", EntityOne.class)).isNotNull(); + } + + @Test // DATAMONGO-1703 + public void getPathItemShouldReturnNullWhenNoTypeMatchFound() { + + ObjectPath path = ObjectPath.ROOT.push(new EntityOne(), one, "id-1"); + + assertThat(path.getPathItem("id-1", "one", EntityThree.class)).isNull(); + } + + @Test // DATAMONGO-1703 + public void getPathItemShouldReturnCachedItemWhenIdAndCollectionMatchAndIsAssignable() { + + ObjectPath path = ObjectPath.ROOT.push(new EntityTwo(), one, "id-1"); + + assertThat(path.getPathItem("id-1", "one", EntityOne.class)).isNotNull(); + } + + @Test // DATAMONGO-1703 + public void getPathItemShouldReturnNullWhenIdAndCollectionMatchButNotAssignable() { + + ObjectPath path = ObjectPath.ROOT.push(new EntityOne(), one, "id-1"); + + assertThat(path.getPathItem("id-1", "one", EntityTwo.class)).isNull(); + } + + @Test // DATAMONGO-1703 + public void getPathItemShouldReturnNullWhenIdAndCollectionMatchAndAssignableToInterface() { + + ObjectPath path = ObjectPath.ROOT.push(new EntityThree(), one, "id-1"); + + assertThat(path.getPathItem("id-1", "one", ValueInterface.class)).isNotNull(); + } + + @Document(collection = "one") + static class EntityOne { + + } + + static class EntityTwo extends EntityOne { + + } + + interface ValueInterface { + + } + + @Document(collection = "three") + static class EntityThree implements ValueInterface { + + } +}