December 3, 2010

JAXB and Immutable Objects

I was recently sent a blog post:  JAXB and immutable objects, by Aniket Shaligram.  In this post the author addresses the issue by adding a default constructor and using field access, to avoid adding set methods.  This approach is perfectly valid, but leaves the class being more mutable than some people like (including the person who sent me the link).  In this post I'll discuss an alternative approach using XmlAdapter.

Java Model

The following object model will be used for this example:

package blog.immutable;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="purchase-order")
public class PurchaseOrder {

    private Customer customer;

    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

}

Customer will be our immutable object.   Note how the name and address fields are final and do not have corresponding setters, the values may only be set once via the constructor.

package blog.immutable;

public class Customer {

    private final String name;
    private final Address address;

    public Customer(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public Address getAddress() {
        return address;
    }

}

package blog.immutable;

public class Address {

    private String street;

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

} 

Mapping the Immutable Class

JAXB does not know how to deal with the Customer class since it does not have a no-arg constructor and its fields are final. To solve this we need to introduce a class that JAXB does know how to handle:

package blog.immutable.adpater;

import javax.xml.bind.annotation.XmlAttribute;
import blog.immutable.Address;

public class AdaptedCustomer {

    private String name;
    private Address address;

    @XmlAttribute
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

}

Then we will leverage an XmlAdapter to convert between the unmappable Customer and the mappable AdaptedCustomer classes:

package blog.immutable.adpater;

import javax.xml.bind.annotation.adapters.XmlAdapter;
import blog.immutable.Customer;

public class CustomerAdapter extends XmlAdapter<AdaptedCustomer, Customer> {

    @Override
    public Customer unmarshal(AdaptedCustomer adaptedCustomer) throws Exception {
        return new Customer(adaptedCustomer.getName(), adaptedCustomer.getAddress());
    }

    @Override
    public AdaptedCustomer marshal(Customer customer) throws Exception {
        AdaptedCustomer adaptedCustomer = new AdaptedCustomer();
        adaptedCustomer.setName(customer.getName());
        adaptedCustomer.setAddress(customer.getAddress());
        return adaptedCustomer;
    }

}

Then we annotate the Customer class to leverage our XmlAdapter.  Now any field/property of type Customer will automatically leverage the XmlAdapter during marshal and unmarshal operations:

package blog.immutable;

import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import blog.immutable.adpater.CustomerAdapter;

@XmlJavaTypeAdapter(CustomerAdapter.class)
public class Customer {

    private final String name;
    private final Address address;

    public Customer(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public Address getAddress() {
        return address;
    }

}

Summary

As can be seen from this example, with XmlAdapter there is no such thing as an unmapped class with JAXB. For more information on XmlAdapter see:

11 comments:

  1. Hi Blaise

    I've tried to use XmlAdapter to bind my immutable object, and there is a problem i've faced.

    In your example PurchaseOrder instance is mutable, and its member is immutable. I've tried and it works that way fine.

    In my case i need the root object to be immutable. So I tried to use @XmlJavaTypeAdapter with @XmlRootElement and it didn't worked.

    So I'm wondering if that is correct behavior and is there a way to use XmlAdapter for root elements?

    - Oleksiy

    ReplyDelete
  2. Hi Oleksiy,

    The root object case is not quite as clean. The result of the unmarshal operation will be the adapted object, then you will need to call the conversion code yourself.

    -Blaise

    ReplyDelete
  3. Thanks for this post, it was very informative.

    As part of the research we've been doing on our project, we discovered that since Java 1.5 (and thanks to JSR-133) it's possible to construct objects using reflection that have only a private constructor as well as only final fields, and JAXB can take advantage of this.

    For those that aren't a fan of having to essentially create a copy of the Customer object (as in the above example) you can define a private no-arg constructor as follows:

    private Customer() {
    this.name = null;
    this.address = null;
    }

    Then you don't actually need to create a type adapter. I posted a working example of this based on your post at the following location:

    https://test.kuali.org/svn/rice/sandbox/immutable-jaxb/

    ReplyDelete
    Replies
    1. link worked for me, and I think its the cleanest solution by far.. so here is Customer (Java 1.5): for posterity


      package blog.immutable;

      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.XmlRootElement;

      @XmlRootElement(name="customer")
      @XmlAccessorType(XmlAccessType.NONE)
      public final class Customer {

      @XmlAttribute
      private final String name;

      @XmlElement
      private final Address address;

      @SuppressWarnings("unused")
      private Customer() {
      this(null, null);
      }

      public Customer(String name, Address address) {
      this.name = name;
      this.address = address;
      }

      public String getName() {
      return name;
      }

      public Address getAddress() {
      return address;
      }

      }

      Delete
  4. Hi -

    So we have an interesting problem and I'm not sure if XmlAdapter can be used. Basically, we have this scencario:

    We want to use JAXB to annotate POJO classes for marshalling/unmarshalling into JSON objects, instead of XML. The format of the JSON object is a simple event message as follows:
    - eventTime: A JSON timestamp
    - eventType: A JSON String denoting the event type
    - payload: A generic JSON object

    So the problem is we want to keep the "payload" property of the message opaque for as long as possible. Hence, we would like to parse the JSON message object into a POJO like this:

    public class EventMessage {
    public Date eventTime;
    public String eventType;

    // A Java Object, or some generic JAXB equivalent
    public Object payload;
    }

    Then down the line some bit of logic can examine the eventType, determine that it's interested in the message, and do a second pass to parse the "payload" property into something concrete (i.e., if this was a user update message, then maybe into a POJO called UserUpdatePayload).

    Thanks for your help,
    -Julio

    ReplyDelete
  5. Hi Julio,

    I wouldn't use XmlAdapter for this scenario. Instead you could mark the payload property with @XmlAnyElement. Then you could do one of the following:

    1. @XmlAnyElement(lax=true)

    You could have one model for the message envelope, and other models for the individual payloads. My post below gives an example of how this could be done.

    - Using @XmlAnyElement to Build a Generic Message

    2. @XmlAnyElement

    If you just mark the payload property with @XmlAnyElement the contents will be kept as a DOM node (this should still work if you're using JAXB to process JSON via something like Jettison). Then later you can unmarshal this DOM node into the correct payload.

    -Blaise

    ReplyDelete
  6. Is there a way to push the

    @XmlJavaTypeAdapter(CustomerAdapter.class)

    Into config rather than annotations? My domain model is in a third part lib.

    JD

    ReplyDelete
  7. Hi,

    I'm trying to map a collection of immutable objects by using interface on elements, i.e. the collection property type is List< Code> with Code an interface.

    Should I create the Adapter so that it adapts to/from the interface or the implementation?

    I tried both ways and JAXB RI complains that it can't find a no-arg contructor, as if it was ignoring the XmlJavaTypeAdapter.

    Is what I'm trying to do a supported scenario?

    Thanks,
    Gabriele

    ReplyDelete
    Replies
    1. Hi Gabriele,

      Since Code is an interface depending on your JAXB implementation you may need to specify an @XmlElement(type=CodeImpl.class) where CodeImpl is a JAXB friendly object and they write an XmlAdapter that converts to/from that to your immutable object.
      - JAXB and Interface Fronted Models

      -Blaise

      Delete

Note: Only a member of this blog may post a comment.