diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/MappingException.java b/org.springframework.context/src/main/java/org/springframework/mapping/MappingException.java index bcf32e381cb..cf5e87aa560 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/MappingException.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/MappingException.java @@ -25,7 +25,7 @@ import java.util.List; * @see Mapper#map(Object, Object) * @author Keith Donald */ -public class MappingException extends RuntimeException { +public final class MappingException extends RuntimeException { private List mappingFailures; diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/MappingFailure.java b/org.springframework.context/src/main/java/org/springframework/mapping/MappingFailure.java index f8d57499ef1..d78916d0746 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/MappingFailure.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/MappingFailure.java @@ -19,7 +19,7 @@ package org.springframework.mapping; * Indicates an individual mapping failed. * @author Keith Donald */ -public class MappingFailure { +public final class MappingFailure { private final Throwable cause; diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/BeanMappableType.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/BeanMappableType.java index 647ac8319f8..e232bef3868 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/BeanMappableType.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/BeanMappableType.java @@ -25,7 +25,11 @@ import org.springframework.expression.EvaluationContext; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardTypeConverter; -class BeanMappableType implements MappableType { +final class BeanMappableType implements MappableType { + + public boolean isInstance(Object object) { + return true; + } public Set getFields(Object object) { Set fields = new LinkedHashSet(); diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/DefaultMappableTypeFactory.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/DefaultMappableTypeFactory.java new file mode 100644 index 00000000000..6a2b08573db --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/DefaultMappableTypeFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.mapping.support; + +/** + * Default mappable type factory that registers Map and Bean mappable types. + * @author Keith Donald + */ +final class DefaultMappableTypeFactory extends MappableTypeFactory { + public DefaultMappableTypeFactory() { + add(new MapMappableType()); + add(new BeanMappableType()); + } +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/DefaultMapperTargetFactory.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/DefaultMapperTargetFactory.java index d98a707d935..73016f54a99 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/DefaultMapperTargetFactory.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/DefaultMapperTargetFactory.java @@ -24,7 +24,7 @@ import org.springframework.util.ClassUtils; * @author Keith Donald * @see BeanUtils#instantiate(Class) */ -class DefaultMapperTargetFactory implements MapperTargetFactory { +final class DefaultMapperTargetFactory implements MapperTargetFactory { public boolean supports(TypeDescriptor targetType) { return ClassUtils.hasConstructor(targetType.getType(), null); diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/MapMappableType.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/MapMappableType.java index ec10a5e62db..aecec1e91b9 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/MapMappableType.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/MapMappableType.java @@ -25,7 +25,11 @@ import org.springframework.expression.EvaluationContext; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardTypeConverter; -class MapMappableType implements MappableType> { +final class MapMappableType implements MappableType> { + + public boolean isInstance(Object object) { + return object instanceof Map; + } public Set getFields(Map object) { LinkedHashSet fields = new LinkedHashSet(object.size(), 1); diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/MappableType.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/MappableType.java index ccaaf43af57..dc3e7758a9f 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/MappableType.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/MappableType.java @@ -37,4 +37,10 @@ interface MappableType { */ EvaluationContext getEvaluationContext(T object, ConversionService conversionService); + /** + * Is this object to map an instanceof this mappable type? + * @param object the object to test + */ + boolean isInstance(Object object); + } \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/MappableTypeFactory.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/MappableTypeFactory.java new file mode 100644 index 00000000000..d844468d875 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/MappableTypeFactory.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.mapping.support; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Generic MappableTypeFactory that has no mappable types registered by default. + * Call {@link #add(MappableType)} to register. + * @author Keith Donald + */ +public class MappableTypeFactory { + + private Set> mappableTypes = new LinkedHashSet>(); + + /** + * Add a MappableType to this factory. + * @param mappableType the mappable type + */ + public void add(MappableType mappableType) { + this.mappableTypes.add(mappableType); + } + + @SuppressWarnings("unchecked") + public MappableType getMappableType(T object) { + for (MappableType type : mappableTypes) { + if (type.isInstance(object)) { + return type; + } + } + throw new IllegalArgumentException("Object of type [" + object.getClass().getName() + + "] not mappable - no suitable MappableType exists"); + } +} \ No newline at end of file diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/MapperConverter.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/MapperConverter.java index 0c0f1e34b30..2f421896341 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/MapperConverter.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/MapperConverter.java @@ -27,7 +27,7 @@ import org.springframework.mapping.Mapper; * The default MapperTargetFactory instantiates a target by calling its default constructor. * @author Keith Donald */ -public class MapperConverter implements GenericConverter { +public final class MapperConverter implements GenericConverter { private Mapper mapper; 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 dfa03cc4843..2fb39f2a0ea 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) */ -class MappingContextHolder { +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/MappingConversionService.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/MappingConversionService.java new file mode 100644 index 00000000000..535283777b7 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/MappingConversionService.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.mapping.support; + +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.core.convert.support.GenericConverter; + +final class MappingConversionService extends DefaultConversionService { + + @Override + protected GenericConverter getDefaultConverter(TypeDescriptor sourceType, TypeDescriptor targetType) { + return new MapperConverter(new SpelMapper()); + } + +} \ 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 4e8dc251dd6..ccff06bdb92 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 @@ -19,17 +19,13 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.GenericTypeResolver; -import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterRegistry; -import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.core.convert.support.GenericConverter; import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationException; import org.springframework.expression.Expression; @@ -39,11 +35,13 @@ import org.springframework.expression.spel.standard.SpelExpressionParserConfigur import org.springframework.mapping.Mapper; import org.springframework.mapping.MappingException; import org.springframework.mapping.MappingFailure; +import org.springframework.util.Assert; /** * A general-purpose object mapper implementation based on the Spring Expression Language (SpEL). * @author Keith Donald * @see #setAutoMappingEnabled(boolean) + * @see #setMappableTypeFactory(MappableTypeFactory) * @see #addMapping(String) * @see #addMapping(String, String) * @see #getConverterRegistry() @@ -52,8 +50,6 @@ public class SpelMapper implements Mapper { private static final Log logger = LogFactory.getLog(SpelMapper.class); - private static final MappableTypeFactory mappableTypeFactory = new MappableTypeFactory(); - private static final SpelExpressionParser sourceExpressionParser = new SpelExpressionParser(); private static final SpelExpressionParser targetExpressionParser = new SpelExpressionParser( @@ -62,6 +58,8 @@ public class SpelMapper implements Mapper { private final Set mappings = new LinkedHashSet(); + private MappableTypeFactory mappableTypeFactory = new DefaultMappableTypeFactory(); + private boolean autoMappingEnabled = true; private MappingConversionService conversionService = new MappingConversionService(); @@ -77,6 +75,15 @@ public class SpelMapper implements Mapper { this.autoMappingEnabled = autoMappingEnabled; } + /** + * Sets the factory for {@link MappableType mappable types} supported by this mapper. + * Default is {@link DefaultMappableTypeFactory}. + * @param mappableTypeFactory the mappableTypeFactory + */ + public void setMappableTypeFactory(MappableTypeFactory mappableTypeFactory) { + this.mappableTypeFactory = mappableTypeFactory; + } + /** * Register a field mapping. * The source and target field expressions will be the same value. @@ -117,8 +124,10 @@ public class SpelMapper implements Mapper { } /** - * Adds a Mapper to apply to complex nested property mappings of a specific sourceType/targetType pair. - * The source and target types are determined by introspecting the parameterized types on the implementation's Mapper generic interface. + * Adds a Mapper that will map the fields of a nested sourceType/targetType pair. + * The source and target field types are determined by introspecting the parameterized types on the implementation's Mapper generic interface. + * The target instance that is mapped is constructed by a {@link DefaultMapperTargetFactory}. + * This method is a convenience method for {@link #addNestedMapper(Class, Class, Mapper)}. * @param nestedMapper the nested mapper */ public void addNestedMapper(Mapper nestedMapper) { @@ -127,9 +136,23 @@ public class SpelMapper implements Mapper { } /** - * Adds a Mapper to apply to complex nested property mappings of a specific sourceType/targetType pair. - * @param sourceType the source nested property type - * @param targetType the target nested property type + * Adds a Mapper that will map the fields of a nested sourceType/targetType pair. + * The source and target field types are determined by introspecting the parameterized types on the implementation's Mapper generic interface. + * The target instance that is mapped is constructed by the provided {@link MapperTargetFactory}. + * This method is a convenience method for {@link #addNestedMapper(Class, Class, Mapper, MapperTargetFactory)}. + * @param nestedMapper the nested mapper + * @param targetFactory the nested mapper's target factory + */ + public void addNestedMapper(Mapper nestedMapper, MapperTargetFactory targetFactory) { + Class[] typeInfo = getRequiredTypeInfo(nestedMapper); + addNestedMapper(typeInfo[0], typeInfo[1], nestedMapper, targetFactory); + } + + /** + * Adds a Mapper that will map the fields of a nested sourceType/targetType pair. + * The target instance that is mapped is constructed by a {@link DefaultMapperTargetFactory}. + * @param sourceType the source nested object property type + * @param targetType the target nested object property type * @param nestedMapper the nested mapper */ public void addNestedMapper(Class sourceType, Class targetType, Mapper nestedMapper) { @@ -137,8 +160,21 @@ public class SpelMapper implements Mapper { } /** - * Return this mapper's converter registry. - * Allows for registration of simple type converters as well as converters that map nested objects using a Mapper. + * Adds a Mapper that will map the fields of a nested sourceType/targetType pair. + * @param sourceType the source nested object property type + * @param targetType the target nested object property type + * @param nestedMapper the nested mapper + * @param targetFactory the nested mapper's target factory + */ + public void addNestedMapper(Class sourceType, Class targetType, Mapper nestedMapper, + MapperTargetFactory targetFactory) { + this.conversionService.addGenericConverter(sourceType, targetType, new MapperConverter(nestedMapper)); + } + + /** + * Return this mapper's internal converter registry. + * Allows for registration of simple type Converters in addition to MapperConverters that map entire nested object structures using a Mapper. + * To register the latter, consider using one of the {@link #addNestedMapper(Mapper) addNestedMapper} variants. * @see Converter * @see MapperConverter */ @@ -147,6 +183,8 @@ public class SpelMapper implements Mapper { } public Object map(Object source, Object target) { + Assert.notNull(source, "The source to map from cannot be null"); + Assert.notNull(target, "The target to map to cannot be null"); try { MappingContextHolder.push(source); EvaluationContext sourceContext = getEvaluationContext(source); @@ -234,29 +272,4 @@ public class SpelMapper implements Mapper { return false; } - private static class MappingConversionService extends DefaultConversionService { - - @Override - protected GenericConverter getDefaultConverter(TypeDescriptor sourceType, TypeDescriptor targetType) { - return new MapperConverter(new SpelMapper()); - } - - } - - private static class MappableTypeFactory { - - private static final MapMappableType MAP_MAPPABLE_TYPE = new MapMappableType(); - - private static final BeanMappableType BEAN_MAPPABLE_TYPE = new BeanMappableType(); - - @SuppressWarnings("unchecked") - public MappableType getMappableType(T object) { - if (object instanceof Map) { - return (MappableType) MAP_MAPPABLE_TYPE; - } else { - return (MappableType) BEAN_MAPPABLE_TYPE; - } - } - } - }