Introduction to Dozer
Bhaskar S | 11/01/2013 |
Overview
When we are solving an Enterprise problem that involves Integration, often times we run into the problem where the target system we are integrating with expects an object of Type-B while the source has a different object of Type-A.
We typically hand-code a Utility class that creates an object of Type-B from Type-A by copying some of the data members (fields) of Type-A to Type-B.
This can get cumbersome if we have deep object nexting in an object of Type-A and/or Type-B.
Wonder if there is any open-source Java framework for this ??? Enter Dozer !!!
Dozer is an open-source Java framework that allows one to recursively copy a Java bean of Type-A to a Java bean of Type-B by mapping fields of Type-A to Type-B.
Dozer and its Dependencies
Download Dozer from the following site:
Dozer (http://dozer.sourceforge.net/)
Dozer has dependencies on the following additional open-source Java frameworks:
Apache Commons Lang3 (http://commons.apache.org/proper/commons-lang/)
Apache Commons Logging (http://commons.apache.org/proper/commons-logging/)
Apache Commons BeanUtils (http://commons.apache.org/proper/commons-beanutils/)
SLF4J (http://www.slf4j.org/)
Hands-on with Dozer
We will demonstrate the use of Dozer with a simple contact object.
In our first example, we will map the Java object Contact1A to the Java object Contact1B.
The following is an interface that defines the various contact types:
The following is the implementation of the Java object Contact1A. Notice that the contact information is stored in a java.util.Map:
The following is the implementation of the Java object Contact1B, which is a regular Java bean:
Since the Java objects Contact1A and Contact1B don't have any common field names, we will need a custom mapping XML file to map fields from Contact1A to Contact1B as shown below:
The custom mapping XML file shown in Listing.4 above consists of a root element <mappings>, which is a container for two other elements:
<configuration> element:: defines the default global properties
<mapping> element:: defines the field mappings from Type-A to Type-B
There can be more than one <mapping> sections for each of the different Java objects
The <mapping> element, in turn, has the following:
type attribute:: can have a value of either bi-directional or one-way
If we do not specify the attribute and value, it defaults to bi-directional
A value of bi-directional means a Java object of type Type-A can be converted to a Java object of type Type-B and vice-versa
class-a element:: list the Java class type of the source object
class-b element:: list the Java class type of the target object
field element:: defines the mapping from the source object field to target object field
The field names follow the JavaBean specification
The sub-element a defines the field from the source object
The sub-element b defines the field from the target object
If either sub-element a or b is a java.util.Map field, one can specify a key attribute and a value (as is the case in our example)
From our example of the custom mapping XML file from Listing.4, we gather the following:
Source class is com.polarsparc.dozer.Contact1A
Target class is com.polarsparc.dozer.Contact1B
Source field first maps to Target field firstName
Source field last maps to Target field lastName
The value corresponding to the key EMAIL from the Source field contacts (of type java.util.Map) maps to the Target field email
The value corresponding to the key HOME from the Source field contacts (of type java.util.Map) maps to the Target field home
The value corresponding to the key WORK from the Source field contacts (of type java.util.Map) maps to the Target field work
The value corresponding to the key MOBILE from the Source field contacts (of type java.util.Map) maps to the Target field mobile
The following is the test code for using Dozer, to map from an instance of Contact1A to Contact1B and vice-versa:
The core class from the Dozer Java framework that provides the desired bean mapping functionality is the class DozerBeanMapper.
Compiling and executing the test code from Listing.5 produces the following output:
Nov 02, 2013 4:40:50 PM org.dozer.config.GlobalSettings loadGlobalSettings
INFO: Trying to find Dozer configuration file: dozer.properties
Nov 02, 2013 4:40:50 PM org.dozer.config.GlobalSettings loadGlobalSettings
WARNING: Dozer configuration file not found: dozer.properties. Using defaults for all Dozer global properties.
Nov 02, 2013 4:40:50 PM org.dozer.DozerInitializer init
INFO: Initializing Dozer. Version: 5.4.0, Thread Name: main
Nov 02, 2013 4:40:50 PM org.dozer.jmx.JMXPlatformImpl register
INFO: Dozer JMX MBean [org.dozer.jmx:type=DozerStatisticsController] auto registered with the Platform MBean Server
Nov 02, 2013 4:40:50 PM org.dozer.jmx.JMXPlatformImpl register
INFO: Dozer JMX MBean [org.dozer.jmx:type=DozerAdminController] auto registered with the Platform MBean Server
Nov 02, 2013 4:40:50 PM org.dozer.DozerBeanMapper init
INFO: Initializing a new instance of dozer bean mapper.
Nov 02, 2013 4:40:50 PM org.dozer.DozerBeanMapper loadFromFiles
INFO: Using the following xml files to load custom mappings for the bean mapper instance: [contact-1-mapping.xml]
Nov 02, 2013 4:40:50 PM org.dozer.DozerBeanMapper loadFromFiles
INFO: Trying to find xml mapping file: contact-1-mapping.xml
Nov 02, 2013 4:40:50 PM org.dozer.DozerBeanMapper loadFromFiles
INFO: Using URL [file:/home/bswamina/Projects/Java/Dozer/resources/contact-1-mapping.xml] to load custom xml mappings
Nov 02, 2013 4:40:50 PM org.dozer.DozerBeanMapper loadFromFiles
INFO: Successfully loaded custom xml mappings from URL: [file:/home/bswamina/Projects/Java/Dozer/resources/contact-1-mapping.xml]
C1 >> [Contact1A] :: Name = John Doe, Contacts = {HOME=123-456-7890, MOBILE=999-888-7777, WORK=800-123-4567, EMAIL=john.doe@earth.com}
C2 >> [Contact1B] :: Name = John Doe, Contacts = [john.doe@earth.com, 123-456-7890, 800-123-4567, 999-888-7777]
C2 >> [Contact1B] :: Name = John Doe, Contacts = [john.doe@earth.com, 123-456-7890, 800-123-4567, 999-888-7777]
C3 >> [Contact1A] :: Name = John Doe, Contacts = {HOME=123-456-7890, MOBILE=999-888-7777, WORK=800-123-4567, EMAIL=john.doe@earth.com}
We have sucessfully demonstrated our first Dozer example.
Next, we will move on to our second example, where we will map the Java object Contact2A to the Java object Contact2B.
The following is the implementation of the Java object ContactInfo which encapsulates the contact information as a JavaBean:
The following is the implementation of the Java object Contact2A. Notice that this class has a reference to an instance of ContactInfo which encapsulates the contact information:
The following is the implementation of the Java object Contact2B, which is a regular Java bean:
Since the Java objects Contact2A and Contact2B don't have any common properties, we will need a custom mapping XML file to map fields from Contact2A to Contact2B as shown below:
From our example of the custom mapping XML file from Listing.9, we gather the following:
The type attribute on the <mapping> element is omitted. This means the default of bi-directional mapping is assumed
Source class is com.polarsparc.dozer.Contact2A
Target class is com.polarsparc.dozer.Contact2B
Source field first maps to Target field first. Interestingly, this property does not follow the JavaBean specification for the getters and setters. Hence, we specify the get-method and set-method attributes for the element a
Source field last maps to Target field last. Again, notice the additional attributes for the element a
The field dob of the enclosing object info (of type com.polarsparc.dozer.ContactInfo) maps to the Target field dob. Note that we specify the date format using the attribute date-format for the element b
The field email of the enclosing object info (of type com.polarsparc.dozer.ContactInfo) maps to the Target field email
The field home of the enclosing object info (of type com.polarsparc.dozer.ContactInfo) maps to the Target field home
The field work of the enclosing object info (of type com.polarsparc.dozer.ContactInfo) maps to the Target field work
The field mobile of the enclosing object info (of type com.polarsparc.dozer.ContactInfo) maps to the Target field mobile
The following is the test code for using Dozer, to map from an instance of Contact2A to Contact2B and vice-versa:
Compiling and executing the test code from Listing.10 produces the following output:
Nov 02, 2013 6:29:22 PM org.dozer.config.GlobalSettings loadGlobalSettings
INFO: Trying to find Dozer configuration file: dozer.properties
Nov 02, 2013 6:29:22 PM org.dozer.config.GlobalSettings loadGlobalSettings
WARNING: Dozer configuration file not found: dozer.properties. Using defaults for all Dozer global properties.
Nov 02, 2013 6:29:22 PM org.dozer.DozerInitializer init
INFO: Initializing Dozer. Version: 5.4.0, Thread Name: main
Nov 02, 2013 6:29:22 PM org.dozer.jmx.JMXPlatformImpl register
INFO: Dozer JMX MBean [org.dozer.jmx:type=DozerStatisticsController] auto registered with the Platform MBean Server
Nov 02, 2013 6:29:22 PM org.dozer.jmx.JMXPlatformImpl register
INFO: Dozer JMX MBean [org.dozer.jmx:type=DozerAdminController] auto registered with the Platform MBean Server
Nov 02, 2013 6:29:22 PM org.dozer.DozerBeanMapper init
INFO: Initializing a new instance of dozer bean mapper.
Nov 02, 2013 6:29:22 PM org.dozer.DozerBeanMapper loadFromFiles
INFO: Using the following xml files to load custom mappings for the bean mapper instance: [contact-2-mapping.xml]
Nov 02, 2013 6:29:22 PM org.dozer.DozerBeanMapper loadFromFiles
INFO: Trying to find xml mapping file: contact-2-mapping.xml
Nov 02, 2013 6:29:22 PM org.dozer.DozerBeanMapper loadFromFiles
INFO: Using URL [file:/home/bswamina/Projects/Java/Dozer/resources/contact-2-mapping.xml] to load custom xml mappings
Nov 02, 2013 6:29:22 PM org.dozer.DozerBeanMapper loadFromFiles
INFO: Successfully loaded custom xml mappings from URL: [file:/home/bswamina/Projects/Java/Dozer/resources/contact-2-mapping.xml]
C1 >> [Contact2A] :: Name = John Doe, Contacts = Birthdate = Thu Jan 01 00:00:00 EST 1970, Contacts = [john.doe@earth.com, 123-456-7890, 800-123-4567, 999-888-7777]
C2 >> [Contact2B] :: Name = John Doe, Birthdate = 01/01/1970, Contacts = [john.doe@earth.com, 123-456-7890, 800-123-4567, 999-888-7777]
C2 >> [Contact2B] :: Name = John Doe, Birthdate = 01/01/1970, Contacts = [john.doe@earth.com, 123-456-7890, 800-123-4567, 999-888-7777]
C3 >> [Contact2A] :: Name = John Doe, Contacts = Birthdate = Thu Jan 01 00:00:00 EST 1970, Contacts = [john.doe@earth.com, 123-456-7890, 800-123-4567, 999-888-7777]
We have sucessfully demonstrated our second Dozer example.
Finally, we will move on to our third and last example, where we will map the Java object Contact3A to the Java object Contact2B.
The following is an enum ContactEnum that defines the various contact types:
The following is the implementation of the Java object Contact3A. Notice that the contact information is stored in a java.util.Map (with the key of type ContactEnum):
For the Java object implementation for Contact2B, refer to the Listing.8 above.
Since the Java objects Contact3A and Contact2B don't have any property names in common, we will need a custom mapping XML file to map fields from Contact3A to Contact2B and vice-versa as shown below:
The custom mapping XML file in Listing.13 above seems a bit more complex than the prior ones. One of the reasons for this is due to the fact that we have a custom enum (of type ContactEnum) as the key for the contact types in the field contacts in Contact3A.
In Dozer (at least currently), there is no way of specifying the type of the key for a java.util.Map field. This is one of those cases where its upon us to write custom converters to map fields between a source and a target.
A side-effect of this is that we have to define two one-way mappings - one for mapping from Contact3A to Contact2B and the other for mapping from Contact2B to Contact3A.
The following is the implementation of a custom field converter _3Ato2BConverter for mapping a contact type from Contact3A to a field in Contact2B:
The following is the implementation of another custom field converter _2Bto3AConverter for mapping a field from Contact2B to a contact type in Contact3A:
To implement a custom converter (field or class) in Dozer, one needs to implement the org.dozer.ConfigurableCustomConverter interface. This is evident from the Listing.14 and Listing.15 above.
From our example of the custom mapping XML file from Listing.13, we gather the following:
Specified a map-id attribute on the <mapping> element. The value of the map-id attribute is used as a context to choose the desired mapping
Specified a value of one-way on the type attribute of the <mapping> element
Source class is com.polarsparc.dozer.Contact3A
Target class is com.polarsparc.dozer.Contact2B
Source field first maps to Target field firstName
Source field last maps to Target field lastName
The value corresponding to a key of type ContactEnum from the Source field contacts (of type java.util.Map) maps to the Target field. The mapping is performed by our custom converter as specified in the attribute custom-converter. The attribute custom-converter-param provides a context for the custom converter on the contact type
The following is the test code for using Dozer, to map from an instance of Contact3A to Contact2B and vice-versa:
Compiling and executing the test code from Listing.16 produces the following output:
Nov 03, 2013 12:37:05 AM org.dozer.config.GlobalSettings loadGlobalSettings
INFO: Trying to find Dozer configuration file: dozer.properties
Nov 03, 2013 12:37:05 AM org.dozer.config.GlobalSettings loadGlobalSettings
WARNING: Dozer configuration file not found: dozer.properties. Using defaults for all Dozer global properties.
Nov 03, 2013 12:37:05 AM org.dozer.DozerInitializer init
INFO: Initializing Dozer. Version: 5.4.0, Thread Name: main
Nov 03, 2013 12:37:05 AM org.dozer.jmx.JMXPlatformImpl register
INFO: Dozer JMX MBean [org.dozer.jmx:type=DozerStatisticsController] auto registered with the Platform MBean Server
Nov 03, 2013 12:37:05 AM org.dozer.jmx.JMXPlatformImpl register
INFO: Dozer JMX MBean [org.dozer.jmx:type=DozerAdminController] auto registered with the Platform MBean Server
Nov 03, 2013 12:37:05 AM org.dozer.DozerBeanMapper init
INFO: Initializing a new instance of dozer bean mapper.
Nov 03, 2013 12:37:05 AM org.dozer.DozerBeanMapper loadFromFiles
INFO: Using the following xml files to load custom mappings for the bean mapper instance: [contact-3-mapping.xml]
Nov 03, 2013 12:37:05 AM org.dozer.DozerBeanMapper loadFromFiles
INFO: Trying to find xml mapping file: contact-3-mapping.xml
Nov 03, 2013 12:37:05 AM org.dozer.DozerBeanMapper loadFromFiles
INFO: Using URL [file:/home/bswamina/Projects/Java/Dozer/resources/contact-3-mapping.xml] to load custom xml mappings
Nov 03, 2013 12:37:05 AM org.dozer.DozerBeanMapper loadFromFiles
INFO: Successfully loaded custom xml mappings from URL: [file:/home/bswamina/Projects/Java/Dozer/resources/contact-3-mapping.xml]
_3Ato2BConverter: param = EMAIL
_3Ato2BConverter: value = john.doe@earth.com
_3Ato2BConverter: param = HOME
_3Ato2BConverter: value = 123-456-7890
_3Ato2BConverter: param = WORK
_3Ato2BConverter: value = 800-123-4567
_3Ato2BConverter: param = MOBILE
_3Ato2BConverter: value = 999-888-7777
C1 >> [Contact3A] :: Name = John Doe, Contacts = {WORK=800-123-4567, EMAIL=john.doe@earth.com, HOME=123-456-7890, MOBILE=999-888-7777}
C2 >> [Contact2B] :: Name = John Doe, Birthdate = null, Contacts = [john.doe@earth.com, 123-456-7890, 800-123-4567, 999-888-7777]
_2Bto3AConverter: param = EMAIL
_2Bto3AConverter: param = HOME
_2Bto3AConverter: param = WORK
_2Bto3AConverter: param = MOBILE
C2 >> [Contact2B] :: Name = John Doe, Birthdate = null, Contacts = [john.doe@earth.com, 123-456-7890, 800-123-4567, 999-888-7777]
C3 >> [Contact3A] :: Name = John Doe, Contacts = {WORK=800-123-4567, EMAIL=john.doe@earth.com, HOME=123-456-7890, MOBILE=999-888-7777}
We have sucessfully demonstrated our third and final Dozer example.