This post looks at mapping JAXB objects to business domain objects with Orika. Earlier this month, I covered the same mapping use case using reflection-based Dozer. In this post, I'll assume the same example classes need to be mapped, but they will be mapped using Orika instead of Dozer.
Dozer and Orika are intended to solve the same type of problem: the automatic mapping of two "data" objects that do not share a common inheritance but represent the same same of data fields. Dozer uses reflection to accomplish this while Orika uses reflection and bytecode manipulation to accomplish it. Orika's slogan is, "simpler, lighter and faster Java bean mapping."
Orika has an Apache License, Version 2, and can be downloaded at https://github.com/orika-mapper/orika/archive/master.zip (sources) or at http://search.maven.org/#search|ga|1|orika (binaries). Orika has dependencies on Javassist (for bytecode manipulation), SLF4J, and paranamer (to access method/constructor parameter names at runtime). Two of these three dependencies (JavaAssist and paranamer but not SLF4J) are bundled in orika-core-1.4.4-deps-included.jar
. If the dependencies are already available, the slimmer orika-core-1.4.4.jar
can be used instead. As the names of these JARs suggest, I'm using Orika 1.4.4 for my examples in this post.
In my post Dozer: Mapping JAXB Objects to Business/Domain Objects, I discussed reasons that using instances of JAXB-generatated classes as business or domain objects is often not desirable. I then showed "traditional" ways of mapping between JAXB-generated classes and custom data classes so that data could be passed throughout an application in the business domain data objects. For this post, I will be using the same approach, but with Orika doing the mapping rather than doing custom mapping or using Dozer for the mapping. For convenience, I include the cost listings here for the JAXB-generated classes com.blogspot.marxsoftware.AddressType
and com.blogspot.marxsoftware.PersonType
as well as the renamed custom data classes dustin.examples.orikademo.Address
and dustin.examples.orikademo.Person
.
// // This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.4-2 // See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> // Any modifications to this file will be lost upon recompilation of the source schema. // Generated on: 2013.12.03 at 11:44:32 PM MST // package com.blogspot.marxsoftware; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlType; /** * <p>Java class for AddressType complex type. * * <p>The following schema fragment specifies the expected content contained within this class. * * <pre> * <complexType name="AddressType"> * <complexContent> * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType"> * <attribute name="streetAddress1" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="streetAddress2" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="city" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="state" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="zipcode" use="required" type="{http://www.w3.org/2001/XMLSchema}string" /> * </restriction> * </complexContent> * </complexType> * </pre> * * */ @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "AddressType") public class AddressType { @XmlAttribute(name = "streetAddress1", required = true) protected String streetAddress1; @XmlAttribute(name = "streetAddress2") protected String streetAddress2; @XmlAttribute(name = "city", required = true) protected String city; @XmlAttribute(name = "state", required = true) protected String state; @XmlAttribute(name = "zipcode", required = true) protected String zipcode; /** * Gets the value of the streetAddress1 property. * * @return * possible object is * {@link String } * */ public String getStreetAddress1() { return streetAddress1; } /** * Sets the value of the streetAddress1 property. * * @param value * allowed object is * {@link String } * */ public void setStreetAddress1(String value) { this.streetAddress1 = value; } /** * Gets the value of the streetAddress2 property. * * @return * possible object is * {@link String } * */ public String getStreetAddress2() { return streetAddress2; } /** * Sets the value of the streetAddress2 property. * * @param value * allowed object is * {@link String } * */ public void setStreetAddress2(String value) { this.streetAddress2 = value; } /** * Gets the value of the city property. * * @return * possible object is * {@link String } * */ public String getCity() { return city; } /** * Sets the value of the city property. * * @param value * allowed object is * {@link String } * */ public void setCity(String value) { this.city = value; } /** * Gets the value of the state property. * * @return * possible object is * {@link String } * */ public String getState() { return state; } /** * Sets the value of the state property. * * @param value * allowed object is * {@link String } * */ public void setState(String value) { this.state = value; } /** * Gets the value of the zipcode property. * * @return * possible object is * {@link String } * */ public String getZipcode() { return zipcode; } /** * Sets the value of the zipcode property. * * @param value * allowed object is * {@link String } * */ public void setZipcode(String value) { this.zipcode = value; } }JAXB-generated PersonType.java
// // This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.4-2 // See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> // Any modifications to this file will be lost upon recompilation of the source schema. // Generated on: 2013.12.03 at 11:44:32 PM MST // package com.blogspot.marxsoftware; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlType; /** * <p>Java class for PersonType complex type. * * <p>The following schema fragment specifies the expected content contained within this class. * * <pre> * <complexType name="PersonType"> * <complexContent> * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType"> * <sequence> * <element name="MailingAddress" type="{http://marxsoftware.blogspot.com/}AddressType"/> * <element name="ResidentialAddress" type="{http://marxsoftware.blogspot.com/}AddressType" minOccurs="0"/> * </sequence> * <attribute name="firstName" type="{http://www.w3.org/2001/XMLSchema}string" /> * <attribute name="lastName" type="{http://www.w3.org/2001/XMLSchema}string" /> * </restriction> * </complexContent> * </complexType> * </pre> * * */ @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "PersonType", propOrder = { "mailingAddress", "residentialAddress" }) public class PersonType { @XmlElement(name = "MailingAddress", required = true) protected AddressType mailingAddress; @XmlElement(name = "ResidentialAddress") protected AddressType residentialAddress; @XmlAttribute(name = "firstName") protected String firstName; @XmlAttribute(name = "lastName") protected String lastName; /** * Gets the value of the mailingAddress property. * * @return * possible object is * {@link AddressType } * */ public AddressType getMailingAddress() { return mailingAddress; } /** * Sets the value of the mailingAddress property. * * @param value * allowed object is * {@link AddressType } * */ public void setMailingAddress(AddressType value) { this.mailingAddress = value; } /** * Gets the value of the residentialAddress property. * * @return * possible object is * {@link AddressType } * */ public AddressType getResidentialAddress() { return residentialAddress; } /** * Sets the value of the residentialAddress property. * * @param value * allowed object is * {@link AddressType } * */ public void setResidentialAddress(AddressType value) { this.residentialAddress = value; } /** * Gets the value of the firstName property. * * @return * possible object is * {@link String } * */ public String getFirstName() { return firstName; } /** * Sets the value of the firstName property. * * @param value * allowed object is * {@link String } * */ public void setFirstName(String value) { this.firstName = value; } /** * Gets the value of the lastName property. * * @return * possible object is * {@link String } * */ public String getLastName() { return lastName; } /** * Sets the value of the lastName property. * * @param value * allowed object is * {@link String } * */ public void setLastName(String value) { this.lastName = value; } }Domain/Business Class Address.java
package dustin.examples.orikademo; import java.util.Objects; /** * Address class. * * @author Dustin */ public class Address { private String streetAddress1; private String streetAddress2; private String municipality; private String state; private String zipCode; public Address() {} public Address( final String newStreetAddress1, final String newStreetAddress2, final String newMunicipality, final String newState, final String newZipCode) { this.streetAddress1 = newStreetAddress1; this.streetAddress2 = newStreetAddress2; this.municipality = newMunicipality; this.state = newState; this.zipCode = newZipCode; } public String getStreetAddress1() { return this.streetAddress1; } public void setStreetAddress1(String streetAddress1) { this.streetAddress1 = streetAddress1; } public String getStreetAddress2() { return this.streetAddress2; } public void setStreetAddress2(String streetAddress2) { this.streetAddress2 = streetAddress2; } public String getMunicipality() { return this.municipality; } public void setMunicipality(String municipality) { this.municipality = municipality; } public String getState() { return this.state; } public void setState(String state) { this.state = state; } public String getZipCode() { return this.zipCode; } public void setZipCode(String zipCode) { this.zipCode = zipCode; } @Override public int hashCode() { return Objects.hash( this.streetAddress1, this.streetAddress2, this.municipality, this.state, this.zipCode); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Address other = (Address) obj; if (!Objects.equals(this.streetAddress1, other.streetAddress1)) { return false; } if (!Objects.equals(this.streetAddress2, other.streetAddress2)) { return false; } if (!Objects.equals(this.municipality, other.municipality)) { return false; } if (!Objects.equals(this.state, other.state)) { return false; } if (!Objects.equals(this.zipCode, other.zipCode)) { return false; } return true; } @Override public String toString() { return "Address{" + "streetAddress1=" + streetAddress1 + ", streetAddress2=" + streetAddress2 + ", municipality=" + municipality + ", state=" + state + ", zipCode=" + zipCode + '}'; } }Domain/Business Class Person.java
package dustin.examples.orikademo; import java.util.Objects; /** * Person class. * * @author Dustin */ public class Person { private String lastName; private String firstName; private Address mailingAddress; private Address residentialAddress; public Person() {} public Person( final String newLastName, final String newFirstName, final Address newResidentialAddress, final Address newMailingAddress) { this.lastName = newLastName; this.firstName = newFirstName; this.residentialAddress = newResidentialAddress; this.mailingAddress = newMailingAddress; } public String getLastName() { return this.lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getFirstName() { return this.firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public Address getMailingAddress() { return this.mailingAddress; } public void setMailingAddress(Address mailingAddress) { this.mailingAddress = mailingAddress; } public Address getResidentialAddress() { return this.residentialAddress; } public void setResidentialAddress(Address residentialAddress) { this.residentialAddress = residentialAddress; } @Override public int hashCode() { int hash = 3; hash = 19 * hash + Objects.hashCode(this.lastName); hash = 19 * hash + Objects.hashCode(this.firstName); hash = 19 * hash + Objects.hashCode(this.mailingAddress); hash = 19 * hash + Objects.hashCode(this.residentialAddress); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Person other = (Person) obj; if (!Objects.equals(this.lastName, other.lastName)) { return false; } if (!Objects.equals(this.firstName, other.firstName)) { return false; } if (!Objects.equals(this.mailingAddress, other.mailingAddress)) { return false; } if (!Objects.equals(this.residentialAddress, other.residentialAddress)) { return false; } return true; } @Override public String toString() { return "Person{" + "lastName=" + lastName + ", firstName=" + firstName + ", mailingAddress=" + mailingAddress + ", residentialAddress=" + residentialAddress + '}'; } }
As was the case with Dozer, the classes being mapped need to have no-arguments constructors and "set" and "get" methods to support conversion in both directions without any special additional configuration. Also, as was the case with Dozer, Orika maps same-named fields automatically and makes it easy to configure the mapping of the exceptions (fields whose names don't match). The next code listing, for a class I call OrikaPersonConverter
, demonstrates the instantiation and configuration of an Orika MapperFactory to map most fields by default and to map the fields with different names than each other ("municipality" and "city") through explicit mapping configuration. Once the MapperFactory is configured, copying from one object to another is easy and both directions are depicted in the methods copyPersonTypeFromPerson
and copyPersonFromPersonType
.
package dustin.examples.orikademo; import com.blogspot.marxsoftware.AddressType; import com.blogspot.marxsoftware.PersonType; import ma.glasnost.orika.MapperFacade; import ma.glasnost.orika.MapperFactory; import ma.glasnost.orika.impl.DefaultMapperFactory; /** * Convert between instances of {@link com.blogspot.marxsoftware.PersonType} * and {@link dustin.examples.orikademo.Person}. * * @author Dustin */ public class OrikaPersonConverter { /** Orika Mapper Facade. */ private final static MapperFacade mapper; static { final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); mapperFactory.classMap(Address.class, AddressType.class) .field("municipality", "city") .byDefault() .register(); mapper = mapperFactory.getMapperFacade(); } /** No-arguments constructor. */ public OrikaPersonConverter() {} /** * Provide an instance of {@link com.blogspot.marxsoftware.PersonType} * that corresponds with provided {@link dustin.examples.orikademo.Person} as * mapped by Orika Mapper. * * @param person Instance of {@link dustin.examples.orikademo.Person} from which * {@link com.blogspot.marxsoftware.PersonType} will be extracted. * @return Instance of {@link com.blogspot.marxsoftware.PersonType} that * is based on provided {@link dustin.examples.orikademo.Person} instance. */ public PersonType copyPersonTypeFromPerson(final Person person) { PersonType personType = mapper.map(person, PersonType.class); return personType; } /** * Provide an instance of {@link dustin.examples.orikademo.Person} that corresponds * with the provided {@link com.blogspot.marxsoftware.PersonType} as * mapped by Orika Mapper. * * @param personType Instance of {@link com.blogspot.marxsoftware.PersonType} * from which {@link dustin.examples.orikademo.Person} will be extracted. * @return Instance of {@link dustin.examples.orikademo.Person} that is based on the * provided {@link com.blogspot.marxsoftware.PersonType}. */ public Person copyPersonFromPersonType(final PersonType personType) { Person person = mapper.map(personType, Person.class); return person; } }
As is the case with Dozer, the mapping between two classes is bidirectional and so only needs to be made once and will apply in copying from either object to the other.
ConclusionLike Dozer, Orika offers much more customizability and flexibility than demonstrated in this post. However, for relatively simple mappings (which are very common with applications using JAXB-generated objects), Orika is very easy to use out of the box. A good resource for learning more about Orika is the Orika User Guide.
1 comment:
Thanks for this article, just a small precision about Orika, actually the framework can do mapping to constructor arguments: Orika user guide
Orika also support public field and any other convention beyond the JavaBean (get/set).
And with collection through getList().addAll() for example...
Post a Comment