From d4f358abd076e2139c157d008a7359e47f843a2b Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 15 Mar 2011 17:29:18 +0100 Subject: [PATCH] Adapted introduction of TypeInformation in Spring Data Commons Core. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MappingMongoConverter now uses TypeInformation to inspect field types and nested ones. Created overloaded versions of read(…) and write(…) to work with PersistentEntity directly so that nested calls to those methods can be distinguished from from top level ones. --- spring-data-mongodb/pom.xml | 1 + .../convert/MappingMongoConverter.java | 132 +++++++++++------- .../MongoMappingConfigurationBuilder.java | 14 +- .../mapping/MongoPersistentEntity.java | 9 +- .../mapping/MongoPersistentProperty.java | 12 +- .../mongodb/mapping/GenericMappingTests.java | 76 ++++++++++ .../mongodb/mapping/MappingTests.java | 7 +- spring-data-mongodb/template.mf | 1 + 8 files changed, 185 insertions(+), 67 deletions(-) create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/GenericMappingTests.java 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)",