diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/DefaultMappingTargetFactory.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/DefaultMappingTargetFactory.java index 9e715fa2fd2..fd704c77ea8 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/DefaultMappingTargetFactory.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/DefaultMappingTargetFactory.java @@ -30,7 +30,7 @@ final class DefaultMappingTargetFactory implements MappingTargetFactory { return ClassUtils.hasConstructor(targetType.getType(), null); } - public Object createTarget(TypeDescriptor targetType) { + public Object createTarget(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return BeanUtils.instantiate(targetType.getType()); } diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/MappingContextHolder.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/MappingContextHolder.java index 2fb39f2a0ea..42456035f14 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/MappingContextHolder.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/MappingContextHolder.java @@ -24,7 +24,7 @@ import org.springframework.core.NamedThreadLocal; * @author Keith Donald * @see SpelMapper#map(Object, Object) */ -abstract class MappingContextHolder { +public abstract class MappingContextHolder { private static final ThreadLocal> mappingContextHolder = new NamedThreadLocal>( "Mapping context"); diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/MappingConverter.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/MappingConverter.java index 2e0cbdf6231..92f13d87443 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/MappingConverter.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/MappingConverter.java @@ -33,10 +33,20 @@ public final class MappingConverter implements GenericConverter { private MappingTargetFactory mappingTargetFactory; + /** + * Creates a new Converter that delegates to the mapper to complete the type conversion process. + * Uses a {@link DefaultMappingTargetFactory} to create the target object to map and return. + * @param mapper the mapper + */ public MappingConverter(Mapper mapper) { this(mapper, new DefaultMappingTargetFactory()); } + /** + * Creates a new Converter that delegates to the mapper to complete the type conversion process. + * Uses the specified MappingTargetFactory to create the target object to map and return. + * @param mapper the mapper + */ public MappingConverter(Mapper mapper, MappingTargetFactory mappingTargetFactory) { this.mapper = mapper; this.mappingTargetFactory = mappingTargetFactory; @@ -52,7 +62,7 @@ public final class MappingConverter implements GenericConverter { if (sourceType.isAssignableTo(targetType) && isCopyByReference(sourceType, targetType)) { return source; } - return createAndMap(targetType, source, sourceType); + return createTargetAndMap(source, sourceType, targetType); } private boolean isCopyByReference(TypeDescriptor sourceType, TypeDescriptor targetType) { @@ -63,13 +73,13 @@ public final class MappingConverter implements GenericConverter { } } - private Object createAndMap(TypeDescriptor targetType, Object source, TypeDescriptor sourceType) { + private Object createTargetAndMap(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (this.mappingTargetFactory.supports(targetType)) { - Object target = this.mappingTargetFactory.createTarget(targetType); + Object target = this.mappingTargetFactory.createTarget(source, sourceType, targetType); return this.mapper.map(source, target); } else { IllegalStateException cause = new IllegalStateException("[" - + this.mappingTargetFactory.getClass().getName() + "] does not support target type [" + + this.mappingTargetFactory.getClass().getName() + "] does not support targetType [" + targetType.getName() + "]"); throw new ConversionFailedException(sourceType, targetType, source, cause); } diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/MappingTargetFactory.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/MappingTargetFactory.java index 788a65dc9dd..1b72dfd39e6 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/MappingTargetFactory.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/MappingTargetFactory.java @@ -41,6 +41,6 @@ public interface MappingTargetFactory { * @param targetType the target object type descriptor * @return the target */ - public Object createTarget(TypeDescriptor targetType); + public Object createTarget(Object source, TypeDescriptor sourceType, TypeDescriptor targetType); } \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapper.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapper.java index 88fc63d05be..ee12a3caf36 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapper.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapper.java @@ -170,7 +170,8 @@ public class SpelMapper implements Mapper { */ public void addNestedMapper(Class sourceType, Class targetType, Mapper nestedMapper, MappingTargetFactory targetFactory) { - this.conversionService.addGenericConverter(sourceType, targetType, new MappingConverter(nestedMapper)); + this.conversionService.addGenericConverter(sourceType, targetType, new MappingConverter(nestedMapper, + targetFactory)); } /** diff --git a/org.springframework.context/src/test/java/org/springframework/mapping/support/SpelMapperTests.java b/org.springframework.context/src/test/java/org/springframework/mapping/support/SpelMapperTests.java index 38c2d86d073..74e5333c837 100644 --- a/org.springframework.context/src/test/java/org/springframework/mapping/support/SpelMapperTests.java +++ b/org.springframework.context/src/test/java/org/springframework/mapping/support/SpelMapperTests.java @@ -14,6 +14,7 @@ import java.util.Set; import org.junit.Test; import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.core.io.ClassPathResource; import org.springframework.expression.AccessException; @@ -201,6 +202,41 @@ public class SpelMapperTests { assertEquals("bar and baz", target.nested.foo); } + @Test + public void mapBeanNestedCustomNestedMapperCustomMappingTargetFactory() { + PersonDto source = new PersonDto(); + final NestedDto nested = new NestedDto(); + nested.foo = "bar"; + source.setNested(nested); + + Person target = new Person(); + + SpelMapper nestedMapper = new SpelMapper(); + nestedMapper.setAutoMappingEnabled(false); + nestedMapper.addMapping("foo").setConverter(new Converter() { + public String convert(String source) { + return source + " and baz"; + } + }); + mapper.addNestedMapper(NestedDto.class, Nested.class, nestedMapper, new MappingTargetFactory() { + public boolean supports(TypeDescriptor targetType) { + return true; + } + + public Object createTarget(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + NestedDto nestedDto = (NestedDto) source; + assertEquals(nested, nestedDto); + return new Nested(); + } + }); + + mapper.setAutoMappingEnabled(false); + mapper.addMapping("nested"); + mapper.map(source, target); + + assertEquals("bar and baz", target.nested.foo); + } + @Test public void mapBeanNestedCustomNestedMapperHandCoded() { PersonDto source = new PersonDto(); @@ -225,6 +261,37 @@ public class SpelMapperTests { assertEquals("bar and baz", target.nested.foo); } + + @Test + public void mapBeanNestedCustomConverterDelegatingToMapper() { + PersonDto source = new PersonDto(); + NestedDto nested = new NestedDto(); + nested.foo = "bar"; + source.setNested(nested); + + Person target = new Person(); + + mapper.getConverterRegistry().addConverter(new Converter() { + public Nested convert(NestedDto source) { + // allows construction of target to be controlled by the converter + Nested nested = new Nested(); + // mapping can do whatever, here we delegate to nested SpelMapper + SpelMapper nestedMapper = new SpelMapper(); + nestedMapper.addMapping("foo").setConverter(new Converter() { + public String convert(String source) { + return source + " and baz"; + } + }); + return (Nested) nestedMapper.map(source, nested); + } + }); + + mapper.setAutoMappingEnabled(false); + mapper.addMapping("nested"); + mapper.map(source, target); + + assertEquals("bar and baz", target.nested.foo); + } @Test public void mapList() {