Wednesday 14 November 2012

XML Schemas / XSD

Here are a few simple memory joggers for how to create xml schema (xsd) files.

Namespaces

Namespaces are important parts of xsds.  They control the prefixes and help to remove ambiguity if there are two types which have the same name.  They are also import if you want to using binding files to create java classes in different packages for different files.

Normally a targetNamespace is defined for a schema along with a xmlns command which refers to the same targetNamespace.  The targetNamespace says that this xsd defines this namespace.  The xmlns is the same for all items but because it is the same as the targetNamespace it is satisfied by this file (it refers to itself so no import is needed).  The targetNamespace doesn't have to be included but makes it harder to extend or split up the schema later.


  <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
      xmlns:conf="http://www.mycompany.com/myapp/config" targetNamespace="http://www.mycompany.com/myapp/config">


Note that the conf and targetNamespace are the same.

ComplexType

ComplexType is very common.  It defines a link between one object that can have multiple children.  An example of a ComplexType is

    <xs:complexType name="person">
        <xs:annotation>
            <xs:documentation>
                The details about a person.
            </xs:documentation>
        </xs:annotation>
        <xs:sequence>
            <xs:element name="FirstName" type="xs:string" minOccurs="1" maxOccurs="1" />
            <xs:element name="Surname" type="xs:string" minOccurs="1" maxOccurs="1" />
            <xs:element name="Age" type="xs:int" minOccurs="0" maxOccurs="1" />

        </xs:sequence>
    </xs:complexType>


The complexType can then be referenced in another object

    <xs:element name="staff" type="person" minOccurs="0" maxOccurs="unbounded" />

and in xml the person would look like

    <Person>
        <FirstName>Fred</FirstName>
        <Surname>Bloggs</Surname>
        <Age>43</Age>
    </Person>


Enumeration

If an enumeration is being used the don't make it an anonymous one.  If this happens then the xjc parser cannot turn it into a Java enumeration but instead does some horrible string effort.  Therefore, always make enumerations proper types.


    <xs:simpleType name="YesNo">
        <xs:annotation>
            <xs:documentation>Yes or No enumeration</xs:documentation>
        </xs:annotation>
        <xs:restriction base="xs:string">
            <xs:enumeration value="YES" />
            <xs:enumeration value="NO" />
        </xs:restriction>
    </xs:simpleType>


Restrictions need to have a 'base' that is the type of all the enumerations that it includes.

Extension

One object can extend another to include extra values.  For example, the person complexType defined about in the person object can be extended,

    <xs:complexType name="ExtendedPerson" >
        <xs:annotation>
            <xs:documentation>More people details</xs:documentation>
        </xs:annotation>
        <xs:complexContent>
            <xs:extension base="person">
                <xs:sequence>
                    <xs:element name="Salutation" type="SalutationType" minOccurs="1" maxOccurs="1" />
                </xs:sequence>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>

Where SalutationType is another enumeration.  Once Java classes have been generated the ExtendedPerson class extends the Person class with salutationType being the only local variable.  So in xml

    <ExtendedPerson>
        <FirstName>Fred</FirstName>
        <Surname>Bloggs</Surname>
        <Age>43</Age>
        <SalutationType>MR</SalutationType>
    </ExtendedPerson>


Import

Sometimes it is useful to have an import from one schema into another.  This allows types to be used from different schemas.  Firstly include the xmlns for the right target into the schema header as follows,

    xmlns:com="http://www.mycompany.com/myapp/common" 

Then import a schemaLocation which tallies up to this namespace.  The import command is,

    <xs:import namespace="http://www.mycompany.com/myapp/common" schemaLocation="common.xsd"/>


Here the common.xsd must have a targetNamespace as the same as the namespace in the import statement, in this case 'http://www.mycompany.com/myapp/common'.

Binding Files

Binding files (.xjb) can be used to simplify the xjc command and create java objects in different target packages.  To use a binding file the different xsds must have different namespaces.  A binding file which allows different packages to be used could be,

    <jxb:bindings xmlns:jxb="http://java.sun.com/xml/ns/jaxb" version="2.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" >

        <jxb:bindings namespace="http://www.mycompany.com/myapp/common" schemaLocation="common.xsd" >
            <jxb:schemaBindings>
                <jxb:package name="com.mycompany.myapp.common"/>
            </jxb:schemaBindings>
        </jxb:bindings>
    
        <jxb:bindings namespace="http://www.mycompany.com/myapp/config" schemaLocation="config.xsd" >
            <jxb:schemaBindings>
                <jxb:package name="com.mycompany.myapp.config"/>
            </jxb:schemaBindings>
        </jxb:bindings>
    </jxb:bindings>

The binding file can be referenced using the -b command in xjc.

GlobalBindings

It is possible to have a bespoke element that needs to be parsed in a particular way.  This can be done with a globalBinding such as,

    <jxb:globalBindings fixedAttributeAsConstantProperty="true">
        <jxb:javaType name="com.my.SpecialElement" xmlType="my:specialElement"
                      parseMethod="com.my.SpecialElementAdaptor.unmarshal"
                      printMethod="com.my.SpecialElementAdaptor.marshal">
        </jxb:javaType>
    </jxb:globalBindings>

This can also be used to format standard types such as dates, although this will be global across all the schemas and will result in xjc creating Adaptors in weird packages!

    <jxb:globalBindings fixedAttributeAsConstantProperty="true">
        <jxb:javaType name="java.util.Date" xmlType="xs:dateTime"
                      parseMethod="com.my.DateTimeAdaptor.unmarshal"
                      printMethod="com.my.DateTimeAdaptor.marshal">
        </jxb:javaType>
    </jxb:globalBindings>

SchemaBindings

In the situation where there are multiple schemas in a binding file this globalBinding is unlikely to be good enough (or even work at all).  Therefore, the binding can also be included in the schema part so that the binding only applies to one schema.

    <jxb:bindings namespace="http://www.my.com/common" schemaLocation="common.xsd" >
        <jxb:schemaBindings>
            <jxb:package name="com.my.common.generated"/>
        </jxb:schemaBindings>
        <jxb:bindings node="//xs:simpleType[@name='DeletionDateTime']">  
            <jxb:javaType name="java.util.Date"
  parseMethod="com.my.DateTimeAdaptor.unmarshal"
  printMethod="com.my.DateTimeAdaptor.marshal"/>  
        </jxb:bindings>
    </jxb:bindings>

This form uses an xPath to find the correct simpleType.

    node="//xs:simpleType[@name='DeletionDateTime']"

Note that // means search everywhere in the document for a xs:simpleType with name 'DeletionDateTime'.

Both the GlobalBindings and the SchemaBindings create Adaptors which are just named Adaptor#.  This can cause name clashes when you have a hierarchy of projects (jars) which means that you can end up not using the correct adaptor.

XJC Extension & Adaptors

A further option to allow a specific adaptor to be used is to specify the adaptor that you want to use.  This can be done using the xjc extension.  Firstly you need to include the namespace in the bindings file.
    <jxb:bindings  version="2.1" 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">

Next you can then use the xjc options.

    <jxb:bindings namespace="http://com.my/data" schemaLocation="my-data.xsd">
        <jxb:schemaBindings>
            <jxb:package name="com.my.data.generated" />
        </jxb:schemaBindings>
        
        <jxb:bindings node="//xs:complexType[@name='_Name_']/xs:sequence/xs:element[@name='_Date_']">
            <xjc:javaType name="org.joda.time.DateTime" adapter="com.my.data.util.JodaDateAdaptor" />    
        </jxb:bindings>
    </jxb:bindings>

xjc

xjc is bundled with java and is used to parse xsd and xjb files to create JAXB annotated java classes for a particular xml schema.  Example commands are

To parse a single file

    xjc -d myapp/java -p com.mycompany.myapp config.xsd

To parse multiple files

    xjc -d myapp/java -p com.mycompany.myapp *.xsd

To parse multiple files using a binding

    xjc -d myapp/java -b myapp.xjb *.xsd

-d = The route directory to create the classes in
-p = The package from the route directory
-b = refer to a binding file

No comments:

Post a Comment