Jump to: navigation, search

EclipseLink/UserGuide/MOXy/Advanced Concepts/XPath Predicates

< EclipseLink‎ | UserGuide‎ | MOXy
Revision as of 10:52, 15 July 2011 by Rick.barkhouse.oracle.com (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

EclipseLink MOXy

link="http://wiki.eclipse.org/EclipseLink"
EclipseLink
Website
Download
Community
Mailing ListForumsIRC
Bugzilla
Open
Help Wanted
Bug Day
Contribute
Browse Source

Elug api package icon.png Key API

Mapping using XPath Predicates

As we have seen in previous examples, by default, JAXB will use the Java field name as the XML element name:

public class Customer {
   @XmlElement
   private String firstName;
   @XmlElement
   private String lastName;
}
<?xml version="1.0" encoding="UTF-8"?>
<customer>
   <firstName>Bob</firstName>
   <lastName>Roberts</lastName>
</customer>

Or, the XML name can be customized using the name attribute of the @XmlElement annotation:

public class Customer {
   @XmlElement(name="f-name")
   private String firstName;
   @XmlElement(name="l-name")
   private String lastName;
}
<?xml version="1.0" encoding="UTF-8"?>
<customer>
   <f-name>Bob</f-name>
   <l-name>Roberts</l-name>
</customer>

However, sometimes elements need to be mapped based on their position in the document, or based on an attribute value of an element:

<?xml version="1.0" encoding="UTF-8"?>
<node>
   <name>Jane</name>
   <name>Doe</name>
   <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>


For cases like this, EclipseLink MOXy allows you to use XPath predicates to define an expression that will specify the XML element's name.


XPath Predicates

An XPath predicate represents an expression that will be evaluated against the element specified. For example, the XPath statement:

node[2]

Would match the second occurrence of the node element ("DEF"):

<?xml version="1.0" encoding="UTF-8"?>
<data>
   <node>ABC</node>
   <node>DEF</node>
</data>

Predicates can also match based on an attribute value:

node[@name='foo']

Would match the node element with the attribute name="foo" ("ABC"). It would not match the node that contains "DEF":

<?xml version="1.0" encoding="UTF-8"?>
<data>
   <node name="foo">ABC</node>
   <node name="bar">DEF</node>
</data>


Idea.png
For more information on XPath Predicates, see "2.4 Predicates" of the XML Path Language (XPath) specification.


Mapping based on Position

In the following example, our XML contains two name elements; the first occurrence of name should represent the Customer's first name, and the second name will be their last name. To map this, we will specify XPath expressions for each property that will match the appropriate XML element. Note that we also use @XmlType(propOrder) to ensure that our elements will always be in the proper positions.

package example;
 
import javax.xml.bind.annotation.*;
 
import org.eclipse.persistence.oxm.annotations.XmlPath;
 
@XmlRootElement
@XmlType(propOrder={"firstName", "lastName"})
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
    @XmlPath("name[1]/text()")
    private String firstName;
 
    @XmlPath("name[2]/text()")
    private String lastName;
 
    ...
}

This same configuration can be expressed in an EclipseLink XML Bindings document as follows:

...
<java-type name="Customer">
   <xml-root-element/>
   <xml-type prop-order="firstName lastName"/>
   <java-attributes>
      <xml-element java-attribute="firstName" xml-path="name[1]/text()"/>
      <xml-element java-attribute="lastName" xml-path="name[2]/text()"/>
   </java-attributes>
</java-type>
...

This will give us the desired XML representation:

<?xml version="1.0" encoding="UTF-8"?>
<customer>
   <name>Bob</name>
   <name>Smith</name>
</customer>


Mapping based on an Attribute Value

Since EclipseLink MOXy 2.3, you can also map to an XML element based on an Attribute value. In this example, all of our XML elements are named node, differentiated by the value of their name attribute:

<?xml version="1.0" encoding="UTF-8"?>
<node>
   <node name="first-name">Bob</node>
   <node name="last-name">Smith</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>

We can use an XPath in the form of element-name[@attribute-name='value'] to map each Java field:

package example;
 
import javax.xml.bind.annotation.*;
 
import org.eclipse.persistence.oxm.annotations.XmlPath;
 
@XmlRootElement(name="node")
@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 = new ArrayList<PhoneNumber>();
 
    ...
}
package example;
 
import javax.xml.bind.annotation.*;
 
import org.eclipse.persistence.oxm.annotations.XmlPath;
 
@XmlAccessorType(XmlAccessType.FIELD)
public class Address {
 
    @XmlPath("node[@name='street']/text()")
    private String street;
 
    ...
}
package example;
 
import javax.xml.bind.annotation.*;
 
@XmlAccessorType(XmlAccessType.FIELD)
public class PhoneNumber {
 
    @XmlAttribute
    private String type;
 
    @XmlValue
    private String number;
 
    ...
}


"Self" Mappings

EclipseLink allows you to configure your one-to-one mappings so the data from the target object will appear inside the source object's XML element. Expanding on the previous example, we could map the Address information so that it would appear directly under the customer element, and not wrapped in its own element. This is referred to as a "self" mapping, and is achieved by setting the target object's XPath to "." (dot).

The following example demonstrates a self mapping declared in annotations.

package example;
 
import javax.xml.bind.annotation.*;
 
import org.eclipse.persistence.oxm.annotations.XmlPath;
 
@XmlRootElement(name="node")
@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(".")
    private Address address;
 
    @XmlPath("node[@name='phone-number']")
    private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>();
 
    ...
}

Using a self mapping, EclipseLink produces the desired XML. The street data is stored in the root node.

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