MOXy’s Object Graphs & Dynamic JAXB
JAXB (JSR-222) makes it easy for you to convert instances of your domain classes to/from XML. The EclipseLink MOXy implementation offers an extension called Dynamic JAXB where instead of real classes you have instances of a map like class called DynamicEntity. You can access the data on your DynamicEntity using get and set methods that take the property name (i.e. customer.get(“address”) and customer.set(‘name”, “Jane Doe”). In this post, first we will bootstrap a dynamic JAXBContext based on an external mapping file. Then we unmarshal an XML document to dynamic entities, and finally we will apply an object graph to scope the resulting JSON output.
You can try this out today by downloading an EclipseLink 2.5.0 nightly download starting on March 24, 2013 from:
Dynamic Java Model
With a static model the metadata is derived from the Java classes and augmented by any provided metadata (see: JAXB – No Annotations Required ). Since in MOXy’s dynamic JAXB there are no domain classes the types must be completely defined by metadata. This can be done from an XML schema or as done in this example using MOXy’s external mapping document.
oxm.xml
Since there are no real Java classes, in the external mapping document we need to specify each mapping, and for each mapping the type of the Java property.
<?xml version="1.0"?> <xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" package-name="blog.objectgraphs.dynamic"> <java-types> <java-type name="Customer"> <xml-named-object-graphs> <xml-named-object-graph name="contact info"> <xml-named-attribute-node name="name"/> <xml-named-attribute-node name="billingAddress" subgraph="location"/> <xml-named-attribute-node name="phoneNumbers" subgraph="simple"/> <xml-named-subgraph name="location"> <xml-named-attribute-node name="city"/> <xml-named-attribute-node name="province"/> </xml-named-subgraph> </xml-named-object-graph> </xml-named-object-graphs> <xml-root-element/> <java-attributes> <xml-attribute java-attribute="id" type="java.lang.Integer"/> <xml-element java-attribute="name" type="java.lang.String"/> <xml-element java-attribute="billingAddress" type="blog.objectgraphs.dynamic.Address"/> <xml-element java-attribute="shippingAddress" type="blog.objectgraphs.dynamic.Address"/> <xml-element java-attribute="phoneNumbers" name="phoneNumber" type="blog.objectgraphs.dynamic.PhoneNumber" container-type="java.util.List"> <xml-element-wrapper/> </xml-element> </java-attributes> </java-type> <java-type name="Address"> <java-attributes> <xml-element java-attribute="street" type="java.lang.String"/> <xml-element java-attribute="city" type="java.lang.String"/> <xml-element java-attribute="province" type="java.lang.String"/> <xml-element java-attribute="postalCode" type="java.lang.String"/> </java-attributes> </java-type> <java-type name="PhoneNumber"> <xml-named-object-graphs> <xml-named-object-graph name="simple"> <xml-named-attribute-node name="value"/> </xml-named-object-graph> </xml-named-object-graphs> <java-attributes> <xml-attribute java-attribute="type" type="java.lang.String"/> <xml-value java-attribute="value" type="java.lang.String"/> </java-attributes> </java-type> </java-types> </xml-bindings>
jaxb.properties
A jaxb.properties file is used to specify the JAXB provider. For dynamic JAXB the contents of this file are slightly different than usual when using MOXy (compare with Specifying EclipseLink MOXy as your JAXB Provider). This file goes in the same package as the domain model, since here we have a virtual domain model the jaxb.properties file would be the only real item in the blog.objectgraphs.dynamic package.
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContextFactory
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 graph that was defined in the external mapping document. The object graph was defined for the dynamic model exactly as it was for the corresponding static model (see: MOXy’s Object Graphs – Input/Ouput Partial Models to XML & JSON). The only thing that is different is that the object we get from the unmarshal call is an instance of DynamicEntity rather than Customer.
package blog.objectgraphs.dynamic; import java.io.File; import java.util.*; import javax.xml.bind.*; import org.eclipse.persistence.dynamic.DynamicEntity; import org.eclipse.persistence.jaxb.JAXBContextProperties; import org.eclipse.persistence.jaxb.MarshallerProperties; public class DemoMetadata { public static void main(String[] args) throws Exception { Map<String, Object> properties = new HashMap<String, Object>(1); properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "blog/objectgraphs/dynamic/oxm.xml"); JAXBContext jc = JAXBContext.newInstance("blog.objectgraphs.dynamic", DemoMetadata.class.getClassLoader(), properties); Unmarshaller unmarshaller = jc.createUnmarshaller(); File xml = new File("src/blog/objectgraphs/dynamic/input.xml"); DynamicEntity customer = (DynamicEntity) 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, "contact info"); 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.setProperty(MarshallerProperties.JSON_WRAPPER_AS_ARRAY_NAME, true); marshaller.marshal(customer, System.out); } }
Demo – Object Graph Created Programatically
In the demo code below we will create object graph programmatically. The object graph was created for the dynamic model exactly as it was for the corresponding static model (see: MOXy’s Object Gaphs – Partial Models on the Fly to/from XML & JSON). What is different is that we used the name of the dynamic entity to create the object graph instead of the class, and we get an instance of DynamicEntity rather than Customer from the unmarshal call.
package blog.objectgraphs.dynamic; import java.io.File; import java.util.*; import javax.xml.bind.*; import org.eclipse.persistence.dynamic.DynamicEntity; import org.eclipse.persistence.jaxb.JAXBContextProperties; 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 { Map<String, Object> properties = new HashMap<String, Object>(1); properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, "blog/objectgraphs/dynamic/oxm.xml"); JAXBContext jc = JAXBContext.newInstance("blog.objectgraphs.dynamic", DemoMetadata.class.getClassLoader(), properties); Unmarshaller unmarshaller = jc.createUnmarshaller(); File xml = new File("src/blog/objectgraphs/dynamic/input.xml"); DynamicEntity customer = (DynamicEntity) 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 contactInfo = JAXBHelper.getJAXBContext(jc) .createObjectGraph("blog.objectgraphs.dynamic.Customer"); contactInfo.addAttributeNodes("name"); Subgraph location = contactInfo.addSubgraph("billingAddress"); location.addAttributeNodes("city", "province"); Subgraph simple = contactInfo.addSubgraph("phoneNumbers"); simple.addAttributeNodes("value"); // Output XML - Based on Object Graph marshaller.setProperty(MarshallerProperties.OBJECT_GRAPH, contactInfo); 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.setProperty(MarshallerProperties.JSON_WRAPPER_AS_ARRAY_NAME, true); 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 id="123"> <name>Jane Doe</name> <billingAddress> <street>1 A Street</street> <city>Any Town</city> <province>Ontario</province> <postalCode>A1B 2C3</postalCode> </billingAddress> <shippingAddress> <street>2 B Road</street> <city>Another Place</city> <province>Quebec</province> <postalCode>X7Y 8Z9</postalCode> </shippingAddress> <phoneNumbers> <phoneNumber type="work">555-1111</phoneNumber> <phoneNumber type="home">555-2222</phoneNumber> </phoneNumbers> </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 an object graph to select a subset of the mapped content.
<?xml version="1.0" encoding="UTF-8"?> <customer> <name>Jane Doe</name> <billingAddress> <city>Any Town</city> <province>Ontario</province> </billingAddress> <phoneNumbers> <phoneNumber>555-1111</phoneNumber> <phoneNumber>555-2222</phoneNumber> </phoneNumbers> </customer>
JSON Output Based on Object Graph
Below is the same subset as the previous XML document represented as JSON. We have used the new JSON_WRAPPER_AS_ARRAY_NAME property (see Binding to JSON & XML – Handling Collections ) to improve the representation of collection values.
{ "name" : "Jane Doe", "billingAddress" : { "city" : "Any Town", "province" : "Ontario" }, "phoneNumbers" : [ "555-1111", "555-2222" ] }
pointless