July 14, 2010

XPath Based Mapping

In this post we'll explore the XPath based mapping aspect of EclipseLink JAXB (MOXy).  XPath based mapping is what allows an existing object model to be mapped to an existing XML schema.  MOXy is the only XML binding solution I'm aware of with this ability and therefore the only true object-to-XML mapper (OXM).


Below is the model (Customer & Address) we'll use, the get/set methods have been omitted to simplify the example.

Customer

public class Customer {

   private Address billingAddress;

   private Address shippingAddress;

} 

Address  

public class Address { 

    private String street;

}


jaxb.properties

We will add a jaxb.properties file in with our model classes to specify that MOXy should be used as our JAXB implementation.  The file must have the following content:

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

Demo

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;

public class Demo {

   public static void main(String[] args) throws Exception {
      JAXBContext jc = JAXBContext.newInstance(Customer.class);

      Customer customer = new Customer();

      Address billingAddress = new Address();
      billingAddress.setStreet("1 Billing Street");
      customer.setBillingAddress(billingAddress);

      Address shippingAddress = new Address();
      shippingAddress.setStreet("2 Shipping Road");
      customer.setShippingAddress(shippingAddress);

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

}


Standard JAXB Runtime

Using standard JAXB annotations:

import javax.xml.bind.annotation.XmlRootElement;

import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

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

   private Address billingAddress;

   private Address shippingAddress;

}

We can easily produce the following output using our Demo class:

<customer>
   <billingAddress>
      <street>1 Billing Street</street>
   </billingAddress>
   <shippingAddress>
      <street>2 Shipping Road</street>
   </shippingAddress>
</customer>

 
Using MOXy to Add a Grouping Element


Sometimes grouping elements are added to your document to organize data.  JAXB has this concept for collection properties in the form of @XmlElementWrapper.  Here we'll use @XmlPath for non-collection properties, In this case we'll nest the billing/shipping address data within the "contact-info" element.

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclipse.persistence.oxm.annotations.XmlPath;

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

   @XmlPath("contact-info/billing-address")
   private Address billingAddress;

   @XmlPath("contact-info/shipping-address") 
   private Address shippingAddress;

}

Now if we run our Demo class we will get the following output:

<customer>
   <contact-info>
      <billing-address>
         <street>1 Billing Street</street>
      </billing-address>
      <shipping-address>
         <street>2 Shipping Road</street>
      </shipping-address>
   </contact-info>
<customer>



Using MOXy to Map by Position

Normally in JAXB elements with the same name must be mapped to a collection property.  Using MOXy's path based mapping you map non-collection properties to a repeated element by index.

import javax.xml.bind.annotation.XmlRootElement;

import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclipse.persistence.oxm.annotations.XmlPath;

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

   @XmlPath("address[1]")
   private Address billingAddress;

   @XmlPath("address[2]")
   private Address shippingAddress;

}

Running the Demo class will produce the following output, notice how both address properties map to the "address" elements:
<customer>
   <address>
      <street>1 Billing Street</street>
   </address>
   <address>
      <street>2 Shipping Road</street>
   </address>
</customer>

Using MOXy to Map Two Objects to the Same Node
 
We have seen how MOXy can be used to expand the structure by adding a grouping element.  MOXy can also be used to collapse the structure by mapping two objects to the same node.  Again this is done using @XmlPath.

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclipse.persistence.oxm.annotations.XmlPath;

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

   @XmlPath(".")
   private Address billingAddress;

   private Address shippingAddress;

}

Running the Demo class one more time produces the following XML.  Notice how the street element corresponding to the billing address now appears under the customer element.

<customer>
   <street>1 Billing Street</street>
   <shippingAddress>
      <street>2 Shipping Road<street>
   </shippingAddress>
</customer>

Further Reading

4 comments:

  1. HI Blaise,

    Just while searching for Jaxb related topics, stumbled upon ur blog and it is really very informative.. Thanks for the same.. :)

    I am new to Web Services[Soap,wsdl,xml] etc.. Could you please post more articles on
    [a]how to determine xpath,
    [b]how to parse xml,
    [c]how to marshal and unmarshal objects in java,
    [d] Jaxb binding..

    I mean could you please elaborate your writing on various such 'basics' so that it would be very helpful for beginners like us..


    Thanks,
    NSP.

    ReplyDelete
  2. @XmlPath annotation is not working. help me out from this. what should i do for that??

    ReplyDelete
  3. Have you properly specified EclipseLink JAXB (MOXy) as your JAXB provider?
    - Specifying EclipseLink MOXy as Your JAXB Provider

    Also what environment are you running in? The following may help if you are using GlassFish:
    - Creating a RESTful Web Service - Part 3/5

    -Blaise

    ReplyDelete
  4. Hi Blaise,

    when using @XmlPath(".") is there a way to
    a) rename the elements, in your example "billingAdressStreet" instead of "street"
    b) specify which bean properties of the referenced object should be mapped to XML (without changing the referenced class - in your example Address - itself)?

    Thanks,
    Uli

    ReplyDelete