MapperFactory and MapperBuilder config API

This commit is contained in:
Keith Donald
2009-10-16 22:44:54 +00:00
parent 5616770a27
commit a221fb5718
21 changed files with 1040 additions and 658 deletions

View File

@@ -30,6 +30,6 @@ public interface Mapper<S, T> {
* @return the mapped target object
* @throws MappingException if the mapping process failed
*/
Object map(S source, T target);
T map(S source, T target);
}

View File

@@ -0,0 +1,41 @@
/*
* 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.converter.Converter;
/**
* Creates a mapping target by calling a converter.
* @author Keith Donald
*/
final class ConverterMappingTargetFactory implements MappingTargetFactory {
private Converter converter;
public ConverterMappingTargetFactory(Converter converter) {
this.converter = converter;
}
public boolean supports(TypeDescriptor targetType) {
return true;
}
public Object createTarget(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.converter.convert(source);
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.converter.Converter;
import org.springframework.expression.Expression;
/**
* A mapping between a source field and a target field.
* @author Keith Donald
*/
final class FieldToFieldMapping implements SpelMapping {
private final Expression sourceField;
private final Expression targetField;
@SuppressWarnings("unchecked")
private final Converter converter;
public FieldToFieldMapping(Expression sourceField, Expression targetField, Converter<?, ?> converter) {
this.sourceField = sourceField;
this.targetField = targetField;
this.converter = converter;
}
public String getSourceField() {
return this.sourceField.getExpressionString();
}
public String getTargetField() {
return this.targetField.getExpressionString();
}
@SuppressWarnings("unchecked")
public void map(SpelMappingContext context) {
try {
Object value = context.getSourceFieldValue(this.sourceField);
if (this.converter != null) {
value = this.converter.convert(value);
}
context.setTargetFieldValue(this.targetField, value);
} catch (Exception e) {
context.addMappingFailure(e);
}
}
public int hashCode() {
return getSourceField().hashCode() + getTargetField().hashCode();
}
public boolean equals(Object o) {
if (!(o instanceof FieldToFieldMapping)) {
return false;
}
FieldToFieldMapping m = (FieldToFieldMapping) o;
return getSourceField().equals(m.getSourceField()) && getTargetField().equals(m.getTargetField());
}
public String toString() {
return "[FieldToFieldMapping<" + getSourceField() + " -> " + getTargetField() + ">]";
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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.expression.Expression;
import org.springframework.mapping.Mapper;
/**
* A mapping between a source field and several target fields.
* @author Keith Donald
*/
final class FieldToMultiFieldMapping implements SpelMapping {
private final Expression sourceField;
@SuppressWarnings("unchecked")
private final Mapper targetFieldMapper;
public FieldToMultiFieldMapping(Expression sourceField, Mapper<?, ?> targetFieldMapper) {
this.sourceField = sourceField;
this.targetFieldMapper = targetFieldMapper;
}
public String getSourceField() {
return this.sourceField.getExpressionString();
}
@SuppressWarnings("unchecked")
public void map(SpelMappingContext context) {
try {
Object value = context.getSourceFieldValue(this.sourceField);
this.targetFieldMapper.map(value, context.getTarget());
} catch (Exception e) {
context.addMappingFailure(e);
}
}
public int hashCode() {
return getSourceField().hashCode() + this.targetFieldMapper.hashCode();
}
public boolean equals(Object o) {
if (!(o instanceof FieldToMultiFieldMapping)) {
return false;
}
FieldToMultiFieldMapping m = (FieldToMultiFieldMapping) o;
return getSourceField().equals(m.getSourceField()) && this.targetFieldMapper.equals(m.targetFieldMapper);
}
public String toString() {
return "[FieldToFieldMapping<" + getSourceField() + " -> " + this.targetFieldMapper + ">]";
}
}

View File

@@ -23,7 +23,7 @@ import java.util.Set;
* Call {@link #add(MappableType)} to register.
* @author Keith Donald
*/
public class MappableTypeFactory {
class MappableTypeFactory {
private Set<MappableType<?>> mappableTypes = new LinkedHashSet<MappableType<?>>();

View File

@@ -0,0 +1,144 @@
/*
* 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.converter.Converter;
import org.springframework.mapping.Mapper;
/**
* A fluent interface for configuring a {@link Mapper} between a source type and a target type.
* To use, call one or more of the builder methods on this class, then {@link #getMapper()} to obtain the Mapper instance.
* @author Keith Donald
* @param <S> the source type to map from
* @param <T> the target type to map to
* @see #setAutoMappingEnabled(boolean)
* @see #addMapping(String)
* @see #addMapping(String, Converter)
* @see #addMapping(String, Mapper)
* @see #addMapping(String, String)
* @see #addMapping(String, String, Converter)
* @see #addMapping(Mapper)
* @see #addConverter(Converter)
* @see #getMapper()
*/
public interface MapperBuilder<S, T> {
/**
* Sets whether "auto mapping" is enabled.
* When enabled, source and target fields with the same name will automatically be mapped unless an explicit mapping override has been registered.
* Set to false to require explicit registration of all source-to-target mapping rules.
* Default is enabled (true).
* @param autoMappingEnabled auto mapping status
*/
MapperBuilder<S, T> setAutoMappingEnabled(boolean autoMappingEnabled);
/**
* Register a mapping between a source field and a target field.
* The source and target field names will be the same value.
* For example, calling <code>addMapping("order")</code> will register a mapping that maps between the <code>order</code> field on the source and the <code>order</code> field on the target.
* This is a convenience method for calling {@link #addMapping(String, String)} with the same source and target value..
* @param fieldExpression the field mapping expression
* @return this, for configuring additional field mapping options fluently
*/
MapperBuilder<S, T> addMapping(String field);
/**
* Register a mapping between a source field and a target field that first converts the source field value using the provided Converter.
* The source and target field expressions will be the same value.
* For example, calling <code>addMapping("order")</code> will register a mapping that maps between the <code>order</code> field on the source and the <code>order</code> field on the target.
* This is a convenience method for calling {@link #addMapping(String, String, Converter)} with the same source and target value..
* @param fieldExpression the field mapping expression
* @param converter the converter that will convert the source field value before mapping the value to the target field
* @return this, for configuring additional field mapping options fluently
*/
MapperBuilder<S, T> addMapping(String field, Converter<?, ?> converter);
/**
* Register a mapping between a source field and multiple target fields.
* Use this method when you need to map a single source field value to multiple fields on the target.
* For example, calling <code>addMapping("name", firstAndLastNameMapper)</code> might register a mapping that maps the <code>name</code> field on the source to the <code>firstName</code> and <code>lastName</code> fields on the target.
* The target field {@link Mapper} will be passed the value of the source field for its source and the target object T for its target.
* @param field the source field expression
* @param mapper the mapper of the target fields
* @return this, for configuring additional field mapping options fluently
*/
MapperBuilder<S, T> addMapping(String field, Mapper<?, T> mapper);
/**
* Register a mapping between a source field and a target field.
* Use this method when the name of the source field and the name of the target field are different.
* For example, calling <code>addMapping("order", "primaryOrder")</code> will register a mapping that maps between the <code>order</code> field on the source and the <code>primaryOrder</code> field on the target.
* @param sourceFieldExpression the source field mapping expression
* @param targetFieldExpression the target field mapping expression
* @return this, for configuring additional field mapping options fluently
*/
MapperBuilder<S, T> addMapping(String sourceField, String targetField);
/**
* Register a mapping between a source field and a target field that first converts the source field value using the provided Converter.
* Use this method when the name of the source field and the name of the target field are different.
* For example, calling <code>addMapping("order", "primaryOrder")</code> will register a mapping that maps between the <code>order</code> field on the source and the <code>primaryOrder</code> field on the target.
* @param sourceFieldExpression the source field mapping expression
* @param targetFieldExpression the target field mapping expression
* @return this, for configuring additional field mapping options fluently
*/
MapperBuilder<S, T> addMapping(String sourceField, String targetField, Converter<?, ?> converter);
/**
* Register a mapping between multiple source fields and a single target field.
* For example, calling <code>addMapping(dateAndTimeFieldsToDateTimeFieldMapper)</code> might register a mapping that maps the <code>date</code> and <code>time</code> fields on the source to the <code>dateTime</code> field on the target.
* The provided {@link Mapper} will be passed the source object S for its source and the target object T for its target.
* @param mapper the fields to field mapper
* @return this, for configuring additional field mapping options fluently
*/
MapperBuilder<S, T> addMapping(Mapper<S, T> mapper);
/**
* Register a Mapper that will be used to map between nested source and target fields of a specific sourceType/targetType pair.
* The source and target field types are determined by introspecting the parameterized types on the Mapper generic interface.
* The target instance that is mapped is constructed by calling its default constructor.
* @param nestedMapper the nested mapper
* @return this, for configuring additional field mapping options fluently
*/
MapperBuilder<S, T> addNestedMapper(Mapper<?, ?> nestedMapper);
/**
* Register a Mapper that will be used to map between nested source and target fields of a specific sourceType/targetType pair.
* The source and target field types are determined by introspecting the parameterized types on the Mapper generic interface.
* The target instance that is mapped is constructed by calling the provided Converter.
* @param nestedMapper the nested mapper
* @param converter the target converter
* @return this, for configuring additional field mapping options fluently
*/
MapperBuilder<S, T> addNestedMapper(Mapper<?, ?> nestedMapper, Converter<?, ?> converter);
/**
* Register a custom type converter to use to convert between two mapped types.
* The Converter may convert between simple types, such as Strings to Dates.
* Alternatively, it may convert between complex types and initiate a recursive mapping operation between two object fields.
* @see Converter
* @see MappingConverter
*/
MapperBuilder<S, T> addConverter(Converter<?, ?> converter);
/**
* Get the Mapper produced by this builder.
* Call this method after instructing the builder.
* @return the Mapper between S and T ready for use
*/
Mapper<S, T> getMapper();
}

View File

@@ -0,0 +1,58 @@
/*
* 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.mapping.Mapper;
/**
* Factory for creating general-purpose Mappers without depending on a concrete Mapper implementation class.
* @see #defaultMapper()
* @see #mapperBuilder()
* @see #mapperBuilder(Class, Class)
* @author Keith Donald
*/
public class MapperFactory {
private static final SpelMapper DEFAULT_MAPPER = new SpelMapper();
/**
* Get the default Mapper instance suitable for mapping between most object types using "auto mapping" based on field names.
* The Mapper returned is shared and immutable and should not be downcast & modified.
* @return the default mapper
*/
public static Mapper<Object, Object> defaultMapper() {
return DEFAULT_MAPPER;
}
/**
* Get a builder for a new Mapper instance, allowing customization of object mapping policy.
* @return the MapperBuilder
*/
public static MapperBuilder<Object, Object> mapperBuilder() {
return new SpelMapperBuilder<Object, Object>();
}
/**
* Get a builder for a new Mapper instance that maps between objects of sourceType and targetType.
* Allows for customization of object mapping policy.
* Use this method as an alterntative to {@link #mapperBuilder()} when you'd like more type-safety and validation when configuring and using the Mapper.
* @return the MapperBuilder
*/
public static <S, T> MapperBuilder<S, T> mapperBuilder(Class<S> sourceType, Class<T> targetType) {
return new SpelMapperBuilder<S, T>(sourceType, targetType);
}
}

View File

@@ -1,103 +0,0 @@
/*
* 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.Collection;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.core.convert.support.ConverterFactoryGenericConverter;
import org.springframework.core.convert.support.ConverterGenericConverter;
import org.springframework.core.convert.support.GenericConverter;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.mapping.MappingFailure;
/**
* An individual mapping definition between two fields.
* @author Keith Donald
* @since 3.0
*/
class Mapping implements MappingConfiguration {
private Expression source;
private Expression target;
private GenericConverter converter;
public Mapping(Expression source, Expression target) {
this.source = source;
this.target = target;
}
public String getSourceExpressionString() {
return this.source.getExpressionString();
}
public String getTargetExpressionString() {
return this.target.getExpressionString();
}
public MappingConfiguration setConverter(Converter<?, ?> converter) {
return setGenericConverter(new ConverterGenericConverter(converter));
}
public MappingConfiguration setConverterFactory(ConverterFactory<?, ?> converter) {
return setGenericConverter(new ConverterFactoryGenericConverter(converter));
}
public MappingConfiguration setGenericConverter(GenericConverter converter) {
this.converter = converter;
return this;
}
public void map(EvaluationContext sourceContext, EvaluationContext targetContext,
Collection<MappingFailure> failures) {
try {
Object value = this.source.getValue(sourceContext);
if (this.converter != null) {
value = this.converter.convert(value, this.source.getValueTypeDescriptor(sourceContext), this.target
.getValueTypeDescriptor(targetContext));
}
this.target.setValue(targetContext, value);
} catch (Exception e) {
failures.add(new MappingFailure(e));
}
}
public int hashCode() {
return getSourceExpressionString().hashCode() + getTargetExpressionString().hashCode();
}
public boolean equals(Object o) {
if (!(o instanceof Mapping)) {
return false;
}
Mapping m = (Mapping) o;
return getSourceExpressionString().equals(m.getSourceExpressionString())
&& getTargetExpressionString().equals(m.getTargetExpressionString());
}
public String toString() {
return "[Mapping<" + getSourceExpressionString() + " -> " + getTargetExpressionString() + ">]";
}
public void setExclude() {
// TODO Auto-generated method stub
}
}

View File

@@ -1,58 +0,0 @@
/*
* 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.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.core.convert.support.GenericConverter;
/**
* A fluent API for configuring a mapping.
* @see SpelMapper#addMapping(String)
* @see SpelMapper#addMapping(String, String)
* @author Keith Donald
*/
public interface MappingConfiguration {
/**
* Set the type converter to use during this mapping.
* @param converter the converter
* @return this, for call chaining
*/
MappingConfiguration setConverter(Converter<?, ?> converter);
/**
* Set the type converter factory to use during this mapping.
* @param converter the converter factory
* @return this, for call chaining
*/
MappingConfiguration setConverterFactory(ConverterFactory<?, ?> converterFactory);
/**
* Set the generic converter to use during this mapping.
* A generic converter allows access to source and target field type descriptors.
* These descriptors provide additional context that can be used during type conversion.
* @param converter the generic converter
* @return this, for call chaining
*/
MappingConfiguration setGenericConverter(GenericConverter converter);
/**
* Configures that this mapping should be excluded (ignored and not executed).
*/
void setExclude();
}

View File

@@ -24,7 +24,7 @@ import org.springframework.core.NamedThreadLocal;
* @author Keith Donald
* @see SpelMapper#map(Object, Object)
*/
public abstract class MappingContextHolder {
abstract class MappingContextHolder {
private static final ThreadLocal<Stack<Object>> mappingContextHolder = new NamedThreadLocal<Stack<Object>>(
"Mapping context");

View File

@@ -27,11 +27,11 @@ import org.springframework.mapping.Mapper;
* The default MapperTargetFactory instantiates a target by calling its default constructor.
* @author Keith Donald
*/
public final class MappingConverter implements GenericConverter {
final class MappingConverter implements GenericConverter {
private Mapper mapper;
private final Mapper mapper;
private MappingTargetFactory mappingTargetFactory;
private final MappingTargetFactory mappingTargetFactory;
/**
* Creates a new Converter that delegates to the mapper to complete the type conversion process.

View File

@@ -25,7 +25,7 @@ import org.springframework.mapping.Mapper;
* @see MappingConverter
* @see Mapper#map(Object, Object)
*/
public interface MappingTargetFactory {
interface MappingTargetFactory {
/**
* Does this factory support creating mapping targets of the specified type

View File

@@ -0,0 +1,58 @@
/*
* 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.mapping.Mapper;
/**
* A mapping between several source fields and a target field.
* @author Keith Donald
*/
final class MultiFieldToFieldMapping implements SpelMapping {
@SuppressWarnings("unchecked")
private final Mapper multiFieldMapper;
public MultiFieldToFieldMapping(Mapper<?, ?> multiFieldMapper) {
this.multiFieldMapper = multiFieldMapper;
}
@SuppressWarnings("unchecked")
public void map(SpelMappingContext context) {
try {
this.multiFieldMapper.map(context.getSource(), context.getTarget());
} catch (Exception e) {
context.addMappingFailure(e);
}
}
public int hashCode() {
return this.multiFieldMapper.hashCode();
}
public boolean equals(Object o) {
if (!(o instanceof MultiFieldToFieldMapping)) {
return false;
}
MultiFieldToFieldMapping m = (MultiFieldToFieldMapping) o;
return this.multiFieldMapper.equals(m.multiFieldMapper);
}
public String toString() {
return "[MultiFieldToFieldMapping<" + this.multiFieldMapper + ">]";
}
}

View File

@@ -17,8 +17,6 @@ package org.springframework.mapping.support;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
@@ -33,28 +31,19 @@ import org.springframework.expression.ParseException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParserConfiguration;
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 #addNestedMapper(Mapper)
* @see #addNestedMapper(Mapper, MappingTargetFactory)
* @see #getConverterRegistry()
*/
public class SpelMapper implements Mapper<Object, Object> {
final class SpelMapper implements Mapper<Object, Object> {
private static final Log logger = LogFactory.getLog(SpelMapper.class);
private static final SpelExpressionParser sourceExpressionParser = new SpelExpressionParser();
private final SpelExpressionParser sourceExpressionParser = new SpelExpressionParser();
private static final SpelExpressionParser targetExpressionParser = new SpelExpressionParser(
private final SpelExpressionParser targetExpressionParser = new SpelExpressionParser(
SpelExpressionParserConfiguration.CreateObjectIfAttemptToReferenceNull
| SpelExpressionParserConfiguration.GrowListsOnIndexBeyondSize);
@@ -66,63 +55,35 @@ public class SpelMapper implements Mapper<Object, Object> {
private MappingConversionService conversionService = new MappingConversionService();
/**
* Sets whether "auto mapping" is enabled.
* When enabled, source and target fields with the same name will automatically be mapped unless an explicit mapping override has been registered.
* Set to false to require explicit registration of all source-to-target mapping rules.
* Default is enabled (true).
* @param autoMappingEnabled auto mapping status
*/
public SpelMapper() {
}
public SpelMapper(Class sourceType, Class targetType) {
// TODO - addMapping assertions based on specified sourceType and targetType
}
public void setAutoMappingEnabled(boolean 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.
* The source and target field expressions will be the same value.
* For example, calling <code>addMapping("order")</code> will register a mapping that maps between the <code>order</code> field on the source and the <code>order</code> field on the target.
* This is a convenience method for calling {@link #addMapping(String, String)} with the same source and target value..
* @param fieldExpression the field mapping expression
* @return this, for configuring additional field mapping options fluently
*/
public MappingConfiguration addMapping(String fieldExpression) {
return addMapping(fieldExpression, fieldExpression);
public void addMapping(String sourceFieldExpression, String targetFieldExpression, Converter<?, ?> converter) {
Expression sourceField = parseSourceField(sourceFieldExpression);
Expression targetField = parseTargetField(targetFieldExpression);
FieldToFieldMapping mapping = new FieldToFieldMapping(sourceField, targetField, converter);
this.mappings.add(mapping);
}
/**
* Register a mapping between a source and target field.
* For example, calling <code>addMapping("order", "primaryOrder")</code> will register a mapping that maps between the <code>order</code> field on the source and the <code>primaryOrder</code> field on the target.
* @param sourceFieldExpression the source field mapping expression
* @param targetFieldExpression the target field mapping expression
* @return this, for configuring additional field mapping options fluently
*/
public MappingConfiguration addMapping(String sourceFieldExpression, String targetFieldExpression) {
Expression sourceExp;
try {
sourceExp = sourceExpressionParser.parseExpression(sourceFieldExpression);
} catch (ParseException e) {
throw new IllegalArgumentException("The mapping source '" + sourceFieldExpression
+ "' is not a parseable value expression", e);
}
Expression targetExp;
try {
targetExp = targetExpressionParser.parseExpression(targetFieldExpression);
} catch (ParseException e) {
throw new IllegalArgumentException("The mapping target '" + targetFieldExpression
+ "' is not a parseable property expression", e);
}
SpelMapping mapping = new SpelMapping(sourceExp, targetExp);
this.mappings.add(mapping);
return mapping;
public void addMapping(String field, Mapper mapper) {
this.mappings.add(new FieldToMultiFieldMapping(parseSourceField(field), mapper));
}
public void addMapping(Mapper mapper) {
this.mappings.add(new MultiFieldToFieldMapping(mapper));
}
/**
@@ -174,13 +135,6 @@ public class SpelMapper implements Mapper<Object, Object> {
targetFactory));
}
/**
* 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 MappingConverter
*/
public ConverterRegistry getConverterRegistry() {
return conversionService;
}
@@ -192,17 +146,21 @@ public class SpelMapper implements Mapper<Object, Object> {
MappingContextHolder.push(source);
EvaluationContext sourceContext = getEvaluationContext(source);
EvaluationContext targetContext = getEvaluationContext(target);
List<MappingFailure> failures = new LinkedList<MappingFailure>();
SpelMappingContext context = new SpelMappingContext(sourceContext, targetContext);
for (SpelMapping mapping : this.mappings) {
doMap(mapping, sourceContext, targetContext, failures);
if (logger.isDebugEnabled()) {
logger.debug(MappingContextHolder.getLevel() + mapping);
}
mapping.map(context);
}
Set<SpelMapping> autoMappings = getAutoMappings(sourceContext, targetContext);
Set<FieldToFieldMapping> autoMappings = getAutoMappings(sourceContext, targetContext);
for (SpelMapping mapping : autoMappings) {
doMap(mapping, sourceContext, targetContext, failures);
}
if (!failures.isEmpty()) {
throw new MappingException(failures);
if (logger.isDebugEnabled()) {
logger.debug(MappingContextHolder.getLevel() + mapping + " (auto)");
}
mapping.map(context);
}
context.handleFailures();
return target;
} finally {
MappingContextHolder.pop();
@@ -211,6 +169,28 @@ public class SpelMapper implements Mapper<Object, Object> {
// internal helpers
private Expression parseSourceField(String sourceFieldExpression) {
Expression sourceExp;
try {
sourceExp = sourceExpressionParser.parseExpression(sourceFieldExpression);
} catch (ParseException e) {
throw new IllegalArgumentException("The mapping source '" + sourceFieldExpression
+ "' is not a parseable value expression", e);
}
return sourceExp;
}
private Expression parseTargetField(String targetFieldExpression) {
Expression targetExp;
try {
targetExp = targetExpressionParser.parseExpression(targetFieldExpression);
} catch (ParseException e) {
throw new IllegalArgumentException("The mapping target '" + targetFieldExpression
+ "' is not a parseable property expression", e);
}
return targetExp;
}
private Class<?>[] getRequiredTypeInfo(Mapper<?, ?> mapper) {
return GenericTypeResolver.resolveTypeArguments(mapper.getClass(), Mapper.class);
}
@@ -219,17 +199,9 @@ public class SpelMapper implements Mapper<Object, Object> {
return mappableTypeFactory.getMappableType(object).getEvaluationContext(object, this.conversionService);
}
private void doMap(SpelMapping mapping, EvaluationContext sourceContext, EvaluationContext targetContext,
List<MappingFailure> failures) {
if (logger.isDebugEnabled()) {
logger.debug(MappingContextHolder.getLevel() + mapping);
}
mapping.map(sourceContext, targetContext, failures);
}
private Set<SpelMapping> getAutoMappings(EvaluationContext sourceContext, EvaluationContext targetContext) {
private Set<FieldToFieldMapping> getAutoMappings(EvaluationContext sourceContext, EvaluationContext targetContext) {
if (this.autoMappingEnabled) {
Set<SpelMapping> autoMappings = new LinkedHashSet<SpelMapping>();
Set<FieldToFieldMapping> autoMappings = new LinkedHashSet<FieldToFieldMapping>();
Set<String> sourceFields = getMappableFields(sourceContext.getRootObject().getValue());
for (String field : sourceFields) {
if (!explicitlyMapped(field)) {
@@ -249,7 +221,7 @@ public class SpelMapper implements Mapper<Object, Object> {
}
try {
if (targetExpression.isWritable(targetContext)) {
autoMappings.add(new SpelMapping(sourceExpression, targetExpression));
autoMappings.add(new FieldToFieldMapping(sourceExpression, targetExpression, null));
}
} catch (EvaluationException e) {
@@ -268,11 +240,11 @@ public class SpelMapper implements Mapper<Object, Object> {
private boolean explicitlyMapped(String field) {
for (SpelMapping mapping : this.mappings) {
if (mapping.getSourceExpressionString().startsWith(field)) {
if (mapping instanceof FieldToFieldMapping
&& ((FieldToFieldMapping) mapping).getSourceField().startsWith(field)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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.converter.Converter;
import org.springframework.mapping.Mapper;
/**
* MapperBuilder that builds {@link SpelMapper} instances.
* @author Keith Donald
*/
final class SpelMapperBuilder<S, T> implements MapperBuilder<S, T> {
private final SpelMapper mapper;
public SpelMapperBuilder() {
this.mapper = new SpelMapper();
}
public SpelMapperBuilder(Class<S> sourceType, Class<T> targetType) {
this.mapper = new SpelMapper(sourceType, targetType);
}
public MapperBuilder<S, T> setAutoMappingEnabled(boolean autoMappingEnabled) {
this.mapper.setAutoMappingEnabled(autoMappingEnabled);
return this;
}
public MapperBuilder<S, T> addMapping(String field) {
this.mapper.addMapping(field, field, null);
return this;
}
public MapperBuilder<S, T> addMapping(String field, Converter<?, ?> converter) {
this.mapper.addMapping(field, field, converter);
return this;
}
public MapperBuilder<S, T> addMapping(String field, Mapper<?, T> mapper) {
this.mapper.addMapping(field, mapper);
return this;
}
public MapperBuilder<S, T> addMapping(String sourceField, String targetField) {
this.mapper.addMapping(sourceField, targetField, null);
return this;
}
public MapperBuilder<S, T> addMapping(String sourceField, String targetField, Converter<?, ?> converter) {
this.mapper.addMapping(sourceField, targetField, converter);
return this;
}
public MapperBuilder<S, T> addMapping(Mapper<S, T> mapper) {
this.mapper.addMapping(mapper);
return this;
}
public MapperBuilder<S, T> addNestedMapper(Mapper<?, ?> nestedMapper) {
this.mapper.addNestedMapper(nestedMapper);
return this;
}
public MapperBuilder<S, T> addNestedMapper(Mapper<?, ?> nestedMapper, Converter<?, ?> converter) {
this.mapper.addNestedMapper(nestedMapper, new ConverterMappingTargetFactory(converter));
return this;
}
public MapperBuilder<S, T> addConverter(Converter<?, ?> converter) {
this.mapper.getConverterRegistry().addConverter(converter);
return this;
}
@SuppressWarnings("unchecked")
public Mapper<S, T> getMapper() {
return (Mapper<S, T>) this.mapper;
}
}

View File

@@ -15,97 +15,15 @@
*/
package org.springframework.mapping.support;
import java.util.Collection;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.core.convert.support.ConverterFactoryGenericConverter;
import org.springframework.core.convert.support.ConverterGenericConverter;
import org.springframework.core.convert.support.GenericConverter;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.mapping.MappingFailure;
/**
* An individual mapping definition between two fields.
* A single {@link SpelMapper} mapping.
* @author Keith Donald
*/
class SpelMapping implements MappingConfiguration {
private Expression source;
private Expression target;
private GenericConverter converter;
private boolean exclude;
public SpelMapping(Expression source, Expression target) {
this.source = source;
this.target = target;
}
// implementing MappingConfiguration
public MappingConfiguration setConverter(Converter<?, ?> converter) {
return setGenericConverter(new ConverterGenericConverter(converter));
}
public MappingConfiguration setConverterFactory(ConverterFactory<?, ?> converter) {
return setGenericConverter(new ConverterFactoryGenericConverter(converter));
}
public MappingConfiguration setGenericConverter(GenericConverter converter) {
this.converter = converter;
return this;
}
public void setExclude() {
this.exclude = true;
}
// public methods
public String getSourceExpressionString() {
return this.source.getExpressionString();
}
public String getTargetExpressionString() {
return this.target.getExpressionString();
}
public void map(EvaluationContext sourceContext, EvaluationContext targetContext,
Collection<MappingFailure> failures) {
if (exclude) {
return;
}
try {
Object value = this.source.getValue(sourceContext);
if (this.converter != null) {
value = this.converter.convert(value, this.source.getValueTypeDescriptor(sourceContext), this.target
.getValueTypeDescriptor(targetContext));
}
this.target.setValue(targetContext, value);
} catch (Exception e) {
failures.add(new MappingFailure(e));
}
}
public int hashCode() {
return getSourceExpressionString().hashCode() + getTargetExpressionString().hashCode();
}
public boolean equals(Object o) {
if (!(o instanceof SpelMapping)) {
return false;
}
SpelMapping m = (SpelMapping) o;
return getSourceExpressionString().equals(m.getSourceExpressionString())
&& getTargetExpressionString().equals(m.getTargetExpressionString());
}
public String toString() {
return "[Mapping<" + getSourceExpressionString() + " -> " + getTargetExpressionString() + ">]";
}
interface SpelMapping {
/**
* Execute this mapping.
* @param context the mapping context
*/
void map(SpelMappingContext context);
}

View File

@@ -0,0 +1,64 @@
/*
* 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.LinkedList;
import java.util.List;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.mapping.MappingException;
import org.springframework.mapping.MappingFailure;
final class SpelMappingContext {
private final EvaluationContext sourceEvaluationContext;
private final EvaluationContext targetEvaluationContext;
private final List<MappingFailure> failures = new LinkedList<MappingFailure>();
public SpelMappingContext(EvaluationContext sourceEvaluationContext, EvaluationContext targetEvaluationContext) {
this.sourceEvaluationContext = sourceEvaluationContext;
this.targetEvaluationContext = targetEvaluationContext;
}
public Object getSource() {
return this.sourceEvaluationContext.getRootObject().getValue();
}
public Object getTarget() {
return this.targetEvaluationContext.getRootObject().getValue();
}
public Object getSourceFieldValue(Expression sourceField) {
return sourceField.getValue(this.sourceEvaluationContext);
}
public void setTargetFieldValue(Expression targetField, Object value) {
targetField.setValue(this.targetEvaluationContext, value);
}
public void addMappingFailure(Throwable cause) {
this.failures.add(new MappingFailure(cause));
}
public void handleFailures() {
if (!this.failures.isEmpty()) {
throw new MappingException(this.failures);
}
}
}

View File

@@ -29,11 +29,11 @@ import org.springframework.ui.format.Formatter;
public class DateTimeFormatter implements Formatter<DateTime> {
private org.joda.time.format.DateTimeFormatter formatter;
/**
* Creates a new {@link DateTimeFormatter} for the given JodaTime formatting pattern.
* @param pattern
* @see DateTimeFormat
* @see DateTimeFormat#forPattern(String)
*/
public DateTimeFormatter(String pattern) {
this.formatter = DateTimeFormat.forPattern(pattern);
@@ -41,6 +41,7 @@ public class DateTimeFormatter implements Formatter<DateTime> {
/**
* Creates a new {@link DateTimeFormatter} for the given JodaTime formatter.
* @param formatter the Date Formatter instance
*/
public DateTimeFormatter(org.joda.time.format.DateTimeFormatter formatter) {
this.formatter = formatter;