This commit is contained in:
Keith Donald 2009-10-17 04:03:56 +00:00
parent f55b54ec3b
commit f63c3d5313
9 changed files with 108 additions and 32 deletions

View File

@ -45,6 +45,10 @@ final class FieldToFieldMapping implements SpelMapping {
return this.targetField.getExpressionString(); return this.targetField.getExpressionString();
} }
public boolean mapsField(String field) {
return getSourceField().equals(field);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void map(SpelMappingContext context) { public void map(SpelMappingContext context) {
try { try {

View File

@ -38,6 +38,10 @@ final class FieldToMultiFieldMapping implements SpelMapping {
return this.sourceField.getExpressionString(); return this.sourceField.getExpressionString();
} }
public boolean mapsField(String field) {
return getSourceField().equals(field);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void map(SpelMappingContext context) { public void map(SpelMappingContext context) {
try { try {

View File

@ -101,10 +101,11 @@ public interface MapperBuilder<S, T> {
* Register a mapping between multiple source fields and a single target field. * 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. * 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. * 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 * @param mapper the fields to field mapper
* @return this, for configuring additional field mapping options fluently * @return this, for configuring additional field mapping options fluently
*/ */
MapperBuilder<S, T> addMapping(Mapper<S, T> mapper); MapperBuilder<S, T> addMapping(String[] fields, 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. * Register a Mapper that will be used to map between nested source and target fields of a specific sourceType/targetType pair.

View File

@ -15,6 +15,7 @@
*/ */
package org.springframework.mapping.support; package org.springframework.mapping.support;
import org.springframework.core.style.StylerUtils;
import org.springframework.mapping.Mapper; import org.springframework.mapping.Mapper;
/** /**
@ -23,13 +24,29 @@ import org.springframework.mapping.Mapper;
*/ */
final class MultiFieldToFieldMapping implements SpelMapping { final class MultiFieldToFieldMapping implements SpelMapping {
private String[] fields;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private final Mapper multiFieldMapper; private final Mapper multiFieldMapper;
public MultiFieldToFieldMapping(Mapper<?, ?> multiFieldMapper) { public MultiFieldToFieldMapping(String[] fields, Mapper<?, ?> multiFieldMapper) {
this.fields = fields;
this.multiFieldMapper = multiFieldMapper; 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") @SuppressWarnings("unchecked")
public void map(SpelMappingContext context) { public void map(SpelMappingContext context) {
try { try {
@ -52,7 +69,7 @@ final class MultiFieldToFieldMapping implements SpelMapping {
} }
public String toString() { public String toString() {
return "[MultiFieldToFieldMapping<" + this.multiFieldMapper + ">]"; return "[MultiFieldToFieldMapping<" + StylerUtils.style(this.fields) + " -> " + this.multiFieldMapper + ">]";
} }
} }

View File

@ -78,12 +78,12 @@ final class SpelMapper implements Mapper<Object, Object> {
this.mappings.add(mapping); 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)); this.mappings.add(new FieldToMultiFieldMapping(parseSourceField(field), mapper));
} }
public void addMapping(Mapper mapper) { public void addMapping(String[] fields, Mapper<?, ?> mapper) {
this.mappings.add(new MultiFieldToFieldMapping(mapper)); this.mappings.add(new MultiFieldToFieldMapping(fields, mapper));
} }
/** /**
@ -240,8 +240,7 @@ final class SpelMapper implements Mapper<Object, Object> {
private boolean explicitlyMapped(String field) { private boolean explicitlyMapped(String field) {
for (SpelMapping mapping : this.mappings) { for (SpelMapping mapping : this.mappings) {
if (mapping instanceof FieldToFieldMapping if (mapping.mapsField(field)) {
&& ((FieldToFieldMapping) mapping).getSourceField().startsWith(field)) {
return true; return true;
} }
} }

View File

@ -64,8 +64,8 @@ final class SpelMapperBuilder<S, T> implements MapperBuilder<S, T> {
return this; return this;
} }
public MapperBuilder<S, T> addMapping(Mapper<S, T> mapper) { public MapperBuilder<S, T> addMapping(String[] fields, Mapper<S, T> mapper) {
this.mapper.addMapping(mapper); this.mapper.addMapping(fields, mapper);
return this; return this;
} }

View File

@ -21,6 +21,11 @@ package org.springframework.mapping.support;
*/ */
interface SpelMapping { interface SpelMapping {
/**
* Return true if this maps the source field.
*/
boolean mapsField(String field);
/** /**
* Execute this mapping. * Execute this mapping.
* @param context the mapping context * @param context the mapping context

View File

@ -291,18 +291,19 @@ public class MappingTests {
} }
}) })
// multiple fields to field // multiple fields to field
.addMapping(new Mapper<CreateAccountDto, Account>() { .addMapping(new String[] { "activationDay", "activationTime " },
public Account map(CreateAccountDto source, Account target) { new Mapper<CreateAccountDto, Account>() {
DateTime dateTime = ISODateTimeFormat.dateTime().parseDateTime( public Account map(CreateAccountDto source, Account target) {
source.getActivationDate() + "T" + source.getActivationTime()); DateTime dateTime = ISODateTimeFormat.dateTime().parseDateTime(
target.setActivationDateTime(dateTime); source.getActivationDay() + "T" + source.getActivationTime());
return target; target.setActivationDateTime(dateTime);
} return target;
}).getMapper(); }
}).getMapper();
CreateAccountDto dto = new CreateAccountDto(); CreateAccountDto dto = new CreateAccountDto();
dto.setAccountNumber("123456789"); dto.setAccountNumber("123456789");
dto.setName("Keith Donald"); dto.setName("Keith Donald");
dto.setActivationDate("2009-10-12"); dto.setActivationDay("2009-10-12");
dto.setActivationTime("12:00:00.000Z"); dto.setActivationTime("12:00:00.000Z");
dto.setAddress("2009BelAireEstates PalmBay FL 35452"); dto.setAddress("2009BelAireEstates PalmBay FL 35452");
Account account = mapper.map(dto, new Account()); Account account = mapper.map(dto, new Account());
@ -500,7 +501,7 @@ public class MappingTests {
private String address; private String address;
private String activationDate; private String activationDay;
private String activationTime; private String activationTime;
@ -528,12 +529,12 @@ public class MappingTests {
this.address = address; this.address = address;
} }
public String getActivationDate() { public String getActivationDay() {
return activationDate; return activationDay;
} }
public void setActivationDate(String activationDate) { public void setActivationDay(String activationDay) {
this.activationDate = activationDate; this.activationDay = activationDay;
} }
public String getActivationTime() { public String getActivationTime() {

View File

@ -1599,7 +1599,8 @@ public void testDefaultSpelMappingBehavior() {
<title>Registering Explicit Mappings</title> <title>Registering Explicit Mappings</title>
<para> <para>
When default mapping rules are not sufficient, explicit mapping rules can be registered by obtaining a <classname>MapperBuilder</classname> and using it to construct a <classname>Mapper</classname>. When default mapping rules are not sufficient, explicit mapping rules can be registered by obtaining a <classname>MapperBuilder</classname> and using it to construct a <classname>Mapper</classname>.
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:
</para> </para>
<programlisting language="java"><![CDATA[ <programlisting language="java"><![CDATA[
Mapper<PersonDto, Person> mapper = Mapper<PersonDto, Person> mapper =
@ -1616,16 +1617,60 @@ Mapper<PersonDto, Person> mapper =
Since the two property names are not the same, default auto-mapping would never be performed. 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: Handle a situation like this by explicitly registering a mapping rule:
</para> </para>
<programlisting language="java">builder.addMapping("name", "fullName")</programlisting> <programlisting language="java"><![CDATA[
builder.addMapping("name", "fullName")]]>
</programlisting>
<para> <para>
In this example, the <literal>name</literal> field will be mapped to the <literal>fullName</literal> field when the mapper is executed. In the example above, the <literal>name</literal> field will be mapped to the <literal>fullName</literal> field when the mapper is executed.
No default mapping will be performed for <literal>name</literal> since an explicit mapping rule has been configured for this field. No default mapping will be performed for <literal>name</literal> since an explicit mapping rule has been configured for this field.
</para> </para>
</section> </section>
<section id="mapping.SpelMapper-Explicit-singleFieldToMultipleField">
<title>Mapping a single field value to multiple fields</title>
<para>
Suppose you need to map <literal>PersonDto.name</literal> to <literal>Person.firstName</literal> and <literal>Person.lastName</literal>.
Handle a field-to-multi-field requirement like this by explicitly registering a mapping rule:
</para>
<programlisting language="java"><![CDATA[
builder.addMapping("name", new Mapper<String, Person>() {
public Person map(String name, Person person) {
String[] names = name.split(" ");
person.setFirstName(names[0]);
person.setLastName(names[1]);
return person;
}
});]]>
</programlisting>
<para>
In the example above, the first part of the <literal>name</literal> field will be mapped to the <literal>firstName</literal> field and the second part will be mapped to the <literal>lastName</literal> field.
No default mapping will be performed for <literal>name</literal> since an explicit mapping rule has been configured for this field.
</para>
</section>
<section id="mapping.SpelMapper-Explicit-multipleFieldsToField">
<title>Mapping a single field value to multiple fields</title>
<para>
Suppose you need to map <literal>CreateAccountDto.activationDay</literal> and <literal>CreateAccountDto.activationTime</literal> to <literal>Account.activationDateTime</literal>.
Handle a multi-field-to-field requirement like this by explicitly registering a mapping rule:
</para>
<programlisting language="java"><![CDATA[
builder.addMapping(new String[] { "activationDay", "activationTime" }, new Mapper<CreateAccountDto, AccountDto>() {
public Account map(CreateAccountDto dto, Account account) {
DateTime dateTime = ISODateTimeFormat.dateTime().parseDateTime(
dto.getActivationDay() + "T" + dto.getActivationTime());
account.setActivationDateTime(dateTime);
return account;
}
});]]>
</programlisting>
<para>
In the example above, the <literal>activationDay</literal> and <literal>activationTime</literal> fields are mapped to the single <literal>activationDateTime</literal> field.
No default mapping is performed for <literal>activationDay</literal> and <literal>activationTime</literal> since an explicit mapping rule has been configured for these fields.
</para>
</section>
<section id="mapping.SpelMapper-Explicit-forcing"> <section id="mapping.SpelMapper-Explicit-forcing">
<title>Forcing Explicit Mappings</title> <title>Forcing Explicit Mappings</title>
<para> <para>
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:
</para> </para>
<programlisting language="java"><![CDATA[ <programlisting language="java"><![CDATA[
builder.setAutoMappingEnabled(false);]]> builder.setAutoMappingEnabled(false);]]>
@ -1651,14 +1696,14 @@ builder.addMapping("name", "fullName").setConverter() { new Converter<String, St
<title>Ignoring Fields</title> <title>Ignoring Fields</title>
<para> <para>
Sometimes you need to exclude a specific field on a source object from being mapped. 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:
</para> </para>
<programlisting language="java">builder.setExcludedFields("name");</programlisting> <programlisting language="java">builder.setExcludedFields("name");</programlisting>
</section> </section>
<section id="mapper.SpelMapper-CustomTypeConverters"> <section id="mapper.SpelMapper-CustomTypeConverters">
<title>Registering Custom Type Converters</title> <title>Registering Custom Type Converters</title>
<para> <para>
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:
</para> </para>
<programlisting language="java"><![CDATA[ <programlisting language="java"><![CDATA[
builder.addConverter(new Converter<String, Date>() { builder.addConverter(new Converter<String, Date>() {
@ -1675,7 +1720,7 @@ builder.addConverter(new Converter<String, Date>() {
<section id="mapper.SpelMapper-CustomNestedMappers"> <section id="mapper.SpelMapper-CustomNestedMappers">
<title>Registering Custom Nested Mappers</title> <title>Registering Custom Nested Mappers</title>
<para> <para>
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: Do this by adding a nested Mapper:
</para> </para>
<programlisting language="java"><![CDATA[ <programlisting language="java"><![CDATA[
@ -1687,7 +1732,7 @@ builder.addNestedMapper(new Mapper<AddressDto, Address>() {
});]]> });]]>
</programlisting> </programlisting>
<para> <para>
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. This particular nested Mapper is "hand-coded", but it could have easily been another Mapper instance built by a MapperBuilder.
</para> </para>
</section> </section>