January 24, 2012

How Does JAXB Compare to XMLBeans?

In previous posts I compared JAXB (JSR-222) to Simple and XStream when starting from Java objects.  In this post I'll compare JAXB to XMLBeans when starting from an XML schema.   I will use XMLBeans 2.5.0 (December 2009) which is the latest release.


XML Schema (customer.xsd)

Below is the XML schema that will be used to generate our domain models.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema 
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns="http://www.example.com"
    targetNamespace="http://www.example.com"
    elementFormDefault="qualified">

    <xs:element name="customer">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="name" type="xs:string"/>
                <xs:element name="address" type="address"/>
                <xs:element 
                    name="phone-number" 
                    type="phone-number"
                    minOccurs="0" 
                    maxOccurs="unbounded"/>
             </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:complexType name="address">
        <xs:sequence>
            <xs:element name="street" type="xs:string"/>
            <xs:element name="city" type="xs:string"/>
        </xs:sequence>
    </xs:complexType>

    <xs:complexType name="phone-number">
        <xs:simpleContent>
            <xs:extension base="xs:string">
                <xs:attribute name="type" type="xs:string"/>
            </xs:extension>
        </xs:simpleContent>
    </xs:complexType>

</xs:schema>

Generating the Classes

XMLBeans

Below is the call to generate the XMLBeans classes from our XML schema.  For the purposes of this example we will use the -srconly flag to generate only the source.

scomp -d out -srconly customer.xsd

Below are all the artifacts that are generated by XMLBeans.  The XML Schema Binary (XSB) files contain metadata need to perform binding and validation:
  • com/example
    • Address.java
    • CustomerDocument.java
    • PhoneNumber.java
  • com/example/impl
    • AddressImpl.java
    • CustomerDocumentImpl.java
    • PhoneNumberImpl.java
  • schemaorg_apache_xmlbeans
    • element/http_3A_2F_2Fwww_2Eexample_2Ecom
      • customer.xsb
    • javaname/com/example
      • Address.xsb
      • CustomerDocument.xsb
      • PhoneNumber.xsb
      • CustomerDocument
        •  Customer.xsb
    • namespace/http_3A_2F_2Fwww_2Eexample_2Ecom
      • xmlns.xsb
    • src
      • customer.xsd
    •  system/s16C99350D7D3A2544A7BFD5E35CA8BC8
      •  address6f49type.xsb
      •  customer3fdddoctype.xsb
      •  customer11d7elemtype.xsb
      •  customerelement.xsb
      •  index.xsb
      •  phonenumber9c83type.xsb
      •  TypeSystemHolder.class
    • type/http_3A_2F_2Fwww_2Eexample_2Ecom
      • address.xsb
      • phone_2Dnumber.xsb
JAXB

Below is the call to generate the JAXB classes from an XML schema:

xjc -d out customer.xsd 

Below are all the artifacts that are generated by JAXB. Note how many fewer artifacts are created:
  • com.example
    • Address.java
    • Customer.java
    • ObjectFactory.java
    • package-info.java
    • PhoneNumber.java

Java Model - XMLBeans

XMLBeans produces a set of Java interfaces that are backed by implementation classes.  Below we will examine one of these pairs.

Address

This is one of the interfaces that is generated by XMLBeans.  There are a few interesting things worth noting:
  • This interface exposes POJO properties (line 24), and a DOM like model (line 29).
  • This interface includes a factory (line 66).  This factory is used for creating instances of the Address object (line 68), and for unmarshalling instances of Address from XML (line 75).
/**
 * XML Type:  address
 * Namespace: http://www.example.com
 * Java type: com.example.Address
 *
 * Automatically generated - do not modify.
 */
package com.example;


/**
 * An XML address(@http://www.example.com).
 *
 * This is a complex type.
 */
public interface Address extends org.apache.xmlbeans.XmlObject
{
    public static final org.apache.xmlbeans.SchemaType type = (org.apache.xmlbeans.SchemaType)
        org.apache.xmlbeans.XmlBeans.typeSystemForClassLoader(Address.class.getClassLoader(), "schemaorg_apache_xmlbeans.system.s16C99350D7D3A2544A7BFD5E35CA8BC8").resolveHandle("address6f49type");
    
    /**
     * Gets the "street" element
     */
    java.lang.String getStreet();
    
    /**
     * Gets (as xml) the "street" element
     */
    org.apache.xmlbeans.XmlString xgetStreet();
    
    /**
     * Sets the "street" element
     */
    void setStreet(java.lang.String street);
    
    /**
     * Sets (as xml) the "street" element
     */
    void xsetStreet(org.apache.xmlbeans.XmlString street);
    
    /**
     * Gets the "city" element
     */
    java.lang.String getCity();
    
    /**
     * Gets (as xml) the "city" element
     */
    org.apache.xmlbeans.XmlString xgetCity();
    
    /**
     * Sets the "city" element
     */
    void setCity(java.lang.String city);
    
    /**
     * Sets (as xml) the "city" element
     */
    void xsetCity(org.apache.xmlbeans.XmlString city);
    
    /**
     * A factory class with static methods for creating instances
     * of this type.
     */
    
    public static final class Factory
    {
        public static com.example.Address newInstance() {
          return (com.example.Address) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().newInstance( type, null ); }
        
        public static com.example.Address newInstance(org.apache.xmlbeans.XmlOptions options) {
          return (com.example.Address) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().newInstance( type, options ); }
        
        /** @param xmlAsString the string value to parse */
        public static com.example.Address parse(java.lang.String xmlAsString) throws org.apache.xmlbeans.XmlException {
          return (com.example.Address) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( xmlAsString, type, null ); }
        
        public static com.example.Address parse(java.lang.String xmlAsString, org.apache.xmlbeans.XmlOptions options) throws org.apache.xmlbeans.XmlException {
          return (com.example.Address) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( xmlAsString, type, options ); }
        
        /** @param file the file from which to load an xml document */
        public static com.example.Address parse(java.io.File file) throws org.apache.xmlbeans.XmlException, java.io.IOException {
          return (com.example.Address) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( file, type, null ); }
        
        public static com.example.Address parse(java.io.File file, org.apache.xmlbeans.XmlOptions options) throws org.apache.xmlbeans.XmlException, java.io.IOException {
          return (com.example.Address) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( file, type, options ); }
        
        public static com.example.Address parse(java.net.URL u) throws org.apache.xmlbeans.XmlException, java.io.IOException {
          return (com.example.Address) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( u, type, null ); }
        
        public static com.example.Address parse(java.net.URL u, org.apache.xmlbeans.XmlOptions options) throws org.apache.xmlbeans.XmlException, java.io.IOException {
          return (com.example.Address) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( u, type, options ); }
        
        public static com.example.Address parse(java.io.InputStream is) throws org.apache.xmlbeans.XmlException, java.io.IOException {
          return (com.example.Address) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( is, type, null ); }
        
        public static com.example.Address parse(java.io.InputStream is, org.apache.xmlbeans.XmlOptions options) throws org.apache.xmlbeans.XmlException, java.io.IOException {
          return (com.example.Address) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( is, type, options ); }
        
        public static com.example.Address parse(java.io.Reader r) throws org.apache.xmlbeans.XmlException, java.io.IOException {
          return (com.example.Address) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( r, type, null ); }
        
        public static com.example.Address parse(java.io.Reader r, org.apache.xmlbeans.XmlOptions options) throws org.apache.xmlbeans.XmlException, java.io.IOException {
          return (com.example.Address) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( r, type, options ); }
        
        public static com.example.Address parse(javax.xml.stream.XMLStreamReader sr) throws org.apache.xmlbeans.XmlException {
          return (com.example.Address) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( sr, type, null ); }
        
        public static com.example.Address parse(javax.xml.stream.XMLStreamReader sr, org.apache.xmlbeans.XmlOptions options) throws org.apache.xmlbeans.XmlException {
          return (com.example.Address) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( sr, type, options ); }
        
        public static com.example.Address parse(org.w3c.dom.Node node) throws org.apache.xmlbeans.XmlException {
          return (com.example.Address) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( node, type, null ); }
        
        public static com.example.Address parse(org.w3c.dom.Node node, org.apache.xmlbeans.XmlOptions options) throws org.apache.xmlbeans.XmlException {
          return (com.example.Address) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( node, type, options ); }
        
        /** @deprecated {@link org.apache.xmlbeans.xml.stream.XMLInputStream} */
        public static com.example.Address parse(org.apache.xmlbeans.xml.stream.XMLInputStream xis) throws org.apache.xmlbeans.XmlException, org.apache.xmlbeans.xml.stream.XMLStreamException {
          return (com.example.Address) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( xis, type, null ); }
        
        /** @deprecated {@link org.apache.xmlbeans.xml.stream.XMLInputStream} */
        public static com.example.Address parse(org.apache.xmlbeans.xml.stream.XMLInputStream xis, org.apache.xmlbeans.XmlOptions options) throws org.apache.xmlbeans.XmlException, org.apache.xmlbeans.xml.stream.XMLStreamException {
          return (com.example.Address) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( xis, type, options ); }
        
        /** @deprecated {@link org.apache.xmlbeans.xml.stream.XMLInputStream} */
        public static org.apache.xmlbeans.xml.stream.XMLInputStream newValidatingXMLInputStream(org.apache.xmlbeans.xml.stream.XMLInputStream xis) throws org.apache.xmlbeans.XmlException, org.apache.xmlbeans.xml.stream.XMLStreamException {
          return org.apache.xmlbeans.XmlBeans.getContextTypeLoader().newValidatingXMLInputStream( xis, type, null ); }
        
        /** @deprecated {@link org.apache.xmlbeans.xml.stream.XMLInputStream} */
        public static org.apache.xmlbeans.xml.stream.XMLInputStream newValidatingXMLInputStream(org.apache.xmlbeans.xml.stream.XMLInputStream xis, org.apache.xmlbeans.XmlOptions options) throws org.apache.xmlbeans.XmlException, org.apache.xmlbeans.xml.stream.XMLStreamException {
          return org.apache.xmlbeans.XmlBeans.getContextTypeLoader().newValidatingXMLInputStream( xis, type, options ); }
        
        private Factory() { } // No instance of this class allowed
    }
}

AddressImpl

Below is the source for the implementation class:

/**
 * XML Type:  address
 * Namespace: http://www.example.com
 * Java type: com.example.Address
 *
 * Automatically generated - do not modify.
 */
package com.example.impl;
/**
 * An XML address(@http://www.example.com).
 *
 * This is a complex type.
 */
public class AddressImpl extends org.apache.xmlbeans.impl.values.XmlComplexContentImpl implements com.example.Address
{
    private static final long serialVersionUID = 1L;
    
    public AddressImpl(org.apache.xmlbeans.SchemaType sType)
    {
        super(sType);
    }
    
    private static final javax.xml.namespace.QName STREET$0 = 
        new javax.xml.namespace.QName("http://www.example.com", "street");
    private static final javax.xml.namespace.QName CITY$2 = 
        new javax.xml.namespace.QName("http://www.example.com", "city");
    
    
    /**
     * Gets the "street" element
     */
    public java.lang.String getStreet()
    {
        synchronized (monitor())
        {
            check_orphaned();
            org.apache.xmlbeans.SimpleValue target = null;
            target = (org.apache.xmlbeans.SimpleValue)get_store().find_element_user(STREET$0, 0);
            if (target == null)
            {
                return null;
            }
            return target.getStringValue();
        }
    }
    
    /**
     * Gets (as xml) the "street" element
     */
    public org.apache.xmlbeans.XmlString xgetStreet()
    {
        synchronized (monitor())
        {
            check_orphaned();
            org.apache.xmlbeans.XmlString target = null;
            target = (org.apache.xmlbeans.XmlString)get_store().find_element_user(STREET$0, 0);
            return target;
        }
    }
    
    /**
     * Sets the "street" element
     */
    public void setStreet(java.lang.String street)
    {
        synchronized (monitor())
        {
            check_orphaned();
            org.apache.xmlbeans.SimpleValue target = null;
            target = (org.apache.xmlbeans.SimpleValue)get_store().find_element_user(STREET$0, 0);
            if (target == null)
            {
                target = (org.apache.xmlbeans.SimpleValue)get_store().add_element_user(STREET$0);
            }
            target.setStringValue(street);
        }
    }
    
    /**
     * Sets (as xml) the "street" element
     */
    public void xsetStreet(org.apache.xmlbeans.XmlString street)
    {
        synchronized (monitor())
        {
            check_orphaned();
            org.apache.xmlbeans.XmlString target = null;
            target = (org.apache.xmlbeans.XmlString)get_store().find_element_user(STREET$0, 0);
            if (target == null)
            {
                target = (org.apache.xmlbeans.XmlString)get_store().add_element_user(STREET$0);
            }
            target.set(street);
        }
    }
    
    /**
     * Gets the "city" element
     */
    public java.lang.String getCity()
    {
        synchronized (monitor())
        {
            check_orphaned();
            org.apache.xmlbeans.SimpleValue target = null;
            target = (org.apache.xmlbeans.SimpleValue)get_store().find_element_user(CITY$2, 0);
            if (target == null)
            {
                return null;
            }
            return target.getStringValue();
        }
    }
    
    /**
     * Gets (as xml) the "city" element
     */
    public org.apache.xmlbeans.XmlString xgetCity()
    {
        synchronized (monitor())
        {
            check_orphaned();
            org.apache.xmlbeans.XmlString target = null;
            target = (org.apache.xmlbeans.XmlString)get_store().find_element_user(CITY$2, 0);
            return target;
        }
    }
    
    /**
     * Sets the "city" element
     */
    public void setCity(java.lang.String city)
    {
        synchronized (monitor())
        {
            check_orphaned();
            org.apache.xmlbeans.SimpleValue target = null;
            target = (org.apache.xmlbeans.SimpleValue)get_store().find_element_user(CITY$2, 0);
            if (target == null)
            {
                target = (org.apache.xmlbeans.SimpleValue)get_store().add_element_user(CITY$2);
            }
            target.setStringValue(city);
        }
    }
    
    /**
     * Sets (as xml) the "city" element
     */
    public void xsetCity(org.apache.xmlbeans.XmlString city)
    {
        synchronized (monitor())
        {
            check_orphaned();
            org.apache.xmlbeans.XmlString target = null;
            target = (org.apache.xmlbeans.XmlString)get_store().find_element_user(CITY$2, 0);
            if (target == null)
            {
                target = (org.apache.xmlbeans.XmlString)get_store().add_element_user(CITY$2);
            }
            target.set(city);
        }
    }
}

Java Model - JAXB

JAXB implementations produce annotated POJOs.  The generated classes closely resemble the ones we created by hand in the comparisons to Simple and XStream.

Address

//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.10 in JDK 6 
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
// Any modifications to this file will be lost upon recompilation of the source schema. 
// Generated on: 2012.01.23 at 01:19:09 PM EST 
//


package com.example;

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


/**
 * <p>Java class for address complex type.
 * 
 * <p>The following schema fragment specifies the expected content contained within this class.
 * 
 * <pre>
 * <complexType name="address">
 *   <complexContent>
 *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
 *       <sequence>
 *         <element name="street" type="{http://www.w3.org/2001/XMLSchema}string"/>
 *         <element name="city" type="{http://www.w3.org/2001/XMLSchema}string"/>
 *       </sequence>
 *     </restriction>
 *   </complexContent>
 * </complexType>
 * </pre>
 * 
 * 
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "address", propOrder = {
    "street",
    "city"
})
public class Address {

    @XmlElement(required = true)
    protected String street;
    @XmlElement(required = true)
    protected String city;

    /**
     * Gets the value of the street property.
     * 
     * @return
     *     possible object is
     *     {@link String }
     *     
     */
    public String getStreet() {
        return street;
    }

    /**
     * Sets the value of the street property.
     * 
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    public void setStreet(String value) {
        this.street = value;
    }

    /**
     * Gets the value of the city property.
     * 
     * @return
     *     possible object is
     *     {@link String }
     *     
     */
    public String getCity() {
        return city;
    }

    /**
     * Sets the value of the city property.
     * 
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    public void setCity(String value) {
        this.city = value;
    }

}

Demo Code

In the demo code we will unmarshal an XML file, add a phone number to the resulting customer object, and then marshal the customer back to XML.

XMLBeans

With XMLBeans the generated domain model is used to unmarshal (line 12) and marshal (line 19).  The generated model also contains methods for interacting with collections (line 15), this is necessary as XMLBeans represent collection properties as arrays.

package com.example;

import java.io.File;
import com.example.CustomerDocument.Customer;

public class Demo {

    public static void main(String[] args) throws Exception {
        File xml = new File("src/com/example/input.xml");

        CustomerDocument customerDocument =
            CustomerDocument.Factory.parse(xml);
        Customer customer = customerDocument.getCustomer();

        PhoneNumber homePhoneNumber = customer.addNewPhoneNumber();
        homePhoneNumber.setType("home");
        homePhoneNumber.set("555-HOME");

        customerDocument.save(System.out);
    }

}

JAXB

JAXB separates the marshal/unmarshal calls into the standard runtime APIs (lines 9, 13 and 22).  A java.util.List is used for collection properties (line 18).

package com.example;

import java.io.File;
import javax.xml.bind.*;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance("com.example");

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

        PhoneNumber homePhoneNumber = new PhoneNumber();
        homePhoneNumber.setType("home");
        homePhoneNumber.setValue("555-HOME");
        customer.getPhoneNumber().add(homePhoneNumber);

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

}

Summary

Both XMLBeans and JAXB produce Java models that make it easy for developers to interact with XML.  The JAXB model however is annotated POJOs which has the following advantages:

  • JPA annotations could easily be applied to the JAXB model enabling the model to be persisted in a relational database.
  • Once generated the JAXB model could be modified to handle changes in the XML schema, the XMLBeans model would need to be regenerated.
  • Starting with Java SE 6 no additional compile/runtime dependencies are required for the JAXB model. 
  • There are multiple JAXB implementations available:  EclipseLink MOXy, Metro, Apache JaxMe, etc.
  • JAXB is the standard binding layer for JAX-WS (SOAP) and JAX-RS (RESTful) Web Services.


Further Reading

If you enjoyed this post you may also be interested in:

No comments:

Post a Comment