March 22, 2011

Map to Element based on an Attribute Value with EclipseLink JAXB (MOXy)

I am happy to announce that we are expanding MOXy's XPath based mapping in EclipseLink 2.3. You are now able to map to an element based on the value of an attribute.  This is done by leveraging XPath predicates via MOXy's @XmlPath annotation. 

You can try this out today by downloading one EclipseLink 2.3.0 nightly downloads starting on March 22, 2011 from:

XML

Usually the node names (and namespace URI) uniquely map to fields/properties in or domain classes:

<?xml version="1.0" encoding="UTF-8"?>
<customer>
    <first-name>Jane</first-name>
    <last-name>Doe</last-name>
    <address>
        <street>123 A Street</street>
    </address>
    <phone-number type="work">555-1111</phone-number>
    <phone-number type="cell">555-2222</phone-number>
</customer>

However, there are use cases where the qualifier is something like an XML attribute.  Note in the XML below how all of the XML elements are called "node", and the qualifier is an attribute called "name".

<?xml version="1.0" encoding="UTF-8"?>
<node>
   <node name="first-name">Jane</node>
   <node name="last-name">Doe</node>
   <node name="address">
      <node name="street">123 A Street</node>
   </node>
   <node name="phone-number" type="work">555-1111</node>
   <node name="phone-number" type="cell">555-2222</node>
</node>

MOXy Metadata

The XPath will be specified using MOXy's @XmlPath annotation.  The XPath is structure as follows:

@XmlPath("element-name[@attribute-name='value']")

The metadata above means that MOXy will map the field/property to the element called "element-name" that has an attribute called "attribute-name" with the value "value".

Java Model

The following domain model will be used for this example.  The get/set methods have been omitted to save space. Notice how the MOXy extensions are used to extend the JAXB metadata.

Customer

package blog.predicate;

import java.util.List;

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

import org.eclipse.persistence.oxm.annotations.XmlPath;

@XmlRootElement(name="node")
@XmlType(propOrder={"firstName", "lastName", "address", "phoneNumbers"})
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {

    @XmlPath("node[@name='first-name']/text()")
    private String firstName;

    @XmlPath("node[@name='last-name']/text()")
    private String lastName;

    @XmlPath("node[@name='address']")
    private Address address;

    @XmlPath("node[@name='phone-number']")
    private List<PhoneNumber> phoneNumbers;

}

Address

package blog.predicate;

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

import org.eclipse.persistence.oxm.annotations.XmlPath;

@XmlAccessorType(XmlAccessType.FIELD)
public class Address {

    @XmlPath("node[@name='street']/text()")
    private String street;

}

PhoneNumber

package blog.predicate;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;

@XmlAccessorType(XmlAccessType.FIELD)
public class PhoneNumber {

    @XmlAttribute
    private String type;

    @XmlValue
    private String number;

} 

jaxb.properties

In order to specify that we are using the MOXy JAXB implementation we need to put a file called jaxb.properties in with our domain classes with the following entry: 

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

Demo Code

The following code can be used to run the example;

package blog.predicate;

import java.io.File;

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

public class Demo {

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

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Customer customer = (Customer) unmarshaller.unmarshal(new File("input.xml"));

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

} 
Further Reading

If you enjoyed this post you may also be interested in:
Other articles related to new MOXy features in EclipseLink 2.3:

4 comments:

  1. How much of the XPath spec is implemented by MOXy? I'm having trouble unmarshalling using following-sibling axis.

    I'd like to be able to map a property based on the value of the previous node. For example, in the following code I'd like to map the name attribute of my class only if the value of the key node was buildingName:

    <key>buildingName</key>
    <string>Tower Seven</string>

    I had thought this might be possible with the @XPath annotation and the following-sibling axis, but that doesn't seem to be working for me.

    Any thoughts?

    ReplyDelete
  2. Hi juettner,

    MOXy does not yet support the different axis types. MOXy supports the following types of XPath:
    - "foo"
    - "foo/bar"
    - "foo[2]/bar"
    - "foo[@bar='Hello World']"

    For more information see:
    - XPath Based Mapping

    -Blaise

    ReplyDelete
  3. Hi,

    Is it possible to map strings from XML to java.util.Date inside the class ? I'm getting this exception when unmarshalling my xml file :

    Exception Description: The object [22/01/2009 20:56:29], of class [class java.lang.String], from mapping [org.eclipse.persistence.oxm.mappings.XMLDirectMapping[dateHeureObservation-->DateHeureObservation/text()]] with descriptor [XMLDescriptor(com.marshaller.test.Observation --> [DatabaseTable(Observation)])], could not be converted to [class java.util.Calendar].

    As you can see the value of the node is : "22/01/2009 20:56:29", it is automatically trying to convert it to java.util.Calendar.

    I'd like to map it to a property of type java.util.Date (I cannot change the class).

    Is it possible ?

    ReplyDelete
  4. Hi Drew,

    Since you are aren't using the default XML schema dateTime format you will need to use an XmlAdapter. Below are a couple of links to detailed answers I gave on Stack Overflow that you may find useful:
    - jaxb unmarshal timestamp
    - How to handle java.util.Date with MOXy bindings file

    -Blaise

    ReplyDelete