Tuesday, June 11, 2013

Advanced dozer mappings

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