mappable type factory extension point / polish
This commit is contained in:
parent
e3983fe796
commit
8fd3de9397
|
|
@ -25,7 +25,7 @@ import java.util.List;
|
||||||
* @see Mapper#map(Object, Object)
|
* @see Mapper#map(Object, Object)
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
*/
|
*/
|
||||||
public class MappingException extends RuntimeException {
|
public final class MappingException extends RuntimeException {
|
||||||
|
|
||||||
private List<MappingFailure> mappingFailures;
|
private List<MappingFailure> mappingFailures;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ package org.springframework.mapping;
|
||||||
* Indicates an individual mapping failed.
|
* Indicates an individual mapping failed.
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
*/
|
*/
|
||||||
public class MappingFailure {
|
public final class MappingFailure {
|
||||||
|
|
||||||
private final Throwable cause;
|
private final Throwable cause;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,11 @@ import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
import org.springframework.expression.spel.support.StandardTypeConverter;
|
import org.springframework.expression.spel.support.StandardTypeConverter;
|
||||||
|
|
||||||
class BeanMappableType implements MappableType<Object> {
|
final class BeanMappableType implements MappableType<Object> {
|
||||||
|
|
||||||
|
public boolean isInstance(Object object) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public Set<String> getFields(Object object) {
|
public Set<String> getFields(Object object) {
|
||||||
Set<String> fields = new LinkedHashSet<String>();
|
Set<String> fields = new LinkedHashSet<String>();
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,7 +24,7 @@ import org.springframework.util.ClassUtils;
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
* @see BeanUtils#instantiate(Class)
|
* @see BeanUtils#instantiate(Class)
|
||||||
*/
|
*/
|
||||||
class DefaultMapperTargetFactory implements MapperTargetFactory {
|
final class DefaultMapperTargetFactory implements MapperTargetFactory {
|
||||||
|
|
||||||
public boolean supports(TypeDescriptor targetType) {
|
public boolean supports(TypeDescriptor targetType) {
|
||||||
return ClassUtils.hasConstructor(targetType.getType(), null);
|
return ClassUtils.hasConstructor(targetType.getType(), null);
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,11 @@ import org.springframework.expression.EvaluationContext;
|
||||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
import org.springframework.expression.spel.support.StandardTypeConverter;
|
import org.springframework.expression.spel.support.StandardTypeConverter;
|
||||||
|
|
||||||
class MapMappableType implements MappableType<Map<? extends Object, ? extends Object>> {
|
final class MapMappableType implements MappableType<Map<? extends Object, ? extends Object>> {
|
||||||
|
|
||||||
|
public boolean isInstance(Object object) {
|
||||||
|
return object instanceof Map<?, ?>;
|
||||||
|
}
|
||||||
|
|
||||||
public Set<String> getFields(Map<? extends Object, ? extends Object> object) {
|
public Set<String> getFields(Map<? extends Object, ? extends Object> object) {
|
||||||
LinkedHashSet<String> fields = new LinkedHashSet<String>(object.size(), 1);
|
LinkedHashSet<String> fields = new LinkedHashSet<String>(object.size(), 1);
|
||||||
|
|
|
||||||
|
|
@ -37,4 +37,10 @@ interface MappableType<T> {
|
||||||
*/
|
*/
|
||||||
EvaluationContext getEvaluationContext(T object, ConversionService conversionService);
|
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -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<MappableType<?>> mappableTypes = new LinkedHashSet<MappableType<?>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a MappableType to this factory.
|
||||||
|
* @param mappableType the mappable type
|
||||||
|
*/
|
||||||
|
public void add(MappableType<?> mappableType) {
|
||||||
|
this.mappableTypes.add(mappableType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> MappableType<T> 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,7 +27,7 @@ import org.springframework.mapping.Mapper;
|
||||||
* The default MapperTargetFactory instantiates a target by calling its default constructor.
|
* The default MapperTargetFactory instantiates a target by calling its default constructor.
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
*/
|
*/
|
||||||
public class MapperConverter implements GenericConverter {
|
public final class MapperConverter implements GenericConverter {
|
||||||
|
|
||||||
private Mapper mapper;
|
private Mapper mapper;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import org.springframework.core.NamedThreadLocal;
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
* @see SpelMapper#map(Object, Object)
|
* @see SpelMapper#map(Object, Object)
|
||||||
*/
|
*/
|
||||||
class MappingContextHolder {
|
abstract class MappingContextHolder {
|
||||||
|
|
||||||
private static final ThreadLocal<Stack<Object>> mappingContextHolder = new NamedThreadLocal<Stack<Object>>(
|
private static final ThreadLocal<Stack<Object>> mappingContextHolder = new NamedThreadLocal<Stack<Object>>(
|
||||||
"Mapping context");
|
"Mapping context");
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -19,17 +19,13 @@ import java.util.Collections;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.core.GenericTypeResolver;
|
import org.springframework.core.GenericTypeResolver;
|
||||||
import org.springframework.core.convert.TypeDescriptor;
|
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
import org.springframework.core.convert.converter.ConverterRegistry;
|
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.EvaluationContext;
|
||||||
import org.springframework.expression.EvaluationException;
|
import org.springframework.expression.EvaluationException;
|
||||||
import org.springframework.expression.Expression;
|
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.Mapper;
|
||||||
import org.springframework.mapping.MappingException;
|
import org.springframework.mapping.MappingException;
|
||||||
import org.springframework.mapping.MappingFailure;
|
import org.springframework.mapping.MappingFailure;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A general-purpose object mapper implementation based on the Spring Expression Language (SpEL).
|
* A general-purpose object mapper implementation based on the Spring Expression Language (SpEL).
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
* @see #setAutoMappingEnabled(boolean)
|
* @see #setAutoMappingEnabled(boolean)
|
||||||
|
* @see #setMappableTypeFactory(MappableTypeFactory)
|
||||||
* @see #addMapping(String)
|
* @see #addMapping(String)
|
||||||
* @see #addMapping(String, String)
|
* @see #addMapping(String, String)
|
||||||
* @see #getConverterRegistry()
|
* @see #getConverterRegistry()
|
||||||
|
|
@ -52,8 +50,6 @@ public class SpelMapper implements Mapper<Object, Object> {
|
||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(SpelMapper.class);
|
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 sourceExpressionParser = new SpelExpressionParser();
|
||||||
|
|
||||||
private static final SpelExpressionParser targetExpressionParser = new SpelExpressionParser(
|
private static final SpelExpressionParser targetExpressionParser = new SpelExpressionParser(
|
||||||
|
|
@ -62,6 +58,8 @@ public class SpelMapper implements Mapper<Object, Object> {
|
||||||
|
|
||||||
private final Set<SpelMapping> mappings = new LinkedHashSet<SpelMapping>();
|
private final Set<SpelMapping> mappings = new LinkedHashSet<SpelMapping>();
|
||||||
|
|
||||||
|
private MappableTypeFactory mappableTypeFactory = new DefaultMappableTypeFactory();
|
||||||
|
|
||||||
private boolean autoMappingEnabled = true;
|
private boolean autoMappingEnabled = true;
|
||||||
|
|
||||||
private MappingConversionService conversionService = new MappingConversionService();
|
private MappingConversionService conversionService = new MappingConversionService();
|
||||||
|
|
@ -77,6 +75,15 @@ public class SpelMapper implements Mapper<Object, Object> {
|
||||||
this.autoMappingEnabled = autoMappingEnabled;
|
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.
|
* Register a field mapping.
|
||||||
* The source and target field expressions will be the same value.
|
* The source and target field expressions will be the same value.
|
||||||
|
|
@ -117,8 +124,10 @@ public class SpelMapper implements Mapper<Object, Object> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a Mapper to apply to complex nested property mappings of a specific sourceType/targetType pair.
|
* Adds a Mapper that will map the fields of a nested sourceType/targetType pair.
|
||||||
* The source and target types are determined by introspecting the parameterized types on the implementation's Mapper generic interface.
|
* 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
|
* @param nestedMapper the nested mapper
|
||||||
*/
|
*/
|
||||||
public void addNestedMapper(Mapper<?, ?> nestedMapper) {
|
public void addNestedMapper(Mapper<?, ?> nestedMapper) {
|
||||||
|
|
@ -127,9 +136,23 @@ public class SpelMapper implements Mapper<Object, Object> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a Mapper to apply to complex nested property mappings of a specific sourceType/targetType pair.
|
* Adds a Mapper that will map the fields of a nested sourceType/targetType pair.
|
||||||
* @param sourceType the source nested property type
|
* The source and target field types are determined by introspecting the parameterized types on the implementation's Mapper generic interface.
|
||||||
* @param targetType the target nested property type
|
* 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
|
* @param nestedMapper the nested mapper
|
||||||
*/
|
*/
|
||||||
public void addNestedMapper(Class<?> sourceType, Class<?> targetType, Mapper<?, ?> nestedMapper) {
|
public void addNestedMapper(Class<?> sourceType, Class<?> targetType, Mapper<?, ?> nestedMapper) {
|
||||||
|
|
@ -137,8 +160,21 @@ public class SpelMapper implements Mapper<Object, Object> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return this mapper's converter registry.
|
* Adds a Mapper that will map the fields of a nested sourceType/targetType pair.
|
||||||
* Allows for registration of simple type converters as well as converters that map nested objects using a Mapper.
|
* @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 Converter
|
||||||
* @see MapperConverter
|
* @see MapperConverter
|
||||||
*/
|
*/
|
||||||
|
|
@ -147,6 +183,8 @@ public class SpelMapper implements Mapper<Object, Object> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object map(Object source, Object target) {
|
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 {
|
try {
|
||||||
MappingContextHolder.push(source);
|
MappingContextHolder.push(source);
|
||||||
EvaluationContext sourceContext = getEvaluationContext(source);
|
EvaluationContext sourceContext = getEvaluationContext(source);
|
||||||
|
|
@ -234,29 +272,4 @@ public class SpelMapper implements Mapper<Object, Object> {
|
||||||
return false;
|
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 <T> MappableType<T> getMappableType(T object) {
|
|
||||||
if (object instanceof Map<?, ?>) {
|
|
||||||
return (MappableType<T>) MAP_MAPPABLE_TYPE;
|
|
||||||
} else {
|
|
||||||
return (MappableType<T>) BEAN_MAPPABLE_TYPE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue