From 9c9138c4e9704314c5c3dd007ea64f26ab4b3339 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 27 Apr 2011 13:16:50 +0200 Subject: [PATCH] DATADOC-101 - Improved custom converter handling. Converters are now considered for both reading and writing. I also added a shortcut to invoke custom converters for top level types. So far the registered ones had only be used for properties of the given root object, not the root object itself. --- .../convert/MappingMongoConverter.java | 64 ++++++++++-- .../convert/CustomConvertersUnitTests.java | 99 +++++++++++++++++++ 2 files changed, 153 insertions(+), 10 deletions(-) create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/convert/CustomConvertersUnitTests.java 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 2578630e8..f672d0684 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 @@ -25,11 +25,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; -import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; @@ -50,6 +51,7 @@ import org.springframework.core.GenericTypeResolver; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; +import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair; import org.springframework.core.convert.support.ConversionServiceFactory; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.data.mapping.AssociationHandler; @@ -82,7 +84,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext protected static final Log log = LogFactory.getLog(MappingMongoConverter.class); protected final GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService(); - protected final Map, Class> customTypeMapping = new HashMap, Class>(); + protected final Set customTypeMapping = new HashSet(); protected final MappingContext mappingContext; protected SpelExpressionParser spelExpressionParser = new SpelExpressionParser(); protected ApplicationContext applicationContext; @@ -110,7 +112,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext if (null != converters) { for (Converter c : converters) { registerConverter(c); - conversionService.addConverter(c); + } } } @@ -123,9 +125,25 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext */ private void registerConverter(Converter converter) { Class[] arguments = GenericTypeResolver.resolveTypeArguments(converter.getClass(), Converter.class); - if (MONGO_TYPES.contains(arguments[1])) { - customTypeMapping.put(arguments[0], arguments[1]); + if (MONGO_TYPES.contains(arguments[1]) || MONGO_TYPES.contains(arguments[0])) { + customTypeMapping.add(new ConvertiblePair(arguments[0], arguments[1])); } + conversionService.addConverter(converter); + } + + private Class getCustomTarget(Class source, Class expectedTargetType) { + for (ConvertiblePair typePair : customTypeMapping) { + if (typePair.getSourceType().isAssignableFrom(source)) { + + Class targetType = typePair.getTargetType(); + + if (targetType.equals(expectedTargetType) || expectedTargetType == null) { + return targetType; + } + } + } + + return null; } public MappingContext getMappingContext() { @@ -169,6 +187,12 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext if (null == dbo) { return null; } + + Class customTarget = getCustomTarget(clazz, DBObject.class); + + if (customTarget != null) { + return conversionService.convert(dbo, clazz); + } if ((clazz.isArray() || (clazz.isAssignableFrom(Collection.class) @@ -292,6 +316,14 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext if (null == obj) { return; } + + Class customTarget = getCustomTarget(obj.getClass(), DBObject.class); + + if (customTarget != null) { + DBObject result = conversionService.convert(obj, DBObject.class); + dbo.putAll(result); + return; + } PersistentEntity entity = mappingContext.getPersistentEntity(obj.getClass()); write(obj, dbo, entity); @@ -304,7 +336,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext } if (null == entity) { - // Must not have explictly added this entity yet + // Must not have explictly added this entity yet entity = mappingContext.addPersistentEntity(obj.getClass()); if (null == entity) { // We can't map this entity for some reason @@ -470,7 +502,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext } // Lookup potential custom target type - Class basicTargetType = customTypeMapping.get(obj.getClass()); + Class basicTargetType = getCustomTarget(obj.getClass(), null); if (basicTargetType != null) { dbo.put(name, conversionService.convert(obj, basicTargetType)); @@ -480,7 +512,6 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext BasicDBObject propDbObj = new BasicDBObject(); write(obj, propDbObj, mappingContext.getPersistentEntity(prop.getTypeInformation())); dbo.put(name, propDbObj); - } protected void writeMapInternal(Map obj, DBObject dbo) { @@ -546,14 +577,27 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext @SuppressWarnings({"unchecked"}) protected Object getValueInternal(PersistentProperty prop, DBObject dbo, StandardEvaluationContext ctx, Value spelExpr) { - String name = prop.getName(); + + String name = prop.getName(); + Class propertyType = prop.getType(); + Object o; if (null != spelExpr) { Expression x = spelExpressionParser.parseExpression(spelExpr.value()); o = x.getValue(ctx); } else { - DBObject from = dbo; Object dbObj = dbo.get(name); + + if (dbObj == null) { + return null; + } + + Class customTarget = getCustomTarget(dbObj.getClass(), propertyType); + + if (customTarget != null) { + return conversionService.convert(dbObj, propertyType); + } + if (dbObj instanceof DBRef) { dbObj = ((DBRef) dbObj).fetch(); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/convert/CustomConvertersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/convert/CustomConvertersUnitTests.java new file mode 100644 index 000000000..4e95c2e6f --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/convert/CustomConvertersUnitTests.java @@ -0,0 +1,99 @@ +package org.springframework.data.document.mongodb.convert; + +import static org.mockito.Mockito.*; + +import java.util.Arrays; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.document.mongodb.mapping.MongoMappingContext; +import org.springframework.data.document.mongodb.mapping.MongoPersistentEntity; +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class CustomConvertersUnitTests { + + MappingMongoConverter converter; + + @Mock + BarToDBObjectConverter barToDBObjectConverter; + @Mock + DBObjectToBarConverter dbObjectToBarConverter; + + MongoMappingContext context; + MongoPersistentEntity fooEntity; + MongoPersistentEntity barEntity; + + @Before + @SuppressWarnings("unchecked") + public void setUp() { + + context = new MongoMappingContext(); + context.addPersistentEntity(Foo.class); + context.addPersistentEntity(Bar.class); + + when(barToDBObjectConverter.convert(any(Bar.class))).thenReturn(new BasicDBObject()); + when(dbObjectToBarConverter.convert(any(DBObject.class))).thenReturn(new Bar()); + + converter = new MappingMongoConverter(context); + converter.setConverters(Arrays.asList(barToDBObjectConverter, dbObjectToBarConverter)); + } + + @Test + public void nestedToDBObjectConverterGetsInvoked() { + + Foo foo = new Foo(); + foo.bar = new Bar(); + + converter.write(foo, new BasicDBObject()); + verify(barToDBObjectConverter).convert(any(Bar.class)); + } + + @Test + public void nestedFromDBObjectConverterGetsInvoked() { + + BasicDBObject dbObject = new BasicDBObject(); + dbObject.put("bar", new BasicDBObject()); + + converter.read(Foo.class, dbObject); + verify(dbObjectToBarConverter).convert(any(DBObject.class)); + } + + @Test + public void toDBObjectConverterGetsInvoked() { + + converter.write(new Bar(), new BasicDBObject()); + verify(barToDBObjectConverter).convert(any(Bar.class)); + } + + @Test + public void fromDBObjectConverterGetsInvoked() { + + converter.read(Bar.class, new BasicDBObject()); + verify(dbObjectToBarConverter).convert(any(DBObject.class)); + } + + public static class Foo { + public Bar bar; + } + + public static class Bar { + public String foo; + } + + private interface BarToDBObjectConverter extends Converter { + + } + + private interface DBObjectToBarConverter extends Converter { + + } +}