Advanced dozer mappings
We are using dozer to map our external model (used in the
soap api) to our internal model. We have been very happy with dozer and it has
a lot of options.
We have come across the following problem. We have an object
say person. A person can have multiple addresses. We save the addresses in a
general class, and the person has a list of all addresses. There are times
where this is not a nice presentation to the soap api, and we wanted a specific
class per address.
This is the internal model:
public class Address {
private int type;
private String street;
private String city;
private int zip;
}
public class Person {
private List<Address>
addresses;
}
The external model should look like:
public class PersonView {
private List<Address>
addresses;
private HomeAddress home;
private WorkAddress work;
}
public class HomeAddress {
private String street;
private String city;
private int zip;
}
To make matters harder the reason we left the addresses in
the PersonView, is that addresses that are not home or work will still stay in
the old list without a specific class.
This brings us to a need to map multiple fields to the same
field. From the external we need to map all three fields (addresses,home,work)
to the one field of the internal (address). And the same in the reverse – from
one field to three.
The official documentation on dozer for mapping multiple
fields to one is as follows:
How do I map multiple
fields to a single field?
Dozer doesn't currently
support this. And because of the complexities around implementing it, this
feature is not currently on the road map. A possible solution would be to wrap
the multiple fields in a custom complex type and then define a custom converter
for mapping between the complex type and the single field. This way, you could
handle the custom logic required to map the three fields into the single one
within the custom converter.
Though it is not officially supported it does actually work
as follows:
Dozer processes the objects according to the order of the
fields in the map file. So if you have multiple fields to the set, dozer will
run them. But for it to work properly you must create a custom converter. In
this converter when converting from external to internal, you need to create a
new entry in the list with the parameters from the specific class. In the case
of internal to external you need to create the specific class and remove the
entry from the list (so that it will not be twice). You need to make sure that
the list mapping is the last in the file, so that you can remove the entries
that are now mapped to a specific class.
Mapping inherences
Now let’s say that we have the core project, and want to
have other projects that use the core to enhance the person view and add a new
type of address. The address already exits in the list of addresses but it does
not have a specific class.
Obviously we need to create a new class of PersonExView so
that it will hold the new type of address.
The problem is that dozer does not support inheritance in
the xml files (to something like parent in spring configuration files). So we
cannot add another map file for dozer to map from PersonExView to Person, since
this would mean that we have two mappings to the class of Person.
I will describe the different options and the problem with
each option, and in the end come back to a solution in the xml files.
API mapping (http://dozer.sourceforge.net/documentation/apimappings.html)
This solution actually works nicely were we can create the
map via the code. We can define a parameter for the external class
(core==PersonView, extention=PresonExView), and then add the specific fields
accordingly.
If necessary this is an option that works. I did not like it
since it meant that most of the conversions were in the map file and some in
the code. This can bring problems in the future when you need to add more
fields and don’t remember where.
Custom Converters (http://dozer.sourceforge.net/documentation/customconverter.html)
Dozer supports a few types of custom converters. The basic
ones are field converts and type converters.
Field converter
The field converter is not good enough since we need to map
multiple fields to one field.
Type converter
The type converter looked promising since it gives you full access
to the object and you can do whatever you want. The problem is that I only
wanted to change some fields and let dozer do the rest. Once you use a type converter
you need to cover all fields by yourself. If the fields are complex fields you
can pass them to dozer to covert for you (by injection the dozer map class into
the converter). But basic fields like from string to int, you need to do
yourself since the dozer map class assumes they are encapsulated in a class.
Dozer Events
Dozer also supports events, and will let you know every
stage of the process, so you can write a hook and catch before a field is converted
or after it is converted. This looked more promising since it will allow dozer
to all the work, and I can inject my code as part of the process and just do my
fields without all the others.
The problem here is that there is no place that maps the
class PresonExView to Person. Even if we could map it we come back to the
problem that you cannot map two classes to one class.
File profile mapping
In the end I found that the nicest solution is to use only
xml files (with the field converter for the addresses) and use what is called
mapId (profiles).
This feature will allow you to define two different maps.
One will map the PersonView to Person:
<mapping map-null="false"
type="bi-directional" map-id="core">
<class-a>Person</class-a>
<class-b>PersonView</class-b>
<field custom-converter-id="addressConverterId">
<a>addresses</a>
<b>home</b>
</field>
<field custom-converter-id="addressConverterId">
<a>addresses</a>
<b>work</b>
</field>
</mapping>
And in the extended project another file:
<mapping map-null="false"
type="bi-directional" map-id="extended">
<class-a>Person</class-a>
<class-b>PersonExView</class-b>
<field custom-converter-id="addressConverterId">
<a>addresses</a>
<b>home</b>
</field>
<field custom-converter-id="addressConverterId">
<a>addresses</a>
<b>work</b>
</field>
<field custom-converter-id="addressConverterId">
<a>addresses</a>
<b>mobile</b>
</field>
</mapping>
This will work since we do not have both in use at the same
time. When you want to convert between objects you specify the mapId. This way
at runtime only one definition will be used and dozer will know to use the
correct converter. In the way you can achieve what I call mapping inheritance. It
is not real inheritance since you need to redefine the whole object, but it
allows different types of mappings for the same object to others depending on
the scenario (profile/mapId).
No comments:
Post a Comment