diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 1c579cb49..a2213d637 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -41,6 +41,7 @@ org.springframework.data spring-data-commons-core + ${data.commons.version} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java index 8cf6a731f..016a61b37 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java @@ -51,6 +51,7 @@ import java.util.*; @SuppressWarnings({"unchecked"}) public class MappingMongoConverter implements MongoConverter, ApplicationContextAware { + private static final String CUSTOM_TYPE_KEY = "_class"; protected static final Log log = LogFactory.getLog(MappingMongoConverter.class); protected GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService(); @@ -116,15 +117,6 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext return null; } - final StandardEvaluationContext spelCtx = new StandardEvaluationContext(); - if (null != applicationContext) { - spelCtx.setBeanResolver(new BeanFactoryResolver(applicationContext)); - } - String[] keySet = dbo.keySet().toArray(new String[]{}); - for (String key : keySet) { - spelCtx.setVariable(key, dbo.get(key)); - } - if ((clazz.isArray() || (clazz.isAssignableFrom(Collection.class) || clazz.isAssignableFrom(List.class))) @@ -147,7 +139,24 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext } // Retrieve persistent entity info - PersistentEntity entity = mappingContext.getPersistentEntity(clazz); + PersistentEntity persistentEntity = mappingContext.getPersistentEntity(clazz); + if (persistentEntity == null) { + persistentEntity = mappingContext.addPersistentEntity(clazz); + } + + return read(persistentEntity, dbo); + } + + private S read(PersistentEntity entity, final DBObject dbo) { + + final StandardEvaluationContext spelCtx = new StandardEvaluationContext(); + if (null != applicationContext) { + spelCtx.setBeanResolver(new BeanFactoryResolver(applicationContext)); + } + String[] keySet = dbo.keySet().toArray(new String[] {}); + for (String key : keySet) { + spelCtx.setVariable(key, dbo.get(key)); + } final List ctorParamNames = new ArrayList(); final S instance = MappingBeanHelper.constructInstance(entity, new PreferredConstructor.ParameterValueProvider() { @@ -174,7 +183,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext }, spelCtx); // Set the ID - final PersistentProperty idProperty = entity.getIdProperty(); + PersistentProperty idProperty = entity.getIdProperty(); if (dbo.containsField("_id") || null != idProperty) { Object idObj = dbo.get("_id"); try { @@ -188,14 +197,18 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext // Set properties not already set in the constructor entity.doWithProperties(new PropertyHandler() { - public void doWithPersistentProperty(PersistentProperty prop) { - String name = prop.getName(); - if (null != idProperty && name.equals(idProperty.getName())) { - return; - } - if (prop.isAssociation()) { - return; + public void doWithPersistentProperty(PersistentProperty prop) { + if (!ctorParamNames.contains(prop.getName())) { + Object obj = getValueInternal(prop, dbo, spelCtx, prop.getValueAnnotation()); + try { + MappingBeanHelper.setProperty(instance, prop, obj, useFieldAccessOnly); + } catch (IllegalAccessException e) { + throw new MappingException(e.getMessage(), e); + } catch (InvocationTargetException e) { + throw new MappingException(e.getMessage(), e); + } } + if (ctorParamNames.contains(prop.getName())) { return; } @@ -214,7 +227,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext // Handle associations entity.doWithAssociations(new AssociationHandler() { public void doWithAssociation(Association association) { - PersistentProperty inverseProp = association.getInverse(); + PersistentProperty inverseProp = association.getInverse(); Object obj = getValueInternal(inverseProp, dbo, spelCtx, inverseProp.getValueAnnotation()); try { MappingBeanHelper.setProperty(instance, inverseProp, obj); @@ -246,6 +259,15 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext } PersistentEntity entity = mappingContext.getPersistentEntity(obj.getClass()); + write(obj, dbo, entity); + } + + protected void write(final Object obj, final DBObject dbo, PersistentEntity entity) { + + if (obj == null) { + return; + } + if (null == entity) { // Must not have explictly added this entity yet entity = mappingContext.addPersistentEntity(obj.getClass()); @@ -256,7 +278,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext } // Write the ID - final PersistentProperty idProperty = entity.getIdProperty(); + final PersistentProperty idProperty = entity.getIdProperty(); if (!dbo.containsField("_id") && null != idProperty) { Object idObj = null; try { @@ -274,7 +296,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext // Write the properties entity.doWithProperties(new PropertyHandler() { - public void doWithPersistentProperty(PersistentProperty prop) { + public void doWithPersistentProperty(PersistentProperty prop) { String name = prop.getName(); if (null != idProperty && name.equals(idProperty.getName())) { return; @@ -304,7 +326,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext entity.doWithAssociations(new AssociationHandler() { public void doWithAssociation(Association association) { - PersistentProperty inverseProp = association.getInverse(); + PersistentProperty inverseProp = association.getInverse(); Class type = inverseProp.getType(); Object propertyObj = null; try { @@ -336,7 +358,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext } } - protected void writePropertyInternal(PersistentProperty prop, Object obj, DBObject dbo) { + protected void writePropertyInternal(PersistentProperty prop, Object obj, DBObject dbo) { org.springframework.data.document.mongodb.mapping.DBRef dbref = prop.getField() .getAnnotation(org.springframework.data.document.mongodb.mapping.DBRef.class); String name = prop.getName(); @@ -352,8 +374,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext } } else { BasicDBObject propDbObj = new BasicDBObject(); - //dbo.put("_class", prop.getType().getName()); - write(propObjItem, propDbObj); + write(propObjItem, propDbObj, mappingContext.getPersistentEntity(prop.getTypeInformation())); dbList.add(propDbObj); } } @@ -370,7 +391,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext } } else { BasicDBObject propDbObj = new BasicDBObject(); - write(obj, propDbObj); + write(obj, propDbObj, mappingContext.getPersistentEntity(prop.getTypeInformation())); dbo.put(name, propDbObj); } } @@ -411,7 +432,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext } DBObject dbo = new BasicDBObject(); - PersistentProperty idProperty = targetEntity.getIdProperty(); + PersistentProperty idProperty = targetEntity.getIdProperty(); ObjectId id = null; try { id = MappingBeanHelper.getProperty(target, idProperty, ObjectId.class, useFieldAccessOnly); @@ -436,7 +457,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext return dbo; } - protected Object getValueInternal(PersistentProperty prop, DBObject dbo, StandardEvaluationContext ctx, Value spelExpr) { + protected Object getValueInternal(PersistentProperty prop, DBObject dbo, StandardEvaluationContext ctx, Value spelExpr) { String name = prop.getName(); Object o; if (null != spelExpr) { @@ -449,27 +470,26 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext } Object dbObj = from.get(name); if (dbObj instanceof DBObject) { - Class type = prop.getType(); - if (type.isAssignableFrom(Map.class) && dbObj instanceof DBObject) { - Map m = new LinkedHashMap(); + if (prop.isMap() && dbObj instanceof DBObject) { + + // We have to find a potentially stored class to be used first. + Class toType = findTypeToBeUsed((DBObject) dbObj); + Map m = new LinkedHashMap(); + for (Map.Entry entry : ((Map) ((DBObject) dbObj).toMap()).entrySet()) { - Class toType = null; - if (entry.getKey().equals("_class")) { - try { - toType = Class.forName(entry.getValue().toString()); - } catch (ClassNotFoundException e) { - throw new MappingException(e.getMessage(), e); - } - } else if (null != entry.getValue() && entry.getValue() instanceof DBObject) { - m.put(entry.getKey(), read((null != toType ? toType : type), (DBObject) entry.getValue())); + if (entry.getKey().equals(CUSTOM_TYPE_KEY)) { + continue; + } + if (null != entry.getValue() && entry.getValue() instanceof DBObject) { + m.put(entry.getKey(), read((null != toType ? toType : prop.getTypeInformation().getMapValueType()), (DBObject) entry.getValue())); } else { m.put(entry.getKey(), entry.getValue()); } } return m; - } else if (type.isArray() && dbObj instanceof BasicDBObject && ((DBObject) dbObj).keySet().size() == 0) { + } else if (prop.isArray() && dbObj instanceof BasicDBObject && ((DBObject) dbObj).keySet().size() == 0) { // It's empty - return Array.newInstance(type.getComponentType(), 0); + return Array.newInstance(prop.getType().getComponentType(), 0); } else if (prop.isCollection() && dbObj instanceof BasicDBList) { BasicDBList dbObjList = (BasicDBList) dbObj; Object[] items = (Object[]) Array.newInstance(prop.getComponentType(), dbObjList.size()); @@ -485,6 +505,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext } return Arrays.asList(items); } + // It's a complex object, have to read it in if (dbo.containsField("_class")) { try { @@ -495,7 +516,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext throw new MappingException(e.getMessage(), e); } } else { - o = read(type, (DBObject) dbObj); + o = read(mappingContext.getPersistentEntity(prop.getTypeInformation()), (DBObject) dbObj); } } else { o = dbObj; @@ -503,6 +524,22 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext } return o; } + + /** + * Returns the type to be used to convert the DBObject given to. + * + * @param dbObject + * @return + */ + private Class findTypeToBeUsed(DBObject dbObject) { + Object classToBeUsed = dbObject.get(CUSTOM_TYPE_KEY); + + try { + return Class.forName(classToBeUsed.toString()); + } catch (ClassNotFoundException e) { + throw new MappingException(e.getMessage(), e); + } + } /** * Simple singleton to convert {@link ObjectId}s to their {@link String} representation. @@ -556,16 +593,16 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext } } - protected class PersistentPropertyWrapper { - private final PersistentProperty property; + protected class PersistentPropertyWrapper { + private final PersistentProperty property; private final DBObject target; - public PersistentPropertyWrapper(PersistentProperty property, DBObject target) { + public PersistentPropertyWrapper(PersistentProperty property, DBObject target) { this.property = property; this.target = target; } - public PersistentProperty getProperty() { + public PersistentProperty getProperty() { return property; } @@ -573,5 +610,4 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext return target; } } - } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingConfigurationBuilder.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingConfigurationBuilder.java index 2d8e4891b..e1689008c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingConfigurationBuilder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingConfigurationBuilder.java @@ -33,6 +33,7 @@ import org.springframework.data.document.mongodb.index.Indexed; import org.springframework.data.mapping.BasicMappingConfigurationBuilder; import org.springframework.data.mapping.MappingBeanHelper; import org.springframework.data.mapping.model.*; +import org.springframework.data.util.TypeInformation; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; @@ -59,8 +60,8 @@ public class MongoMappingConfigurationBuilder extends BasicMappingConfigurationB } @Override - public PersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, Class type) throws MappingConfigurationException { - PersistentProperty property = new MongoPersistentProperty(field.getName(), type, field, descriptor); + public PersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor, TypeInformation information) throws MappingConfigurationException { + PersistentProperty property = new MongoPersistentProperty(field, descriptor, information); if (field.isAnnotationPresent(Indexed.class)) { Indexed index = field.getAnnotation(Indexed.class); String collection = index.collection(); @@ -75,10 +76,11 @@ public class MongoMappingConfigurationBuilder extends BasicMappingConfigurationB return property; } - @SuppressWarnings({"unchecked"}) @Override - public PersistentEntity createPersistentEntity(Class type, MappingContext mappingContext) throws MappingConfigurationException { - MongoPersistentEntity entity = new MongoPersistentEntity(mappingContext, type); + public PersistentEntity createPersistentEntity(TypeInformation typeInformation, MappingContext mappingContext) throws MappingConfigurationException { + + MongoPersistentEntity entity = new MongoPersistentEntity(mappingContext, typeInformation); + Class type = typeInformation.getType(); // Check for special collection setting if (type.isAnnotationPresent(Document.class)) { @@ -117,7 +119,7 @@ public class MongoMappingConfigurationBuilder extends BasicMappingConfigurationB } @Override - public Association createAssociation(PersistentProperty property) { + public Association createAssociation(PersistentProperty property) { return super.createAssociation(property); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoPersistentEntity.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoPersistentEntity.java index e3d85626c..dee231fd8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoPersistentEntity.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoPersistentEntity.java @@ -18,17 +18,18 @@ package org.springframework.data.document.mongodb.mapping; import org.springframework.data.mapping.BasicPersistentEntity; import org.springframework.data.mapping.model.MappingContext; +import org.springframework.data.util.TypeInformation; /** * @author Jon Brisbin */ -public class MongoPersistentEntity extends BasicPersistentEntity { +public class MongoPersistentEntity extends BasicPersistentEntity { protected String collection; - @SuppressWarnings({"unchecked"}) - public MongoPersistentEntity(MappingContext mappingContext, Class type) { - super(mappingContext, type); + + public MongoPersistentEntity(MappingContext mappingContext, TypeInformation typeInformation) { + super(mappingContext, typeInformation); } public String getCollection() { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoPersistentProperty.java index 02e640b6a..4d89918bd 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoPersistentProperty.java @@ -23,6 +23,7 @@ import java.util.Set; import org.bson.types.ObjectId; import org.springframework.data.mapping.BasicPersistentProperty; +import org.springframework.data.util.TypeInformation; /** * Mongo specific @@ -31,7 +32,7 @@ import org.springframework.data.mapping.BasicPersistentProperty; * * @author Oliver Gierke */ -public class MongoPersistentProperty extends BasicPersistentProperty { +public class MongoPersistentProperty extends BasicPersistentProperty { private static final Set> SUPPORTED_ID_TYPES = new HashSet>(); private static final Set SUPPORTED_ID_PROPERTY_NAMES = new HashSet(); @@ -48,14 +49,13 @@ public class MongoPersistentProperty extends BasicPersistentProperty { /** * Creates a new {@link MongoPersistentProperty}. * - * @param name - * @param type * @param field * @param propertyDescriptor + * @param owningTypeInformation */ - public MongoPersistentProperty(String name, Class type, Field field, - PropertyDescriptor propertyDescriptor) { - super(name, type, field, propertyDescriptor); + public MongoPersistentProperty(Field field, + PropertyDescriptor propertyDescriptor, TypeInformation owningTypeInformation) { + super(field, propertyDescriptor, owningTypeInformation); } /** diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/GenericMappingTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/GenericMappingTests.java new file mode 100644 index 000000000..a74a81e37 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/GenericMappingTests.java @@ -0,0 +1,76 @@ +package org.springframework.data.document.mongodb.mapping; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.document.mongodb.convert.MappingMongoConverter; +import org.springframework.data.document.mongodb.convert.MongoConverter; +import org.springframework.data.mapping.BasicMappingContext; +import org.springframework.data.mapping.model.MappingContext; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Unit tests for testing the mapping works with generic types. + * + * @author Oliver Gierke + */ +public class GenericMappingTests { + + MappingContext context; + MongoConverter converter; + + @Before + public void setUp() { + context = new BasicMappingContext(new MongoMappingConfigurationBuilder(null)); + context.addPersistentEntity(StringWrapper.class); + converter = new MappingMongoConverter(context); + } + + @Test + public void writesGenericTypeCorrectly() { + + StringWrapper wrapper = new StringWrapper(); + wrapper.container = new Container(); + wrapper.container.content = "Foo!"; + + context.addPersistentEntity(StringWrapper.class); + + DBObject dbObject = new BasicDBObject(); + converter.write(wrapper, dbObject); + + Object container = dbObject.get("container"); + assertThat(container, is(notNullValue())); + assertTrue(container instanceof DBObject); + + Object content = ((DBObject) container).get("content"); + assertTrue(content instanceof String); + assertThat((String) content, is("Foo!")); + } + + @Test + public void readsGenericTypeCorrectly() { + + DBObject content = new BasicDBObject("content", "Foo!"); + BasicDBObject container = new BasicDBObject("container", content); + + StringWrapper result = converter.read(StringWrapper.class, container); + assertThat(result.container, is(notNullValue())); + assertThat(result.container.content, is("Foo!")); + } + + public class StringWrapper extends Wrapper { + + } + + public class Wrapper { + Container container; + } + + public class Container { + T content; + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/MappingTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/MappingTests.java index 25c614a33..f2d3489b6 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/MappingTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/MappingTests.java @@ -34,9 +34,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertEquals; /** * @author Jon Brisbin diff --git a/spring-data-mongodb/template.mf b/spring-data-mongodb/template.mf index 34f11fe20..55eb4fb9f 100644 --- a/spring-data-mongodb/template.mf +++ b/spring-data-mongodb/template.mf @@ -19,6 +19,7 @@ Import-Template: org.springframework.data.domain.*;version="[1.0.0, 2.0.0)", org.springframework.data.document.*;version="[1.0.0, 2.0.0)", org.springframework.data.repository.*;version="[1.0.0, 2.0.0)", + org.springframework.data.util.*;version="[1.0.0, 2.0.0)", org.springframework.expression.*;version="[3.0.0, 4.0.0)", org.springframework.expression.common.*;version="[3.0.0, 4.0.0)", org.springframework.expression.spel.standard.*;version="[3.0.0, 4.0.0)",