April 10, 2013

MOXy's Object Graphs - Handling Inheritance

In previous posts we have explored how object graphs can be defined through metadata and programatically.  In this post I'll demonstrate the impact of inheritance in your domain model on how you define object graphs.

You can try this out today by downloading an EclipseLink 2.5.0 nightly download starting on March 24, 2013 from:

Java Model

The following domain model will be used for this example.

Customer

package blog.objectgraphs.inheritance;

import java.util.*;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlNamedAttributeNode;
import org.eclipse.persistence.oxm.annotations.XmlNamedObjectGraph;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@XmlNamedObjectGraph(
    name = "simple",
    attributeNodes = { 
        @XmlNamedAttributeNode(value="contactInfo", subgraph="simple") 
    }
)
public class Customer {
 
    private List<ContactInfo> contactInfo = new ArrayList<ContactInfo>();
  
}

ContactInfo

The @XmlNamedObjectGraph annotation is used to define the object graph for the root class, and @XmlNamedSubGraph is used to define the object graphs for the subclasses.  We have indicated that we do not want to include any of the properties from the super class.

package blog.objectgraphs.inheritance;

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

@XmlSeeAlso({Address.class, PhoneNumber.class})
@XmlAccessorType(XmlAccessType.FIELD)
@XmlNamedObjectGraph(
        name = "simple",
        attributeNodes = { 
        },
        subclassSubgraphs = {
            @XmlNamedSubgraph(
                name = "simple",
                type = Address.class,
                attributeNodes = { 
                    @XmlNamedAttributeNode("city")
                }
            ),
            @XmlNamedSubgraph(
                name = "simple",
                type = PhoneNumber.class,
                attributeNodes = {
                @XmlNamedAttributeNode("number")
            }
        )
    }
)
public abstract class ContactInfo {

    private int id;

}

Address

Address is a subclass of ContactInfo.

package blog.objectgraphs.inheritance;

public class Address extends ContactInfo {

    private String street;

    private String city;

}

PhoneNumber

PhoneNumber is also a subclass of ContactInfo.

package blog.objectgraphs.inheritance;

public class PhoneNumber extends ContactInfo {

    private String number;

    private String extension;

}

Demo Code 

Below we will explore two different approaches for using object graphs.

Demo - Object Graph Specified through Metadata

In the demo code below we will leverage the object graphs that we defined via annotations to output  a subset of the data to XML and JSON.

package blog.objectgraphs.inheritance;

import java.io.File;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.MarshallerProperties;

public class DemoMetadata {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Customer.class);
        
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/blog/objectgraphs/inheritance/input.xml");
        Customer customer = (Customer) unmarshaller.unmarshal(xml);

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

        // Output XML - Based on Object Graph
        marshaller.setProperty(MarshallerProperties.OBJECT_GRAPH, "simple");
        marshaller.marshal(customer, System.out);
        
        // Output JSON - Based on Object Graph
        marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
        marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, false);
        marshaller.marshal(customer, System.out);
    }

}

Demo - Object Graph Created Programatically

In the demo code below we will ignore the object graphs defined via metadata and create an equivalent object graph programatically.  One instance of ObjectGraph is created, and for each mapped property that we want a subset of we will create an instance of Subgraph.  When the properties type has subclasses we can create an instance of Subgraph for each class in the inheritance hierarchy.

package blog.objectgraphs.inheritance;

import java.io.File;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBHelper;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.eclipse.persistence.jaxb.ObjectGraph;
import org.eclipse.persistence.jaxb.Subgraph;

public class DemoRuntime {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Customer.class);
        
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/blog/objectgraphs/inheritance/input.xml");
        Customer customer = (Customer) unmarshaller.unmarshal(xml);

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

        // Create the Object Graph
        ObjectGraph simple = JAXBHelper.getJAXBContext(jc).createObjectGraph(Customer.class);
        Subgraph contactInfoSG = simple.addSubgraph("contactInfo", ContactInfo.class);
        Subgraph addressSG = simple.addSubgraph("contactInfo", Address.class);
        addressSG.addAttributeNodes("city");
        Subgraph phoneNumberSG = simple.addSubgraph("contactInfo", PhoneNumber.class);
        phoneNumberSG.addAttributeNodes("number");

        // Output XML - Based on Object Graph
        marshaller.setProperty(MarshallerProperties.OBJECT_GRAPH, simple);
        marshaller.marshal(customer, System.out);
        
        // Output JSON - Based on Object Graph
        marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
        marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, false);
        marshaller.marshal(customer, System.out);
    }

}

Input/Output

The following input and output are the same for both the metadata driven and programmatic demos.

input.xml/Output

We will use the following document to populate our domain model.  We will also marshal it back out to demonstrate that all of the content is actually mapped.

<?xml version="1.0" encoding="UTF-8"?>
<customer>
    <contactInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="address">
        <id>1</id>
        <street>1 A Street</street>
        <city>Any Town</city>
    </contactInfo>
    <contactInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="phoneNumber">
        <id>2</id>
        <number>555-1111</number>
        <extension>123</extension>
    </contactInfo>
</customer>

XML Output Based on Object Graph 

The XML below was produced by the exact same model as the previous XML document.  The difference is that we leveraged a named object graph to select a subset of the mapped content.

<?xml version="1.0" encoding="UTF-8"?>
<customer>
   <contactInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="address">
      <city>Any Town</city>
   </contactInfo>
   <contactInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="phoneNumber">
      <number>555-1111</number>
   </contactInfo>
</customer>

JSON Output Based on Object Graph

Below is the same subset as the previous XML document represented as JSON.

{
   "contactInfo" : [ {
      "type" : "address",
      "city" : "Any Town"
   }, {
      "type" : "phoneNumber",
      "number" : "555-1111"
   } ]
}

Further Reading
 
If you enjoyed this post then you may also be interested in:

No comments:

Post a Comment

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