Skip to main content

skip to main content

developerWorks  >  Java technology | XML | Open source  >

JiBX 1.2, Part 1: Java code to XML schema

Improve schema quality with custom conversion of Java data models to and from XML documents

developerWorks
Go to the previous pagePage 5 of 11 Go to the next page

Document options
PDF format - Fits A4 and Letter

PDF - Fits A4 and Letter
188 KB (29 pages)

Get Adobe® Reader®

Sample code


My developerWorks needs you!

Connect to your technical community


Rate this tutorial

Help us improve this content


BindGen customizations

In this section, you'll learn how to customize BindGen operation to control the XML representation of data, change the style of names and namespaces, and control some aspects of schema structure.

Customizing BindGen operation

BindGen supports extensive customizations for all aspects of binding and schema generation. The set of customizations to be applied are passed to BindGen as an XML document, with nested elements that mirror the structure of your Java code. Listing 6 gives a simple example:


Listing 6. Simple customizations example
<custom>
  <package name="org.jibx.starter" property-access="true">
    <class name="Address" includes="street1 street2 city state postCode country"/>
    <class name="Item" excludes="description"/>
  </package>
</custom>

This example works with a single Java code package, so Listing 6 uses just one <package> element child of the root <custom> element. <package> and <class> customization elements use name attributes that are relative to any enclosing <package> element, so in the Listing 6 example only a simple class name is required for each <class> element. <package> elements can be nested inside one another, so if you're dealing with classes across a hierarchy of packages, it's easy to handle any options using nested <package> elements. The nested structure is especially convenient because many customization attributes are inherited through the element nesting, as I'll discuss later in this section. Using nesting is optional, though — you can skip the <package> elements completely and use <class> elements with fully qualified class names directly, if you prefer.

A customization file is passed to BindGen as a command-line parameter, using the form -c file-path. Customizations are always optional, and you never need to use a customization file unless you want to change the default BindGen behavior.



Back to top


Controlling how BindGen works with your code

BindGen does a reasonable job with its default handling of Java classes, but there are limits on what can be done without user guidance. For example, the default handling is to include every field in the XML representation except for static, transient, or final fields. This approach works fine for classes that represent simple data objects; however, if your classes include state information or computed values, you might end up with an XML representation that includes values you'd rather not expose outside the class.

Customizations allow you to control in two ways what BindGen uses in the XML representation. First, you can easily switch to using bean-style getXXX(), setXXX(), and isXXX() access methods rather than directly accessing fields. Second, you can choose either to list the values you want to include in the XML representation for a class or to list the values you want to exclude. The Listing 6 customizations demonstrate both these techniques.

One-way conversions

This tutorial uses JiBX for two-way conversions between Java data structures and XML documents. BindGen also supports generating one-way conversions, which can be more convenient in some cases. For instance, value object classes are generally immutable and define only final fields and read ("get") access methods. This makes value object classes difficult to use for two-way conversions with BindGen. If you instead tell BindGen to generate an output-only conversion, it will happily work with either the fields or the properties, whichever you prefer. To generate a one-way conversion, use direction="output" or direction="input" on the root <custom> element of the customizations.

The <package> element in Listing 6 uses a property-access="true" attribute to tell BindGen to look for bean-style properties defined by public, nonstatic access methods, rather than fields, when determining which values to include in the XML representation. This attribute is an example of an inherited customization setting, which applies to everything nested inside the element with the attribute. In the Listing 6 example, the setting applies to the two nested <class> elements. It also applies to all other classes in the package, even though no <class> customization elements are present for those other classes. Besides determining how values are found from the class representation, the property-access setting also controls how the values are accessed by the generated JiBX code — directly from the fields or by calling the access methods.

The first <class> element in Listing 6 uses an includes="street1 street2 city state postCode country" attribute to list the specific values from the class that BindGen needs to include in the XML representation. The second <class> element in the listing uses an excludes="description" attribute, which lists values to be excluded from the XML representation. Because you're using property access rather than field access for values, these names are matched with properties defined by get/set/is access methods. If you were using fields, the value names would be matched with field names.

The custgen1 Ant target runs BindGen using the Listing 6 customizations. This target is an alternative to the bindgen target shown earlier, so to run the complete build you'd use the Ant command line: ant compile custgen1 bind. Listing 7 shows the item type definition from the schema generated when this target is run:


Listing 7. Customized schema output fragment
<xs:element name="item" minOccurs="0" maxOccurs="unbounded">
  <xs:complexType>
    <xs:sequence>
      <xs:element type="xs:string" name="id" minOccurs="0"/>
    </xs:sequence>
    <xs:attribute type="xs:int" use="required" name="quantity"/>
    <xs:attribute type="xs:float" use="required" name="price"/>
  </xs:complexType>
</xs:element>

You can see from Listing 7 that the description value is now missing from the schema representation, as specified by the customizations.

You need to use a different XML document as input when using this customization because the <description> element present in the original XML is no longer used. The Ant run1 target runs the test program using a data1.xml input and generating out1.xml as output. You can also run the entire sequence, from compiling the source code to running the test program, with the custom1 Ant target.

Controlling instance creation

Instance creation during unmarshalling can also be controlled using customizations. By default, JiBX expects to have a no-argument (default) constructor defined for each class (which the Java compiler generates automatically, if you don't define any other constructors). When a new instance of the class is needed during unmarshalling, JiBX uses that no-argument constructor to create the instance. If some of your classes only define constructors with arguments, you can use BindGen customizations to make them usable by JiBX. One way of doing this is by defining a factory method to be used for creating instances of the class, using a factory="xxx" attribute on the <class> customizations element to supply the fully qualified (with leading package and class information) name of a static method returning an instance of the class. You can also just add add-constructors="true" on the root <custom> element, which will generate a binding that adds no-argument constructors to classes as needed. This second approach works fine for normal data classes, but you'll still need to supply a factory for any interface or abstract classes (which can never be constructed directly). Of course, if you're generating an output-only binding (see the One-way conversions sidebar), instance creation is not an issue and you don't need to be concerned about constructors.

Other customizations for working with input classes

BindGen supports many other customizations used to control how it works with the Java input classes. For example, if you use a naming convention for your Java field names, you can configure BindGen to ignore particular prefix or suffix strings by using strip-prefixes or strip-suffixes attributes. (So to ignore leading m_ and s_ prefixes, for instance, you'd use strip-prefixes="m_ s_"). These modifications to field names are applied before the fields are matched to value names used in other customizations and naturally also apply when XML names are generated from the field names.

You can also customize the handling of individual fields or bean properties within a class, using nested <value> elements. You'll see how to work with these value customization elements in a later example.



Back to top


Controlling the XML representation

Besides controlling how BindGen interprets your Java code, you can use customizations to control the XML representation of data. These XML customizations include the actual representation (as an element or an attribute) of values, the order and names of elements and attributes, whether a value is optional or required, and more.

The earlier Listing 6 customization example demonstrates one XML customization in the form of the includes="street1 street2 city state postCode country" attribute used on the first <class> element. I discussed how this selects the values from the class that are included in the XML representation. It also controls the XML representation in that the order in which the values are listed becomes the order in which they're expressed in the XML representation. That's not a significant issue for attributes (which are always considered unordered in XML), but it is important for elements.

If you don't specify the order of values by using an includes attribute on the <class> customization, BindGen generates the values in the order they're delivered by using Java reflection on the classes. For most Java compilers and JVMs, this reflection order will match the order of the definitions in the Java source code. However, Java compilers and JVMs are not required to preserve this order from the source code, so some compilers or JVMs might cause BindGen to change the order of child elements. If you want to be certain the XML representation will always be the same no matter what Java compiler and JVM are used, the includes attribute gives you an easy way to fix the order.

You can also control the XML representation of a value using the includes attribute. BindGen allows leading flag characters to be used on each name in the list to indicate the representation: @ for an attribute, and / for an attribute. So if you change the Listing 6 customization to includes="street1 street2 city state @postCode country", the representation of the post code value changes from a child element to an attribute.

Controlling required status

Controlling whether a value is considered optional or required is another easy customization using the <class> element's requireds and optionals attributes. As with the includes attribute, you can precede names in the requireds and optionals lists by a flag character to indicate whether they should be expressed as a child element or an attribute.

By default, BindGen treats all primitive values and simple object values (classes with direct XML equivalent types, other than String) as attributes and treats all complex object values as child elements. All primitive values are treated as required, and all object values as optional. In addition to overriding these defaults at the <class> customization level by using the includes, requireds, and optionals elements, you can change the default representation to use elements for all values by setting a value-style="element" attribute at any level of customizations (<custom>, <package>, or <class> element). You can also use the require attribute to control which types should be treated as required values in the XML:

  • require="none" makes everything optional.
  • require="primitives" is the default, making only primitive values required.
  • require="objects" inverts the default, making primitives optional and object types required.
  • require="all" treats all values as required by default.

Listing 8 shows the custom2.xml customization file from the tutorial download's dwcode1 directory, illustrating several of the features I've discussed in this section:


Listing 8. Customizing order, required status, and representation
<custom property-access="true">
  <package name="org.jibx.starter">
    <class name="Address" includes="street1 street2 city @state @postCode country"
        requireds="street1 city"/>
    <class name="Customer" includes="customerNumber firstName lastName"
        requireds="lastName firstName /customerNumber"/>
    <class name="Item" excludes="description" requireds="@id quantity price"/>
    <class name="Order" requireds="/orderNumber customer billTo shipping orderDate"/>
  </package>
</custom>

You can try this set of customizations by using the Ant custgen2 target (ant compile custgen2 bind, to run the complete build). Listing 9 shows selected portions of the generated schema using these customizations, showing the resulting order, required status (with minOccurs="0" for optional elements, which are required by default in schema, and use="required" for required attributes, which are optional by default in schema), and element or attribute representation:


Listing 9. Schema generated using customizations
<xs:complexType name="order">
  <xs:annotation>
    <xs:documentation>Order information.</xs:documentation>
  </xs:annotation>
  <xs:sequence>
    <xs:element type="xs:long" name="orderNumber">
      <xs:annotation>
        <xs:documentation>Get the order number.</xs:documentation>
      </xs:annotation>
    </xs:element>
    <xs:element name="customer">
      <xs:complexType>
        <xs:sequence>
          <xs:element type="xs:long" name="customerNumber"/>
          <xs:element type="xs:string" name="firstName"/>
          <xs:element type="xs:string" name="lastName"/>
        </xs:sequence>
      </xs:complexType>
    </xs:element>
    ...
  </xs:sequence>
  <xs:attribute type="xs:date" use="required" name="orderDate"/>
  <xs:attribute type="xs:date" name="shipDate"/>
  <xs:attribute type="xs:float" name="total"/>
</xs:complexType>
<xs:element type="tns:order" name="order"/>
<xs:complexType name="address">
  <xs:annotation>
    <xs:documentation>Address information.</xs:documentation>
  </xs:annotation>
  <xs:sequence>
    <xs:element type="xs:string" name="street1"/>
    <xs:element type="xs:string" name="street2" minOccurs="0"/>
    <xs:element type="xs:string" name="city"/>
    <xs:element type="xs:string" name="country" minOccurs="0"/>
  </xs:sequence>
  <xs:attribute type="xs:string" name="state"/>
  <xs:attribute type="xs:string" name="postCode"/>
</xs:complexType>

After compiling the binding using the bind Ant task, you can test this using the run2 task which takes the data2.xml test document as input and generates an output out2.xml. You can also run the complete sequence from compile to test with the custom2 target. Listing 10 shows the test document:


Listing 10. Test document matching customizations
<order orderDate="2008-10-18" shipDate="2008-10-22" xmlns="http://jibx.org/starter">
  <orderNumber>12345678</orderNumber>
  <customer>
    <customerNumber>5678</customerNumber>
    <firstName>John</firstName>
    <lastName>Smith</lastName>
  </customer>
  <billTo state="WA" postCode="98059">
    <street1>12345 Happy Lane</street1>
    <city>Plunk</city>
    <country>USA</country>
  </billTo>
  <shipping>PRIORITY_MAIL</shipping>
  <shipTo state="WA" postCode="98034">
    <street1>333 River Avenue</street1>
    <city>Kirkland</city>
  </shipTo>
  <item quantity="1" price="5.99" id="8394983498512"/>
  <item quantity="2" price="9.50" id="9912349050499"/>
  <item quantity="1" price="8.95" id="1293000488209"/>
</order>

Compare Listing 10 with the original test document, shown in Listing 3, to see how your customizations have changed the XML representation of the data (including changing the form of the line item representations to empty elements, a much more compact representation than the original).



Back to top


Controlling names and namespaces

Java names customarily use a "camelcase" style: names are mostly lowercase, but the initial letter of each word is uppercased. For field or property names, the initial uppercase applies only to words after the first (resulting in names like postCode and customerNumber). XML names are not as standardized, and several different styles are commonly used. These include the camelcase style with initial lowercase (the Java field and property name style), camelcase with an initial uppercase character (the Java class name style), hyphen-separator (words separated by hyphens) style, dot-separator (words separated by periods) style, and underscore-separator (words separated by underscores) style.

BindGen assumes the camelcase style for XML names by default, but you can easily change this by setting a name-style attribute at any level of customization (<custom>, <package>, or <class> element). The allowed values for this attribute match the different XML styles listed above:

  • camel-case (the default)
  • upper-camel-case
  • hyphenated
  • dotted
  • underscored

You can also set the XML name for a value by using a customization specifically for that value. Using an individual value customization gives you full control over both how that value will be accessed and how it will be represented in XML. Listing 11 gives a couple of examples of using customization elements for individual values, based on the same example code you've seen in the earlier examples:


Listing 11. Customizing names and namespace
<custom property-access="true" name-style="hyphenated" namespace="http://jibx.org/custom"
    namespace-style="fixed">
  <package name="org.jibx.starter">
    <class name="Address" includes="street1 street2 city @state @postCode country"
        requireds="street1 city"/>
    <class name="Customer" includes="customerNumber firstName lastName"
        requireds="lastName firstName /customerNumber"/>
    <class name="Item" excludes="description" requireds="@id quantity price"/>
    <class name="Order" requireds="orderNumber customer billTo shipping orderDate">
     <value property-name="orderNumber" element="order-num"/>
      <value property-name="items" item-name="line-item" element="order-items"/>
    </class>
  </package>
</custom>

The first value customization in Listing 11 is for the orderNumber property, inside the <class name="Order"...> element. By using an element="order-num" attribute, the orderNumber customization tells BindGen to express the value as an element, rather than the default attribute form used for a primitive value. The second customization is for the items collection property. This customization uses both item-name and element attributes. The item-name attribute controls the name used for the individual values represented by the collection, while the element attribute forces the use of the supplied name as a wrapper element around the values in the collection.

XML without namespaces

All the tutorial examples use XML namespaces because the use of namespaces is generally considered a best practice for data exchange. If you want to work with XML without namespaces, you can use a namespace-style="none" attribute at any level of the customizations to turn off namespaces completely for all nested components.

The Listing 11 customizations also define the namespace to be used in XML documents. The previous examples rely on the default BindGen handling of namespaces, which is to derive the namespace URI used in the XML representation of Java code from the Java package. This default handling converted the org.jibx.starter package to the namespace URI http://jibx.org/starter. In Listing 11, the namespace is customized by adding a pair of attributes — namespace="http://jibx.org/custom" and namespace-style="fixed" — on the root <custom> element. The first of these attributes defines the base namespace, while the second prevents the normal behavior of modifying the namespace based on the Java package. These attributes are both inherited through nesting of customization elements, so they could just as easily have been placed on the <package> element instead of the <custom> element.

You can try out the Listing 11 customizations by using the Ant custgen3 target for the binding and schema generation, and the run3 target to run a test (after using the standard bind target to run the JiBX binding compiler — or just use the full3 target to do the whole sequence). Listing 12 shows the input document used with the test code:


Listing 12. XML sample with customized names and namespace
<order order-date="2008-10-18" ship-date="2008-10-22" xmlns="http://jibx.org/custom">
  <order-num>12345678</order-num>
  <customer>
    <customer-number>5678</customer-number>
    <first-name>John</first-name>
    <last-name>Smith</last-name>
  </customer>
  <bill-to state="WA" post-code="98059">
    <street1>12345 Happy Lane</street1>
    <city>Plunk</city>
    <country>USA</country>
  </bill-to>
  <shipping>PRIORITY_MAIL</shipping>
  <ship-to state="WA" postCode="98034">
    <street1>333 River Avenue</street1>
    <city>Kirkland</city>
  </ship-to>
  <order-items>
    <line-item quantity="1" price="5.99" id="AC4983498512"/>
    <line-item quantity="2" price="9.50" id="IW2349050499"/>
    <line-item quantity="1" price="8.95" id="RC3000488209"/>
  </order-items>
</order>

If you compare Listing 12 with the Listing 10 sample, you'll see how the representation has been changed by the latest customizations.



Back to top


Customizing schema representations

You've now seen how BindGen customizations can change the XML representation of your Java data. Customizations can also be used to control some aspects of the actual schema structure.

Recall that BindGen defaults to using nested definitions in preference to global types and elements. If you review the Listing 9 generated schema, you'll see this nesting structure. The schema uses only three global definitions: the address and order complex types, and the order element. The other classes in the Java data structure (Customer, Item, and Shipping) are each referenced at only one point in the Order class, so the corresponding type definitions are embedded directly within the order schema type definition.

You can change the schema style by using a force-mapping="true" attribute on any of the nesting customization elements. Listing 13 shows the custom4.xml customizations file, which adds this change to the custom2.xml customizations matching the Listing 9 generated schema:


Listing 13. Customization for schema structure
<custom property-access="true" force-mapping="true">
  <package name="org.jibx.starter">
    <class name="Address" includes="street1 street2 city @state @postCode country"
        requireds="street1 city"/>
    <class name="Customer" includes="customerNumber firstName lastName"
        requireds="lastName firstName /customerNumber"/>
    <class name="Item" excludes="description" requireds="@id quantity price"/>
    <class name="Order" requireds="/orderNumber customer billTo shipping orderDate"/>
  </package>
</custom>

Listing 14 shows the resulting schema structure (generated as starter.xsd by running the custgen4 Ant target). This version of the schema represents the same XML document structure as the Listing 9 schema but includes separate type definitions matching each Java class.


Listing 14. Customized schema structure
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:tns="http://jibx.org/starter" elementFormDefault="qualified"
    targetNamespace="http://jibx.org/starter">
  <xs:simpleType name="shipping">
    <xs:annotation>
      <xs:documentation>Supported shipment methods. The "INTERNATIONAL" shipment
      methods can only be used for orders with shipping addresses outside the U.S., and
      one of these methods is required in this case.</xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      ...
    </xs:restriction>
  </xs:simpleType>
  <xs:complexType name="item">
    <xs:annotation>
      <xs:documentation>Order line item information.</xs:documentation>
    </xs:annotation>
    <xs:sequence/>
    <xs:attribute type="xs:string" use="required" name="id"/>
    <xs:attribute type="xs:int" use="required" name="quantity"/>
    <xs:attribute type="xs:float" use="required" name="price"/>
  </xs:complexType>
  <xs:element type="tns:order" name="order"/>
  <xs:complexType name="address">
    <xs:annotation>
      <xs:documentation>Address information.</xs:documentation>
    </xs:annotation>
    <xs:sequence>
      <xs:element type="xs:string" name="street1"/>
      <xs:element type="xs:string" name="street2" minOccurs="0"/>
      <xs:element type="xs:string" name="city"/>
      <xs:element type="xs:string" name="country" minOccurs="0"/>
    </xs:sequence>
    <xs:attribute type="xs:string" name="state"/>
    <xs:attribute type="xs:string" name="postCode"/>
  </xs:complexType>
  <xs:complexType name="customer">
    <xs:annotation>
      <xs:documentation>Customer information.</xs:documentation>
    </xs:annotation>
    <xs:sequence>
      <xs:element type="xs:long" name="customerNumber"/>
      <xs:element type="xs:string" name="firstName"/>
      <xs:element type="xs:string" name="lastName"/>
    </xs:sequence>
  </xs:complexType>
  <xs:complexType name="order">
    <xs:annotation>
      <xs:documentation>Order information.</xs:documentation>
    </xs:annotation>
    <xs:sequence>
      <xs:element type="xs:long" name="orderNumber">
        <xs:annotation>
          <xs:documentation>Get the order number.</xs:documentation>
        </xs:annotation>
      </xs:element>
      <xs:element type="tns:customer" name="customer"/>
      <xs:element type="tns:address" name="billTo"/>
      <xs:element type="tns:shipping" name="shipping"/>
      <xs:element type="tns:address" name="shipTo" minOccurs="0"/>
      <xs:element type="tns:item" name="item" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
    <xs:attribute type="xs:date" use="required" name="orderDate"/>
    <xs:attribute type="xs:date" name="shipDate"/>
    <xs:attribute type="xs:float" name="total"/>
  </xs:complexType>
</xs:schema>

Schemas of the type shown in Listing 14, called "Venetian Blind" style schemas, are popular for use with complex XML structure definitions. By separating out each type definition, this schema style lets you easily reuse component structures when modifying or extending a schema. The flexibility of the Venetian Blind style is probably not important if you just plan to use your Java code as the base for any further changes (rerunning BindGen each time your code changes), but it can be nice if you intend to use the schema as a basis for further development.



Back to top



Go to the previous pagePage 5 of 11 Go to the next page