August 26, 2010

Using @XmlAnyElement to Build a Generic Message

In this post we are building our own XML messaging framework.  We want to have a standard message object (similar to a SOAP envelope) that the framework can understand, and the ability to easily add domain specific payloads over time.


Message Object

Our message object needs to represent 3 things:
  1. Who the message is from.
  2. Who the message is to.
  3. The body of the message.
The first 2 items are easy enough to do, the 3rd is harder because we don't know what the payload of the message is going to be.  We will handle this by setting the type of the property to Object, and using the @XmlAnyElement(lax=true) annotation.  What this means is that when dealing with this property the @XmlRootElement of the referenced object will be used.  If lax is set to false then the content will be unmarshalled as DOM nodes.

package message;

import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Message {

    @XmlAttribute
    private String to;

    @XmlAttribute
    private String from;

    @XmlAnyElement(lax=true)
    private Object body;

}

Customer Payload

The Customer class will be the root type for the body of the message when the payload corresponds to customer information, so we need to annotate it as @XmlRootElement.

package customer;

import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {

    private String name;
    private Address address;

}

package customer;

import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
public class Address {

    private String street;
    private String city;

}

Product Payload

The Product class will be the root type for the body of the message when the payload corresponds to product information, so we need to annotate it with @XmlRootElement.

package product;

import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Product {

    private String name;

}

Creating the JAXBContext

We could create the JAXBContext on an array of classes, but our goal is to be able to easily add additional payloads over time.  Here we will create the JAXBContext using a context path:

JAXBContext.newInstance("message:customer:product");

The context path includes 3 package names seperated by the colon ':' character.  In each of these packages we need to include a file named jaxb.index with a list of files.  Below is an example of the jaxb.index file in the customer package:

Address
Customer

When we want to add a model to represent orders to our framework we would extend our JAXBContext creation to like:
JAXBContext.newInstance("message:customer:product:order");


Trying it Out

Finally, we can verify that our example works with the following message.  The body of the message corresponds to a customer payload. 

<message to="john@example.com" from="jane@example.com">
    <customer>
        <name>Sue Smith</name>
        <address>
            <street>123 A Street</street>
            <city>Any Town</city>
        </address>
    </customer>
</message>

Below is some sample code to handle this message.
import java.io.File;
import javax.xml.bind.*;
import message.Message;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance("message:customer:product");

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File file = new File("input.xml");
        Message message = (Message) unmarshaller.unmarshal(file);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(message, System.out);
    }

}

11 comments:

  1. Thanks blaise. I don't suppose you could give an example of how to marshal this XML using this object structure could you? I want to do the reverse: Inject some fairly freeform XML into a field of type @XmlAnyElement.

    ReplyDelete
  2. With this example you could create an instance of Message and set an instance of Customer or Product on it. You can also set an instance of org.w3c.dom.Element on it, if you wish to include free form XML.

    If you always want the content of the property to be a DOM node you have specify that lax=false on the @XmlAnyElement annotation.

    ReplyDelete
  3. Once I have the message object,
    Message message = (Message) unmarshaller.unmarshal(file);

    What is the best way to get the Customer object out of the Message object? Do I have to check for instanceof against all object types that I have and cast it to the proper type? Eg

    if(message.getBody() instanceof Customer) {
    Customer customer = (Customer) message.getBody();
    }

    I say this because I am working on an extensible REST api with tons of objects, and thats a lot of boilerplate code that I have to write.

    ReplyDelete
  4. Since with JAXB you have control over you objects, you could leverage a common super class or interface with known API that will eliminate the need for doing the instanceof checks.

    -Blaise

    ReplyDelete
  5. Many frameworks are classpath aware? Why do you think JAXB hasn't taken this approach and done away with the ObjectFactory?

    ReplyDelete
    Replies
    1. Hi bballing,

      Is there a particular framework that you could point to as an example?

      -Blaise

      Delete
  6. Say in the springframework you are able to add @Component to a class, then in the applicationContext.xml you add it scans that class path looking for classes with @Component annotations.

    - Josh

    ReplyDelete
  7. Hi Blaise,
    I am working with CXF 2.3.6 with default JAXB binding, and marshalling of interfaces works using the technique mentioned in this post. However, unmarshalling fails with the error
    2012-09-06 17:36:00,264 [qtp1223223487-31] ERROR - javax.ws.rs.WebApplicationException: java.lang.ClassCastException: com.sun.org.apache.xerces.internal.dom.ElementNSImpl cannot be cast to com.emeter.config.mgmt.model.jaxb.valueholder.IValueHolder
    javax.ws.rs.WebApplicationException: java.lang.ClassCastException: com.sun.org.apache.xerces.internal.dom.ElementNSImpl cannot be cast to com.emeter.config.mgmt.model.jaxb.valueholder.IValueHolder

    It doesn't work with lax=true either. I do not have much control over creating the JAXBContext with CXF. I do not want to override too much in the framework, just use whatever configuration is provided. I need the interface to be extensible by adding more classes (other developers, different jars) in future.

    I read this http://blog.bdoughan.com/2010/07/moxy-jaxb-map-interfaces-to-xml.html, and after reading your comment dated March 6, 2012 in that post, now I am confused whether unmarshalling of interfaces even works with JAXB RI, or I have to necessarily use Moxy.

    If I necessarily have to use Moxy, how do I instruct CXF to use Moxy and not use JAXB RI.

    regards,
    Kartik

    ReplyDelete
    Replies
    1. Hi Kartik,

      In JAX-RS frameworks a ContextResolver can be used to build a JAXBContext that is aware of all the model classes that it needs to be. I'm assuming that objects are coming back as DOM elements because JAXB has processed the classes with the corresponding @XmlRootElement annotations. You can find a ContextResolver example here:
      - MOXy's XML Metadata in a JAX-RS Service

      MOXy has some added support for handling interfaces. The following example will work with any JAXB (JSR-222) implementation:
      - JAXB and Interface Fronted Models

      -Blaise

      Delete
  8. Hi Blaise ,

    Do you have solution for http://stackoverflow.com/questions/18036811 ?

    ReplyDelete