Binding tutorial
In this tutorial we will go through a basic example of binding XML schema (XSD) to a Java object model. We will look at basic JBossXB API and in-line schema annotations. The goal of this tutorial is to demonstrate schema-driven unmarshalling.
And, so, for schema-driven unmarshalling, first of all, we need a schema. We are going to use the schema used in XML Schema Part 0: Primer Second Edition as an example.
Let's first write the code. Since all the bindings are going to be in the schema definition, the code will be just a few lines.
SchemaBinding schema = XsdBinder.bind(resource.toExternalForm()); Unmarshaller unmarshaller = UnmarshallerFactory.newInstance().newUnmarshaller(); unmarshaller.unmarshal(xml.toExternalForm(), schema);
That's it.
In the first line, we pass our schema to XsdBinder and it gives us an object which contains binding metadata for all the defined in the schema elements, attributes and types.
The bind method will never throw an exception if the XSD file has a valid content. It might throw an exception if binding annotations in the schema are used incorrectly though. But if you have a valid schema definition, you can always pass it in to bind method and get an instance of SchemaBinding for it. Then, if necessary, you can use SchemaBinding API to adjust binding metadata for specific elements, attributes or types. In this tutorial, though, we will not look into the API-based (or programmatic) binding. We will use XSD annotations to customize bindings instead.
If you are debugging your bindings, enable TRACE logging for XsdBinder to see what elements, attributes and types it binds, what annotations are applied, etc.
Once, SchemaBinding is created it can be re-used for as many unmarshallings as you need. It is also ok to share the instance between concurrent threads. Creation of SchemaBindings might be expensive from the performance and resources point of view, so, if possible instances of SchemaBinding should be created once, cached and re-used.
If we run the code above with un-annotated schema we will receive null from the unmarshaller. Remember, we haven't yet customized bindings yet, so, the default binding rules are in effect.
Because the unmarshaller was unable to load classes it expected to be in the classpath and/or resolve fields following the default binding rules it just ignored them and returned null.
If you are debugging your bindings, enable TRACE level logging for org.jboss.xml.binding category and you will see what classes and fields were expected by the unmarshaller.
In cases when we don't expect a class or a field to be not found during unmarshalling, it is better to receive an exception immediately instead getting null as the result. You can switch to this mode by setting ignoreUnresolvedFieldOrClass to false on the SchemaBinding
schema.setIgnoreUnresolvedFieldOrClass(false);
The messages from the unmarshaller about not found classes/fields in the logs or exceptions help us to bind the schema. The unmarshaller says what it needs. At the moment of writting this tutorial the log looks like this
TRACE [RtElementHandler] Failed to resolve class for element purchaseOrder of type PurchaseOrderType: PurchaseOrderType TRACE [RtElementHandler] Failed to resolve class for element shipTo of type USAddress: USAddress TRACE [RtElementHandler] Failed to resolve class for element billTo of type USAddress: USAddress TRACE [RtElementHandler] Failed to resolve class for element item of type null: Item TRACE [RtElementHandler] Failed to resolve class for element item of type null: Item TRACE [RtElementHandler] Failed to resolve class for element item of type null: Item
Let's look at the first message. The message is pretty clear. Following the default binding rules, the unmarshaller expected class PurchaseOrderType in the default package (since the namespace is empty). We have two options: create PurchaseOrderType class in the default package or create, e.g., org.jboss.test.xml.po.PurchaseOrder class and bind purchaseOrder element to it. Let's choose the last one.
Here is an important snippet of the class code
package org.jboss.test.xml.po; public class PurchaseOrder { public USAddress shipTo; private USAddress billTo; private String comment; private Collection items; private Calendar orderDate;
We define fields for each element in purchaseOrderType. For all the private fields we also add getters and setters that are not shown here. We leave shipTo field public just show that it'll work for public fields w/o getters and setters. The class also has a no-arg constructor. It's also possible to not have a no-arg constructor but a constructor with parameters for all the fields in the class but it's a subject for another tutorial.
Let's also create USAddress and Item classes in the same package. Nothing special about them
public class USAddress { private final String country = "US"; private String name; private String street; private String city; private String state; private BigDecimal zip;
public class Item { private String productName; private BigInteger quantity; private BigDecimal USPrice; private String comment; private Calendar shipDate; private String partNum;
Now, it's time to bind elements to these classes. To be more precise, we are going to bind complex types to these classes. Since, elements inherit binding metadata from their types, binding types to these classes will also bind all the elements of these types to the classes automatically.
Before we start adding binding elements to the schema we have to declare the namespace these binding elements are defined in. Like this
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:jbxb="http://www.jboss.org/xml/ns/jbxb">
Now we can bind PurchaseOrderType to our PurchaseOrder class. Like this
<xsd:complexType name="PurchaseOrderType"> <xsd:annotation> <xsd:appinfo> <jbxb:class impl="org.jboss.test.xml.po.PurchaseOrder"></jbxb:class> </xsd:appinfo> </xsd:annotation> <xsd:sequence> <xsd:element name="shipTo" type="USAddress"></xsd:element> <xsd:element name="billTo" type="USAddress"></xsd:element> <xsd:element ref="comment" minOccurs="0"></xsd:element> <xsd:element name="items" type="Items"></xsd:element> </xsd:sequence> <xsd:attribute name="orderDate" type="xsd:date"></xsd:attribute> </xsd:complexType>
Right after complexType element but before its content definition we add xsd:annotation and inside xsd:appinfo we customize its bindings. Now, if we re-run the code, the unmarshaller will create an instance of PurchaseOrder for purchaseOrder element and even will set comment and orderDate on it since these fields satisfy the default binding rules. We don't have null anymore as the result. However, it is still unable to resolve USAddress and Item classes.
Note, the classes we developed and the expected classes have the same class names but are in different packages. So, instead of specifying the fully-qualified class names for USAddress type and the anonymous type in item element we can define the default package name to look for classes in. This is done in the schema-level annotation, the one that follows the root xsd:schema element:
<xsd:appinfo> <jbxb:schema> <jbxb:package name="org.jboss.test.xml.book"></jbxb:package> </jbxb:schema> </xsd:appinfo>
And that's all! Now, if we re-run the unmarshaller, we should get the expected an instance of PurchaseOrder. This code and the schema can be found in the testsuite. The testcase is org.jboss.test.xml.PoUnitTestCase.
NOTE: in this tutorial, for simplicity, when we developed PurchaseOrder, USAddress and Item classes, for field types we used the types from the default XSD type to Java type binding table.
Comments