ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Understanding JAXB: Java Binding Customization
Pages: 1, 2

Inline Annotated Schema

Modifying the XSD is one way to control the generated output from the schema compiler, but it is not the only way. In fact, you can't control a lot of things simply by modifying the XSD file. To that end, the schema compiler uses a set of default binding rules when it generates Java bindings. For example, the schema compiler will autogenerate a Java identifier name from an XML name as a default binding rule. Often, it is necessary to override default binding rules either due to naming requirements (e.g., package names) or naming collisions. We can override binding rules in one of two ways: by using an external binding declaration file or by writing binding declaration inline with the XML schema document. Let's see an example where we override binding rules inline with the schema.



Listing 5 shows an XML schema that contains information that may be useful to a drawing application. For clarity, an associated XML document is also shown in Listing 6.

Listing 5. An XML Schema Document for a Drawing Application

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" 
  xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" 
  jaxb:version="1.0" jaxb:extensionBindingPrefixes="xjc">
    <xs:element name="Widgets">
        <xs:complexType>
            <xs:choice minOccurs="0" maxOccurs="unbounded">
                <xs:element name="Rectangle" type="Rectangle"/>
                <xs:element name="Square" type="Square"/>
                <xs:element name="Circle" type="Circle"/>
            </xs:choice>
         </xs:complexType>
    </xs:element>
    <xs:complexType name="Rectangle">
        <xs:sequence>
            <xs:element name="Width" type="xs:integer"/>
            <xs:element name="Height" type="xs:integer"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="Square">
        <xs:sequence>
            <xs:element name="Length" type="xs:integer"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="Circle">
        <xs:sequence>
            <xs:element name="Radius" type="xs:integer"/>
        </xs:sequence>
    </xs:complexType>
</xs:schema>

Listing 6. Sample XML Document for the Drawing Application

<?xml version="1.0" encoding="UTF-8"?>
<Widgets xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" 
  xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:noNamespaceSchemaLocation="C:\hold\Example\po4.xsd">
    <Square>
        <Length>6</Length>
    </Square>
    <Rectangle>
        <Width>2</Width>
        <Height>9</Height>
    </Rectangle>

    <Circle>
        <Radius>2</Radius>
    </Circle>
    <Rectangle>
        <Width>4</Width>
        <Height>5</Height>
    </Rectangle>
</Widgets>

As shown, the element Widgets defines a choice group which can have three possibilities: Rectangle, Square and/or Circle. Each of the above mentioned elements are defined as a complexType. Realize that the shape elements can appear any number of times and in any order. Let's have a look at the generated Java bindings. In particular, look at the methods exposed to the Widgets:

Listing 7. Generated Bindings for the Shapes Example

public interface WidgetsType {

    java.util.List getRectangleOrSquareOrCircle();
    public interface Circle
        extends javax.xml.bind.Element, generated.Circle
    {
    }
    public interface Rectangle
        extends javax.xml.bind.Element, generated.Rectangle
    {
    }
    public interface Square
        extends javax.xml.bind.Element, generated.Square
    {
    }

}

public interface Widgets
    extends javax.xml.bind.Element, generated.WidgetsType
{

}

To start, we have a naming problem. We did not provide a name (via binding declaration) for the choice group, and so the binding compiler had to autogenerate a name. In this case, it took the names of the subelements and combined them with "Or". Hence the accessor name for the list:

java.util.List getRectangleOrSquareOrCircle();

Cleary the above method name is not meaningful and we need to resolve that. Before we do however, let's see how we might use the bindings.

Listing 8. Java Bindings in Action

JAXBContext jc = JAXBContext.newInstance("generated");
Unmarshaller u = jc.createUnmarshaller();
Widgets widgets = (Widgets)u.unmarshal(new File(xmlfile));
List shapes = widgets.getRectangleOrSquareOrCircle();
// iterate shapes
Iterator shapesIt = shapes.iterator();
while(shapesIt.hasNext())
{
	Object obj = shapesIt.next();
	if(obj instanceof Circle)
	{
		Circle circle = (Circle)obj;
		// process circle...
	}
	else if(obj instanceof Square)
	{
		Square square = (Square)obj;
		// process Square
	}
	else if(obj instanceof Square)
	{
		Rectangle rectangle = (Rectangle)obj;
		// process Rectangle...
	}
	else
	{
		// throw exception
	}
}

We have already identified that we have a naming problem in the accessor method. Observing the code in Listing 8, we see a fundamental OO problem. We have to get a list of shapes, figure out what the real type of the shape is, and then do something with it. It would be nice if we can avoid this. Specifically, it would be nice if we could have all of the shapes derive from a common base class so we could treat the shapes polymorphically. Listing 9 shows an XSD with annotated binding rules to resolve the issues discussed.

Listing 9. Annotated XSD

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" 
  xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" 
  jaxb:version="1.0" jaxb:extensionBindingPrefixes="xjc">
    <xs:annotation>
        <xs:appinfo>
            <jaxb:globalBindings>
                <xjc:superClass name="com.syh.Shape"/>
            </jaxb:globalBindings>
        </xs:appinfo>
    </xs:annotation>
    <xs:element name="Widgets">
        <xs:complexType>
            <xs:choice minOccurs="0" maxOccurs="unbounded">
                <xs:annotation>
                    <xs:appinfo>
                        <jaxb:property name="Shapes"/>
                    </xs:appinfo>
                </xs:annotation>
                <xs:element name="Rectangle" type="Rectangle"/>
                <xs:element name="Square" type="Square"/>
                <xs:element name="Circle" type="Circle"/>
            </xs:choice>
         </xs:complexType>
    </xs:element>
    <xs:complexType name="Rectangle">
        <xs:sequence>
            <xs:element name="Width" type="xs:integer"/>
            <xs:element name="Height" type="xs:integer"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="Square">
        <xs:sequence>
            <xs:element name="Length" type="xs:integer"/>
        </xs:sequence>
    </xs:complexType>
    <xs:complexType name="Circle">
        <xs:sequence>
            <xs:element name="Radius" type="xs:integer"/>
        </xs:sequence>
    </xs:complexType>
</xs:schema>

We have made two changes to our XSD from Listing 7:

  1. Added a globalBindings element that defines what the base class is for the implementation objects.
  2. Below the choice group, we have given the choice group a name (Shapes).

Listing 10 shows a portion of the generated Java bindings.

Listing 10. Improved Java Bindings

public interface WidgetsType
{
    java.util.List getShapes();
    public interface Circle
        extends javax.xml.bind.Element, generated.Circle
    {
    }
    public interface Rectangle
        extends javax.xml.bind.Element, generated.Rectangle
    {
    }
    public interface Square
        extends javax.xml.bind.Element, generated.Square
    {
    }
}

public class SquareImpl
    extends com.syh.Shape
    implements generated.Square, com.sun.xml.bind.JAXBObject,
     generated.impl.runtime.UnmarshallableObject, 
     generated.impl.runtime.XMLSerializable, 
     generated.impl.runtime.ValidatableObject
{
}

As shown, we now get an accessor that makes sense. Also shown is one of the shape implementations. As you can see, the shape now derives from com.syh.Shape (as do the other shapes). Now instead of having to determine the underlying shape type, we can treat all shapes polymorphically. We have made great improvements with little changes to the XSD. Unfortunately, the changes do come with a caveat. Note that we have put the base class for the shapes at the globalBindings section. The globalBindings section defines overrides at the global scope. That means that all of the objects generated by the binding compiler will extend com.syh.Shape, not just the shapes. In other words, the implementation of the root element, Widget, also derives from com.syh.Shape. Unfortunately, there is nothing we can do about that at this point. With JAXB, you either get all or none; either all of your objects have to derive from the same base class, or none. You cannot have just one class derive from a base class and not the rest. Perhaps in a later version of JAXB, Sun will resolve this issue. For now, however, we can either live with the Widget being a Shape or modify this by hand.

A note in passing: the objects generated by the schema compiler are not, by default, serializable. This poses a problem if one needs to pass these objects over the network (e.g., if we use them along with our EJBs). Fortunately, we can add another entry to the global bindings section to have all of our objects implement java.io.Serializable. To generate serializable objects, add:

<xjc:serializable uid="12343"/>

to the globalBindings section. Finally, realize that JAXB is a specification and vendors provide implementation of the JAXB specs. Sun provides a reference implementation of the specification. The two entries we made into the globalBindings section (prefixed with the xjc namespace) are vendor extensions. Any time your schema contains vendor extensions, you have to compile with the -extension switch.

External Binding Declaration

We have already discussed how we can override binding rules by annotating the XML schema. In this section, we'll talk about how one can remove the binding rules out of the schema document and into a separate file (called an external binding file). This technique is useful when you are dealing with large schema documents or when you simply want to separate the two files for clarity. Interestingly, the external binding file is also an XML schema document. The XML schema document contains binding declarations that override the default binding rules. Listing 10 shows the external binding file for our previous example.

Listing 11. Using An External Binding File

<jxb:bindings version="1.0" 
  xmlns:jxb="http://java.sun.com/xml/ns/jaxb" 
  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" 
  jxb:extensionBindingPrefixes="xjc">
    <jxb:bindings schemaLocation="po4.xsd" node="/xs:schema">
        <jxb:globalBindings>
            <xjc:superClass name="com.syh.Shape"/>
            <xjc:serializable uid="12343"/>
        </jxb:globalBindings>
        <jxb:bindings node="//xs:element[@name='Widgets']//xs:complexType//xs:choice">
            <jxb:property name="Shapes"/>
        </jxb:bindings>
    </jxb:bindings>
</jxb:bindings>

The binding file shown above does exactly what the previous example did inline. That is, it overrides bindings so that:

  1. All implementation objects derive from com.syh.Shape.
  2. All implementation objects are serializable.
  3. The name of the choice group is set to Shapes, to ensure that the list is named appropriately (i.e., getShapes()).

There is nothing magical about using an external binding file. The only thing that may throw you off is that you need to understand the basics of XPath, but that's it. An interesting point at this stage is that you can use inline and external binding simultaneously. Moreover, you can separate your external bindings to more than one file, if necessary. The only thing to remember is that when compiling with external bindings you need to use the -b switch for every bindings file. Finally, understand that there is not a restriction on the external binding file's extension. However, by convention, .xjb is used.

Binding Declaration Language

We have shown several examples where we override the default bindings used by the binding compiler. In all of our examples, we intentionally left out the fact that behind all of our overrides is a language called Binding Declaration Language. We deliberately left this out because the majority of readers will want to gain an understanding of JAXB to the level where they can solve their day-to-day problems, without having to become an expert on the topic. Having said that, since we have completed the examples, it makes sense to just mention a few facts about Binding Declaration Language.

  1. The Binding Declaration Language is an XML-based language.
  2. The Binding Declaration Language defines the constructs of binding declarations.
  3. The language is extensible.

Unless you are thinking about writing an implementation of JAXB, you can usually get by just by looking at examples.

Conclusion

We have shown that there are three techniques that one can use to customize the Java bindings produced by the schema compiler. They include:

  1. Modifying the XML schema document.
  2. Using inline annotated binding declarations.
  3. Using external binding files.

A fair question at this point is: when would you use method 1 versus method 2 or 3? Realistically, you have to a combination of methods (1 and either 2 or 3). You have to apply 1, because 1 will drastically improve the Java bindings from the start; you will get bindings that better map to the XML document. You have to understand 1 and begin to write your schema documents following the suggestions outlined in 1. Once you have run through 1, all of your other problems will be solved by either 2 or 3. These usually include naming issues that were not solved with 1.

An interesting point to keep in mind while considering or working with JAXB is that one can easily generate an XSD using a tool; given an XML document, one can generate an XSD. Unfortunately, the XSDs that are generated by tools result in horrible Java bindings using JAXB. The conclusion is that you have to write the schema by hand. The good news is that writing a schema is much simpler and faster that having to hand-code (and test) Java bindings that can handle marshalling and unmarshalling as seamlessly as JAXB can. Having said that, one might ask, "Can I just use an XML document with the schema compiler rather than a schema document?" The definitive answer is no; a schema document is required.

References

Sayed Hashimi is an independent consultant based in Jacksonville, Florida. He loves to work with both Java and .NET based technologies. His research interest include: languages, algorithms, and mesh generation. Sayed holds a Master of Engineering Degree from the University of Florida.


Return to ONJava.com.