March 3, 2013

MOXy's @XmlInverseReference is now Truly Bidirectional

EclipseLink JAXB (MOXy)'s @XmlInverseReference annotation enables you to map a back pointer during the unmarshal operation (useful when mapping JPA entities).  The problem was it acted like @XmlTransient during the marshal operation.  This means that previously it could only be used in one direction.  Now I'm happy to announce in EclipseLink 2.5.0 we have expanded @XmlInverseReference so that the corresponding property may be writeable enabling it to be used in both directions.

You can try this out today by downloading one EclipseLink 2.5.0 nightly downloads starting on March 1, 2013 from:

Java Model

Below is the Java model that we will use for this example.  To preserve backwards compatibility by default the @XmInverseReference annotation still acts like @XmlTransient during the marshal operation.  To make it writable simply pair it with the @XmlElement annotation.

Customer

Below the @XmlInverseReference annotation is used to specify that Customer's back pointer to Address is mapped by the customer property on Address.

package blog.inversereference;

import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlInverseReference;

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

    String name;

    @XmlElement
    @XmlInverseReference(mappedBy="customer")
    Address address;

}

Address

Here the @XmlInverseReference annotation is used to specify that when the address property is populated on the Customer object, the customer property on the Address object will be set as a back pointer.

package blog.inversereference;

import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;


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

    String street;

    String city;

    @XmlElement
    @XmlInverseReference(mappedBy="address")
    Customer customer;

}

jaxb.properties

When using MOXy as your JAXB (JSR-222) provider where it is not the default you need to include a file called jaxb.properties in the same package as the domain model with the following entry (see:  Specifying EclipseLink MOXy as your JAXB Provider).

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

  
XML Demo

Input (customer.xml)

In the XML document below the Address information is nested within the Customer information.

<?xml version="1.0" encoding="UTF-8"?>
<customer>
    <name>Jane Doe</name>
    <address>
        <street>1 A Street</street>
        <city>Any Town</city>
    </address>
</customer>

Demo

In the demo code below we will unmarshal an XML document where Customer is the root object.  Then we will grab the nested Address object and marshal it as the root object.

package blog.inversereference;

import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;

public class DemoXML {

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

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        StreamSource xml = new StreamSource("src/blog/inversereference/customer.xml");
        Customer customer = (Customer) unmarshaller.unmarshal(xml);

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

}

Output

In the resulting XML output now the Customer information is nested within the Address information.

<?xml version="1.0" encoding="UTF-8"?>
<address>
   <street>1 A Street</street>
   <city>Any Town</city>
   <customer>
      <name>Jane Doe</name>
   </customer>
</address>

JSON Demo

MOXy also offers first class support for JSON-binding (see: JSON Binding with EclipseLink MOXy - Twitter Example).  This means that all MOXy's XML-binding extensions also apply to its JSON-binding.

Input (address.json)

In the JSON document below the Customer information is nested within the Address information.

{
   "street" : "1 A Street",
   "city" : "Any Town",
   "customer" : {
      "name" : "Jane Doe"
   }
}

Demo

In the demo code below we will unmarshal a JSON document where Address is the root object.  Then we will grab the nested Customer object and marshal it as the root object.  This is the opposite of what we did in the XML demo.

package blog.inversereference;

import java.util.*;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextProperties;

public class DemoJSON {

    public static void main(String[] args) throws Exception {
        Map<String, Object> properties = new HashMap<String, Object>(2);
        properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
        properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
        JAXBContext jc = JAXBContext.newInstance(
            new Class[] {Customer.class}, properties);
 
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        StreamSource json = new StreamSource("src/blog/inversereference/address.json");
        Address address = unmarshaller.unmarshal(json, Address.class).getValue();
        
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(address.customer, System.out);
    }

}

Output

In the resulting JSON output now the Address information is nested within the Customer information.

{
   "name" : "Jane Doe",
   "address" : {
      "street" : "1 A Street",
      "city" : "Any Town"
   }
}

External Metadata 

MOXy also offers an external binding document which allows you to provide metadata for third party objects or apply alternate mappings for your model (see: Mapping Object to Multiple XM Schemas - Weather Example).  Below is the mapping document for this example.

<?xml version="1.0"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="blog.inversereference"
    xml-accessor-type="FIELD">
    <java-types>
        <java-type name="Customer">
            <xml-root-element/>
            <java-attributes>
                <xml-element java-attribute="address">
                    <xml-inverse-reference mapped-by="customer"/>
                </xml-element>
            </java-attributes>
        </java-type>
        <java-type name="Address">
            <xml-root-element/>
            <java-attributes>
                <xml-element java-attribute="customer">
                    <xml-inverse-reference mapped-by="address"/>
                </xml-element>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

No comments:

Post a Comment

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