diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/FieldToFieldMapping.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/FieldToFieldMapping.java index 7781acf1f39..2ee6bb49dd1 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/FieldToFieldMapping.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/FieldToFieldMapping.java @@ -45,6 +45,10 @@ final class FieldToFieldMapping implements SpelMapping { return this.targetField.getExpressionString(); } + public boolean mapsField(String field) { + return getSourceField().equals(field); + } + @SuppressWarnings("unchecked") public void map(SpelMappingContext context) { try { diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/FieldToMultiFieldMapping.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/FieldToMultiFieldMapping.java index a9c3d6dbb5b..69282a9be87 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/FieldToMultiFieldMapping.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/FieldToMultiFieldMapping.java @@ -38,6 +38,10 @@ final class FieldToMultiFieldMapping implements SpelMapping { return this.sourceField.getExpressionString(); } + public boolean mapsField(String field) { + return getSourceField().equals(field); + } + @SuppressWarnings("unchecked") public void map(SpelMappingContext context) { try { diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/MapperBuilder.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/MapperBuilder.java index 3af54185fe3..16503ef81c4 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/MapperBuilder.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/MapperBuilder.java @@ -101,10 +101,11 @@ public interface MapperBuilder { * Register a mapping between multiple source fields and a single target field. * For example, calling addMapping(dateAndTimeFieldsToDateTimeFieldMapper) might register a mapping that maps the date and time fields on the source to the dateTime 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 fields the source field mapping expressions * @param mapper the fields to field mapper * @return this, for configuring additional field mapping options fluently */ - MapperBuilder addMapping(Mapper mapper); + MapperBuilder addMapping(String[] fields, Mapper mapper); /** * Register a Mapper that will be used to map between nested source and target fields of a specific sourceType/targetType pair. diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/MultiFieldToFieldMapping.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/MultiFieldToFieldMapping.java index 7abe0c1dcdd..136430f126a 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/MultiFieldToFieldMapping.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/MultiFieldToFieldMapping.java @@ -15,6 +15,7 @@ */ package org.springframework.mapping.support; +import org.springframework.core.style.StylerUtils; import org.springframework.mapping.Mapper; /** @@ -23,13 +24,29 @@ import org.springframework.mapping.Mapper; */ final class MultiFieldToFieldMapping implements SpelMapping { + private String[] fields; + @SuppressWarnings("unchecked") private final Mapper multiFieldMapper; - public MultiFieldToFieldMapping(Mapper multiFieldMapper) { + public MultiFieldToFieldMapping(String[] fields, Mapper multiFieldMapper) { + this.fields = fields; this.multiFieldMapper = multiFieldMapper; } + public String[] getSourceFields() { + return fields; + } + + public boolean mapsField(String field) { + for (String f : this.fields) { + if (f.equals(field)) { + return true; + } + } + return false; + } + @SuppressWarnings("unchecked") public void map(SpelMappingContext context) { try { @@ -52,7 +69,7 @@ final class MultiFieldToFieldMapping implements SpelMapping { } public String toString() { - return "[MultiFieldToFieldMapping<" + this.multiFieldMapper + ">]"; + return "[MultiFieldToFieldMapping<" + StylerUtils.style(this.fields) + " -> " + this.multiFieldMapper + ">]"; } } \ 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 423ece1281d..8eddad39ca2 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 @@ -78,12 +78,12 @@ final class SpelMapper implements Mapper { this.mappings.add(mapping); } - public void addMapping(String field, Mapper mapper) { + 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)); + public void addMapping(String[] fields, Mapper mapper) { + this.mappings.add(new MultiFieldToFieldMapping(fields, mapper)); } /** @@ -240,8 +240,7 @@ final class SpelMapper implements Mapper { private boolean explicitlyMapped(String field) { for (SpelMapping mapping : this.mappings) { - if (mapping instanceof FieldToFieldMapping - && ((FieldToFieldMapping) mapping).getSourceField().startsWith(field)) { + if (mapping.mapsField(field)) { return true; } } diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapperBuilder.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapperBuilder.java index 8f5b356842e..22d3fbaeb8d 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapperBuilder.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapperBuilder.java @@ -64,8 +64,8 @@ final class SpelMapperBuilder implements MapperBuilder { return this; } - public MapperBuilder addMapping(Mapper mapper) { - this.mapper.addMapping(mapper); + public MapperBuilder addMapping(String[] fields, Mapper mapper) { + this.mapper.addMapping(fields, mapper); return this; } diff --git a/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapping.java b/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapping.java index 3cb57738557..85317112d97 100644 --- a/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapping.java +++ b/org.springframework.context/src/main/java/org/springframework/mapping/support/SpelMapping.java @@ -21,6 +21,11 @@ package org.springframework.mapping.support; */ interface SpelMapping { + /** + * Return true if this maps the source field. + */ + boolean mapsField(String field); + /** * Execute this mapping. * @param context the mapping context diff --git a/org.springframework.context/src/test/java/org/springframework/mapping/support/MappingTests.java b/org.springframework.context/src/test/java/org/springframework/mapping/support/MappingTests.java index 824bd77391d..f7de9dae0bf 100644 --- a/org.springframework.context/src/test/java/org/springframework/mapping/support/MappingTests.java +++ b/org.springframework.context/src/test/java/org/springframework/mapping/support/MappingTests.java @@ -291,18 +291,19 @@ public class MappingTests { } }) // multiple fields to field - .addMapping(new Mapper() { - public Account map(CreateAccountDto source, Account target) { - DateTime dateTime = ISODateTimeFormat.dateTime().parseDateTime( - source.getActivationDate() + "T" + source.getActivationTime()); - target.setActivationDateTime(dateTime); - return target; - } - }).getMapper(); + .addMapping(new String[] { "activationDay", "activationTime " }, + new Mapper() { + public Account map(CreateAccountDto source, Account target) { + DateTime dateTime = ISODateTimeFormat.dateTime().parseDateTime( + source.getActivationDay() + "T" + source.getActivationTime()); + target.setActivationDateTime(dateTime); + return target; + } + }).getMapper(); CreateAccountDto dto = new CreateAccountDto(); dto.setAccountNumber("123456789"); dto.setName("Keith Donald"); - dto.setActivationDate("2009-10-12"); + dto.setActivationDay("2009-10-12"); dto.setActivationTime("12:00:00.000Z"); dto.setAddress("2009BelAireEstates PalmBay FL 35452"); Account account = mapper.map(dto, new Account()); @@ -500,7 +501,7 @@ public class MappingTests { private String address; - private String activationDate; + private String activationDay; private String activationTime; @@ -528,12 +529,12 @@ public class MappingTests { this.address = address; } - public String getActivationDate() { - return activationDate; + public String getActivationDay() { + return activationDay; } - public void setActivationDate(String activationDate) { - this.activationDate = activationDate; + public void setActivationDay(String activationDay) { + this.activationDay = activationDay; } public String getActivationTime() { diff --git a/spring-framework-reference/src/validation.xml b/spring-framework-reference/src/validation.xml index 4d0dabc7eda..103194724c2 100644 --- a/spring-framework-reference/src/validation.xml +++ b/spring-framework-reference/src/validation.xml @@ -1599,7 +1599,8 @@ public void testDefaultSpelMappingBehavior() { Registering Explicit Mappings When default mapping rules are not sufficient, explicit mapping rules can be registered by obtaining a MapperBuilder and using it to construct a Mapper. - Explicit mapping rules always override the default: + Explicit mapping rules always override the default. + The MapperBuilder provides a fluent API for registering object-to-object Mapping rules: mapper = @@ -1616,16 +1617,60 @@ Mapper mapper = Since the two property names are not the same, default auto-mapping would never be performed. Handle a situation like this by explicitly registering a mapping rule: - builder.addMapping("name", "fullName") + + - In this example, the name field will be mapped to the fullName field when the mapper is executed. + In the example above, the name field will be mapped to the fullName field when the mapper is executed. No default mapping will be performed for name since an explicit mapping rule has been configured for this field. +
+ Mapping a single field value to multiple fields + + Suppose you need to map PersonDto.name to Person.firstName and Person.lastName. + Handle a field-to-multi-field requirement like this by explicitly registering a mapping rule: + + () { + public Person map(String name, Person person) { + String[] names = name.split(" "); + person.setFirstName(names[0]); + person.setLastName(names[1]); + return person; + } +});]]> + + + In the example above, the first part of the name field will be mapped to the firstName field and the second part will be mapped to the lastName field. + No default mapping will be performed for name since an explicit mapping rule has been configured for this field. + +
+
+ Mapping a single field value to multiple fields + + Suppose you need to map CreateAccountDto.activationDay and CreateAccountDto.activationTime to Account.activationDateTime. + Handle a multi-field-to-field requirement like this by explicitly registering a mapping rule: + + () { + public Account map(CreateAccountDto dto, Account account) { + DateTime dateTime = ISODateTimeFormat.dateTime().parseDateTime( + dto.getActivationDay() + "T" + dto.getActivationTime()); + account.setActivationDateTime(dateTime); + return account; + } +});]]> + + + In the example above, the activationDay and activationTime fields are mapped to the single activationDateTime field. + No default mapping is performed for activationDay and activationTime since an explicit mapping rule has been configured for these fields. + +
Forcing Explicit Mappings - You can require that all mapping rules must be defined explicitly by disabling the "auto mapping" feature: + You can require that all mapping rules be defined explicitly by disabling the "auto mapping" feature: @@ -1651,14 +1696,14 @@ builder.addMapping("name", "fullName").setConverter() { new ConverterIgnoring Fields Sometimes you need to exclude a specific field on a source object from being mapped. - Do this by marking a mapping as excluded: + Do this by marking one or more source fields as excluded: builder.setExcludedFields("name");
Registering Custom Type Converters - You can also install Converters to convert values of different types in a custom way: + You may also install Converters to convert values of different types in a custom way: () { @@ -1675,7 +1720,7 @@ builder.addConverter(new Converter() {
Registering Custom Nested Mappers - When mapping between two large object graphs, you may need to register explicit mapping rules for nested bean properties. + When mapping between two object graphs, you may find you need to register explicit mapping rules for nested bean properties. Do this by adding a nested Mapper: () { });]]> - The example above registers a nested Mapper that will map nested AddressDto properties to nested Address properties. + The example Mapper above will map nested AddressDto properties to nested Address properties. This particular nested Mapper is "hand-coded", but it could have easily been another Mapper instance built by a MapperBuilder.