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 a7e1e3605..065a1d750 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 @@ -31,11 +31,13 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.CollectionFactory; import org.springframework.core.convert.ConversionException; +import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.ConversionServiceFactory; import org.springframework.data.convert.EntityInstantiator; import org.springframework.data.convert.TypeMapper; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.AssociationHandler; +import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.BeanWrapper; @@ -46,6 +48,7 @@ import org.springframework.data.mapping.model.PersistentEntityParameterValueProv import org.springframework.data.mapping.model.PropertyValueProvider; import org.springframework.data.mapping.model.SpELContext; import org.springframework.data.mapping.model.SpELExpressionEvaluator; +import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; @@ -215,9 +218,9 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App MongoDbPropertyValueProvider provider = new MongoDbPropertyValueProvider(source, evaluator, parent); PersistentEntityParameterValueProvider parameterProvider = new PersistentEntityParameterValueProvider( entity, provider, parent); - parameterProvider.setSpELEvaluator(evaluator); - return parameterProvider; + return new ConverterAwareSpELExpressionParameterValueProvider(evaluator, conversionService, parameterProvider, + parent); } private S read(final MongoPersistentEntity entity, final DBObject dbo, Object parent) { @@ -933,7 +936,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App * (non-Javadoc) * @see org.springframework.data.convert.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty) */ - @SuppressWarnings("unchecked") public T getPropertyValue(MongoPersistentProperty property) { String expression = property.getSpelExpression(); @@ -943,20 +945,60 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App return null; } - TypeInformation type = property.getTypeInformation(); - Class rawType = type.getType(); + return readValue(value, property.getTypeInformation(), parent); + } + } - if (conversions.hasCustomReadTarget(value.getClass(), rawType)) { - return (T) conversionService.convert(value, rawType); - } else if (value instanceof DBRef) { - return (T) (rawType.equals(DBRef.class) ? value : read(type, ((DBRef) value).fetch(), parent)); - } else if (value instanceof BasicDBList) { - return (T) readCollectionOrArray(type, (BasicDBList) value, parent); - } else if (value instanceof DBObject) { - return (T) read(type, (DBObject) value, parent); - } else { - return (T) getPotentiallyConvertedSimpleRead(value, rawType); - } + /** + * Extension of {@link SpELExpressionParameterValueProvider} to recursively trigger value conversion on the raw + * resolved SpEL value. + * + * @author Oliver Gierke + */ + private class ConverterAwareSpELExpressionParameterValueProvider extends + SpELExpressionParameterValueProvider { + + private final Object parent; + + /** + * Creates a new {@link ConverterAwareSpELExpressionParameterValueProvider}. + * + * @param evaluator must not be {@literal null}. + * @param conversionService must not be {@literal null}. + * @param delegate must not be {@literal null}. + */ + public ConverterAwareSpELExpressionParameterValueProvider(SpELExpressionEvaluator evaluator, + ConversionService conversionService, ParameterValueProvider delegate, Object parent) { + + super(evaluator, conversionService, delegate); + this.parent = parent; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.SpELExpressionParameterValueProvider#potentiallyConvertSpelValue(java.lang.Object, org.springframework.data.mapping.PreferredConstructor.Parameter) + */ + @Override + protected T potentiallyConvertSpelValue(Object object, Parameter parameter) { + return readValue(object, parameter.getType(), parent); + } + } + + @SuppressWarnings("unchecked") + private T readValue(Object value, TypeInformation type, Object parent) { + + Class rawType = type.getType(); + + if (conversions.hasCustomReadTarget(value.getClass(), rawType)) { + return (T) conversionService.convert(value, rawType); + } else if (value instanceof DBRef) { + return (T) (rawType.equals(DBRef.class) ? value : read(type, ((DBRef) value).fetch(), parent)); + } else if (value instanceof BasicDBList) { + return (T) readCollectionOrArray(type, (BasicDBList) value, parent); + } else if (value instanceof DBObject) { + return (T) read(type, (DBObject) value, parent); + } else { + return (T) getPotentiallyConvertedSimpleRead(value, rawType); } } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java index 71026e308..c357c5605 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java @@ -54,6 +54,7 @@ import org.springframework.data.mapping.PropertyPath; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mapping.model.MappingInstantiationException; import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; @@ -65,6 +66,7 @@ import com.mongodb.BasicDBObject; import com.mongodb.DB; import com.mongodb.DBObject; import com.mongodb.DBRef; +import com.mongodb.util.JSON; /** * Unit tests for {@link MappingMongoConverter}. @@ -1323,6 +1325,18 @@ public class MappingMongoConverterUnitTests { converter.write(wrapper, dbObject); } + /** + * @see DATAMONGO-592 + */ + @Test + public void recursivelyConvertsSpELReadValue() { + + DBObject input = (DBObject) JSON + .parse("{ \"_id\" : { \"$oid\" : \"50ca271c4566a2b08f2d667a\" }, \"_class\" : \"com.recorder.TestRecorder2$ObjectContainer\", \"property\" : { \"property\" : 100 } }"); + + converter.read(ObjectContainer.class, input); + } + static class GenericType { T content; } @@ -1516,6 +1530,38 @@ public class MappingMongoConverterUnitTests { Throwable throwable; } + @Document + static class PrimitiveContainer { + + @Field("property") + private final int m_property; + + @PersistenceConstructor + public PrimitiveContainer(@Value("#root.property") int a_property) { + m_property = a_property; + } + + public int property() { + return m_property; + } + } + + @Document + static class ObjectContainer { + + @Field("property") + private final PrimitiveContainer m_property; + + @PersistenceConstructor + public ObjectContainer(@Value("#root.property") PrimitiveContainer a_property) { + m_property = a_property; + } + + public PrimitiveContainer property() { + return m_property; + } + } + private class LocalDateToDateConverter implements Converter { public Date convert(LocalDate source) {