Figure 13.2 showed the use of custom binding declarations with the binding compiler, to override the default binding behavior. The binding declarations are XML elements themselves. JAXB specifies two techniques to pass them to the binding compiler:
They can be passed as separate XML input, as Figure 13.2 shows.
They can be inlined with the source XML schema itself. This is the mechanism supported by the JAXB reference implementation.
When inlined, the declarations are included, using the xsd:annotation and xsd:appinfo tags.
In XML schemas, the annotation element is a top-level element that specifies comments treated as inline documentation by schema parsers. The annotation may have any number of documentation or appInfo elements as child elements. appinfo specifies information specific to the application. A sample schema fragment is shown below:
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:annotation> <xsd:appInfo>W3Schools Note</xsd:appInfo> <xsd:documentation xml:lang="en"> Some application documentation here </xsd:documentation> </xsd:annotation> <!--other elements here with possibly any number of annotations--> </xsd:schema>
When inlining declarations with the source schema, JAXB uses these annotation and appInfo elements to insert the JAXB specific information, using the following syntax:
<xsd:annotation> <xsd:appinfo> <binding declaration> </xsd:appinfo> </xsd:annotation>
If an external file is used to specify the declarations, the file follows this format:
<jaxb:bindings schemaLocation = "location of schema here"> <jaxb:bindings node = "XPath String referencing node here"> <binding declaration> <jaxb:bindings> </jaxb:bindings>
For example, an external binding declaration referencing the billingaddress node in the purchaseorder.xsd schema may look like this:
<jaxb:bindingsxmlns:jaxb="http://java.sun.com/xml/jaxb" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ens="http://www.flutebank.com/schema" version="1.0"> <jaxb:bindings schemaLocation="purchaseorder.xsd"> <jaxb:binding node="./xs:attribute[@name='billingaddress']"> <jaxb:property name="country"/> </jaxb:binding> </jaxb:binding> </jaxb:bindings>
A binding declaration has scope that specifies the schema elements to which it applies. There are four levels of scope, described below and shown in Figure 13.3, ranging from the generic to the specific. The more specific scope (e.g., component) overrides the higher-level (e.g., global) scope.
Global scope applies to all schema elements in the source schema and all schemas included or imported.
Schema scope applies to all schema elements in a schema's target namespace.
Definition scope applies to all schema elements that reference the type definition or the global declaration.
Component scope applies only to the schema element annotated with the binding declaration.
Our treatment of the declarations in the subsequent paragraphs has purposely been kept light. We recommend the JAXB specifications for detailed syntax use, for two reasons:
Most developers are likely to use the default mechanism.
We anticipate vendor tools and a wizard to be provided that would minimize hand insertions. The tools will use the standard declarations and, because these declarations are specified in a standard manner by JAXB, would be portable across implementations.
JAXB defines seven distinct custom declarations that can be annotated to XML schemas. Let us look at an example of how custom bindings can be used with the purchaseorder.xsd schema in Listing 13.1. We will walk down the different steps and also the specifics of the syntax for the declarations used.
When using inlined annotations to specify the custom bindings, the XML schema must include the namespace and version number defined in JAXB. The modified declaration for the purchase order is shown below, with modifications highlighted in bold:
The globalBindings declaration has global scope and is used to customize the bindings globally for all schemas. A schema can have only one such declaration, and the declaration affects all the imported and included schemas. The syntax for this declaration is shown below:
<globalBindings> [ collectionType = "collectionType" ] [ fixedAttributeAsConstantProperty= "true" | "false" | "1" | "0"] [ generateIsSetMethod= "true" | "false" | "1" | "0" ] [ enableFailFastCheck = "true" | "false" | "1" | "0" ] [ choiceContentProperty = "true" | "false" | "1" | "0" ] [ underscoreBinding = "asWordSeparator" | "asCharInWord" ] [ typesafeEnumBase = "typesafeEnumBase" ] [ typesafeEnumMemberName = "generateName" | "generateError" ] [ enableJavaNamingConventions = "true" | "false" | "1" | "0" ] [ modelGroupAsClass = "true" | "false" | "1" | "0" ] [ <javaType> . . . </javaType> ]* </globalBindings>
Previously we looked at how some elements, such as the Catalog, mapped to a java.util.List. A globally scoped globalBinding declaration can be used to specify the exact implementation the generated code may use:
<xsd:annotation> <xsd:appinfo> <jaxb:globalBindings collectionType="java.util.Vector"/> </xsd:appinfo> </xsd:annotation>
The schemaBindings declaration has schema scope and is used to customize the bindings for the specific schema. The syntax for this declaration is shown below:
<schemaBindings> <package [ name = "packageName" ] [ <javadoc> . . . </javadoc> ] </package> <nameXmlTransform> [ <typeName [ suffix="suffix" ] [ prefix="prefix" ] /> ] [ <elementName [ suffix="suffix" ] [ prefix="prefix" ] />] [ <modelGroupName [ suffix="suffix" ] [ prefix="prefix" ] />] [ <anonymousTypeName [ suffix="suffix" ] [ prefix="prefix" ] />] </nameXmlTransform> </schemaBindings>
The nameXmlTransform element in this declaration is specifically intended for use with UDDI 2.0, which contains many declarations that may cause duplicate names (or collisions) during generation of the Java bindings. For example, the following element in a UDDI schema will cause a fatal error:
<element name="bindingTemplate" type="uddi:bindingTemplate"/>
The nameXmlTransform element can be used to apply a suffix and prefix to such elements and avoid collisions.
In the original purchaseorder.xsd example, the Java code was generated by default in the com.flutebank.schema package corresponding to the target namespace http://www.flutebank.com/schema of the schema. This can be changed to com.flutebank.custompackage by using a schema scoped schemaBinding declaration:
This general-purpose declaration can be used to insert text that will appear as Javadoc comments in the generated Java code. It can be used only for component scope declaration. The syntax is:
<javadoc> Contents in Javadoc format </javadoc>
The sample usage of this declaration is shown below, along with the class declaration.
This declaration can be used to specify the name of the Java interface or the implementation class to use for a specific element. This will be useful for tool vendors and providers in specifying their own implementation classes rather than the default JAXB implementation code.
<class [ name = "className"]> [ implClass= "implClass" ] [ <javadoc> . . . </javadoc> ] </class>
In the original purchaseorder.xsd example, the billingaddress element was mapped to a Java interface named Billingaddress by default. This can be customized to the name PrimaryBillingAddress:
<xsd:element name="billingaddress"> <xsd:complexType> <xsd:annotation> <xsd:appinfo> <jaxb:class name="PrimaryBillingAddress"> <jaxb:javadoc> The custom Java interface corresponding to the billingaddress element in the schema </jaxb:javadoc> </jaxb:class> </xsd:appinfo> </xsd:annotation> <!--The rest of the orignal schema from Listing 13.1-->
Listing 13.6 shows the effect of these annotations on the generated code. The interface is named PrimaryBillingAddress, it is now in the package com.flutebank.custompackage, and the specified Javadoc comments precede the standard comments.
package com.flutebank.custompackage; /** * *The custom Java interface corresponding to the billingaddress element in the schema * Java content class for anonymous complex type. * <p>The following schema fragment specifies the expected content contained within this * java content object. * <p> * <pre> * <complexType> * <complexContent> * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType"> * <sequence> * <element ref="{http://www.flutebank.com/schema}name"/> * <element ref="{http://www.flutebank.com/schema}street"/> * <element ref="{http://www.flutebank.com/schema}city"/> * <element ref="{http://www.flutebank.com/schema}state"/> * <element ref="{http://www.flutebank.com/schema}zip"/> * </sequence> * </restriction> * </complexContent> * </complexType> * </pre> * */ public interface PrimaryBillingAddress { com.flutebank.custompackage.Name getName(); void setName(com.flutebank.custompackage.Name value); com.flutebank.custompackage.Zip getZip(); void setZip(com.flutebank.custompackage.Zip value); com.flutebank.custompackage.State getState(); void setState(com.flutebank.custompackage.State value); com.flutebank.custompackage.City getCity(); void setCity(com.flutebank.custompackage.City value); com.flutebank.custompackage.Street getStreet(); void setStreet(com.flutebank.custompackage.Street value); }
This declaration is used to customize the binding of an XML schema element to the Java representation as a property. The syntax for the property declaration is shown below:
<property [ name = "propertyName"] [ baseType = "propertyBaseType"] [ collectionType = "propertyCollectionType" ] [ fixedAttributeAsConstantProperty= "true" | "false" | "1" | "0"] [ generateIsSetMethod= "true" | "false" | "1" | "0" ] [ enableFailFastCheck="true" | "false" | "1" | "0" ] [ choiceContentProperty = "true" | "false" | "1" | "0" ] [ <javadoc> . . . </javadoc> ] </property>
The generateIsSetMethod customization is important because it causes two additional property methods, isSetXXX() and unsetXXX(), to be included in the generated code (where XXX is the property name). The application code can use these methods to distinguish between schema default values and values occurring explicitly within an instance document.
For example, the city and date elements are annotated in the original schema as shown below. This causes the name of the property in the Java code to be changed from city to billingcity and date to dateorderplaced. Had the original property been named Date, it might have caused a collision with the class java.util.Date during code generation, requiring a custom declaration.
<xsd:element name="city" type="xsd:string"> <xsd:annotation> <xsd:appinfo> <jaxb:property name="billingcity" generateIsSetMethod="true"/> </xsd:appinfo> </xsd:annotation> </xsd:element> <xsd:element name="date" type="xsd:string"> <xsd:annotation> <xsd:appinfo> <jaxb:property name="dateorderplaced"/> </xsd:appinfo> </xsd:annotation> </xsd:element>
The effect of these declarations can be seen in the code generated for the interface PrimaryBillingAddress, which now contains the following methods:
package com.flutebank.custompackage; public interface PrimaryBillingAddress { // Other Java code here not shown com.flutebank.custompackage.City getBillingcity(); void setBillingcity(com.flutebank.custompackage.City value); boolean isSetBillingcity(); void unsetBillingcity(); }
This declaration is used to customize the bindings of an XML schema data type to the corresponding Java data type. The Java data type can be a Java built-in data type or a custom-defined type (e.g., an application-defined class).
This declaration can be applied as an annotation to a specific element or included in the globalBinding declaration. The syntax for the javaType declaration is shown below:
<javaType name="javaType" [ xmlType="xmlType" ] [ parseMethod="parseMethod" ] [ printMethod="printMethod" ]>
In the original purchaseorder.xsd, the identifier element was defined as a string and therefore automatically mapped to a Java interface called Identifier, with getValue and setValue methods that treated the underlying property as a String. If it is known that this value will always be an integer, the default mapping can be overridden with the javaType declaration, as shown below:
<xsd:element name="identifier" type="xsd:string"> <xsd:annotation> <xsd:appinfo> <jaxb:javaType name="int"/> </xsd:appinfo> </xsd:annotation> </xsd:element>
The Java interface generated as a result of this annotation is:
package com.flutebank.custompackage; public interface Identifier extends javax.xml.bind.Element{ int getValue(); void setValue(int value); }
Earlier, we mentioned how an enumeration of base type "xs:NCName" maps to typesafe class. This binding declaration can be used to customize the generated bindings for simple type definitions with enumeration facets. The syntax is shown below:
<typesafeEnumClass name = "enumClassName"> <typesafeEnumMember name = "enumMemberName"> [ value = "enumMemberValue" ] [ <javadoc> enumMemberJavadoc </javadoc> ] </typesafeEnumMember> [ <javadoc> enumClassJavadoc </javadoc> ] </typesafeEnumClass>
Using typesafeEnums enables schema enumeration values to be mapped to Java constants. This optimizes performance by making it possible for constants to be compared, instead of string comparisons. To understand the difference, let us look at the sample schema discussed earlier in the "Enumerations" section:
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" > <xsd:element name="Day"> <xsd:simpleType> <xsd:restriction base="xsd:string"> <xsd:enumeration value="Saturday"/> <xsd:enumeration value="Sunday"/> <xsd:enumeration value="Monday"/> <xsd:enumeration value="Tuesday"/> <xsd:enumeration value="Wednesday"/> <xsd:enumeration value="Thursday"/> <xsd:enumeration value="Friday"/> </xsd:restriction> </xsd:simpleType> </xsd:element> </xsd:schema>
When compiled, this generates the Day interface, corresponding to the enumeration:
public interface Day extends javax.xml.bind.Element{ String getValue(); void setValue(String value); }
This same schema can be customized with the typesafeEnum declaration, as shown below:
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="1.0"> <xsd:element name="Day"> <xsd:simpleType> <xsd:annotation> <xsd:appinfo> <jaxb:typesafeEnumClass name="WeekDays"/> </xsd:appinfo> </xsd:annotation> <xsd:restriction base="xsd:string"> <xsd:enumeration value="Saturday"/> <xsd:enumeration value="Sunday"/> <xsd:enumeration value="Monday"/> <xsd:enumeration value="Tuesday"/> <xsd:enumeration value="Wednesday"/> <xsd:enumeration value="Thursday"/> <xsd:enumeration value="Friday"/> </xsd:restriction> </xsd:simpleType> </xsd:element> </xsd:schema>
When compiled, the Day interface now uses a type-safe class instead of strings:
public interface Day extends javax.xml.bind.Element{ generated.WeekDays getValue(); void setValue(generated.WeekDays value); }
Listing 13.7 also shows the generated class Weekdays. Notice the private constructor and the parse method.
public class WeekDays { public final static WeekDays SATURDAY = new WeekDays("Saturday"); private final static Object $$$_SATURDAY = "Saturday"; public final static WeekDays SUNDAY = new WeekDays("Sunday"); private final static Object $$$_SUNDAY = "Sunday"; public final static WeekDays MONDAY = new WeekDays("Monday"); private final static Object $$$_MONDAY = "Monday"; public final static WeekDays TUESDAY = new WeekDays("Tuesday"); private final static Object $$$_TUESDAY = "Tuesday"; public final static WeekDays WEDNESDAY = new WeekDays("Wednesday"); private final static Object $$$_WEDNESDAY = "Wednesday"; public final static WeekDays THURSDAY = new WeekDays("Thursday"); private final static Object $$$_THURSDAY = "Thursday"; public final static WeekDays FRIDAY = new WeekDays("Friday"); private final static Object $$$_FRIDAY = "Friday"; private final String value; private WeekDays(String v) { value = v; } public String toString() { return value; } public final int hashCode() { return super.hashCode(); } public final boolean equals(Object o) { return super.equals(o); } public static WeekDays parse(String str) { if ($$$_SATURDAY.equals(str)) { return SATURDAY; } if ($$$_SUNDAY.equals(str)) { return SUNDAY; } if ($$$_MONDAY.equals(str)) { return MONDAY; } if ($$$_TUESDAY.equals(str)) { return TUESDAY; } if ($$$_WEDNESDAY.equals(str)) { return WEDNESDAY; } if ($$$_THURSDAY.equals(str)) { return THURSDAY; } if ($$$_FRIDAY.equals(str)) { return FRIDAY; } return null; } }