mappable type factory extension point / polish

This commit is contained in:
Keith Donald 2009-10-07 20:27:48 +00:00
parent e3983fe796
commit 8fd3de9397
12 changed files with 176 additions and 45 deletions

View File

@ -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<MappingFailure> mappingFailures;

View File

@ -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;

View File

@ -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<Object> {
final class BeanMappableType implements MappableType<Object> {
public boolean isInstance(Object object) {
return true;
}
public Set<String> getFields(Object object) {
Set<String> fields = new LinkedHashSet<String>();

View File

@ -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());
}
}

View File

@ -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);

View File

@ -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<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) {
LinkedHashSet<String> fields = new LinkedHashSet<String>(object.size(), 1);

View File

@ -37,4 +37,10 @@ interface MappableType<T> {
*/
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);
}

View File

@ -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");
}
}

View File

@ -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;

View File

@ -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<Stack<Object>> mappingContextHolder = new NamedThreadLocal<Stack<Object>>(
"Mapping context");

View File

@ -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());
}
}

View File

@ -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<Object, Object> {
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<Object, Object> {
private final Set<SpelMapping> mappings = new LinkedHashSet<SpelMapping>();
private MappableTypeFactory mappableTypeFactory = new DefaultMappableTypeFactory();
private boolean autoMappingEnabled = true;
private MappingConversionService conversionService = new MappingConversionService();
@ -77,6 +75,15 @@ public class SpelMapper implements Mapper<Object, Object> {
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<Object, Object> {
}
/**
* 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<Object, Object> {
}
/**
* 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<Object, Object> {
}
/**
* 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<Object, Object> {
}
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<Object, Object> {
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;
}
}
}
}