Java : JAXB 2.0 Java-to-Schema with annotations

This page last changed on Sep 18, 2006 by Kees de Kooter

Intro

Part of the JEE 5 stack is the JAXB 2.0 specification aka JSR-222. It is redesigned to work with Java 5 annotations. It also added the possibility of mapping POJOs to Schema.

Setting up the environment

First of all we need a JDK 5 with JEE 5 installed.

JSR-222 is a specification to be implemented by vendors. Fortunately Sun delivers a reference implementation. These are the current (September 2006) releases:

<dependency>
      <groupId>javax.xml</groupId>
      <artifactId>jaxb-api</artifactId>
      <version>2.0EA3</version>
    </dependency>
    <dependency>
      <groupId>javax.xml</groupId>
      <artifactId>jaxb-impl</artifactId>
      <version>2.0.2</version>
    </dependency>

Mapping to namespaces

To identify to JAXB which classes it should examine in every package a file named jaxb.index needs to be present. It contains a simple list with a classname per line. Package level annotations are placed in a file called package-info.java

The default namespace for a package can be defined with the @XmlSchema annotation. If we want to enforce all (child) tags to contain a namespace prefix we have to set the elementFormDefault attribute. Same goes for attributeFormDefault.

@XmlSchema(namespace="http://www.foo.com/bar", elementFormDefault=XmlNsForm.QUALIFIED)

Because this prefix is form an XML point of view just an identifier JAXB makes up default prefixes like ns1, ns2. If we want to use our own prefixes we can create a subclass of NamespacePrefixMapper that is available in the reference implementetion (Note that this is not part of the JSR-222 spec!!).

public class DefaultNamespacePrefixMapper extends NamespacePrefixMapper {

    @Override
    public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {

        String result = suggestion;

        if (namespaceUri == "http://www.foo.com/bar") {
            result = "bar";
        }

        if (namespaceUri == "http://www.foo.com/foo") {
            result = "foo";
        }

        return result;
    }

This mapper is loaded into the marshaller like this (see below for more about the marshaller).

marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper",
     new DefaultNamespacePrefixMapper());

Mapping elements

Class level

@XmlRootElement indicates that this class can be the root in an xml fragment @XmlType(namespace="http://www.foo.com/bar", propOrder={"one", "two"} defines the default namespace for this class and the order in which the fields will be marshalled.

Field level

@XmlTransient makes JAXB skip this property

Custom adapters

The default marshallers of JAXB try to map the standard types to their XML counterpart. If you want to do marshal to a custom format you have to define an adapter at either the class of the package level.

@XmlJavaTypeAdapter(value=DateAdapter.class, type=Date.class)

Here is an example of a special date format:

public class DateAdapter extends XmlAdapter<String,Date> {

    public static String DATE_PATTERN = "yyyyMMddHHmmssSSS";

    @Override
    public String marshal(Date date) throws Exception {
        String result = null;

        if (date != null) {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(
                    STUF_DATE_PATTERN);
            result = simpleDateFormat.format(date);
        }

        return result;
    }

    @Override
    public Date unmarshal(String string) throws Exception {
        Date result = null;

        if (!string.equals("")) {
            SimpleDateFormat dateFormatter = new SimpleDateFormat(
                    DATE_PATTERN);
            ParsePosition pp = new ParsePosition(0);
            result = dateFormatter.parse(string, pp);
        }

        return result;        
    }
}

Marshalling to XML

The actual marshalling is performed by - surprise - the Marshaller.

public void marshal(Object object) {

    try {
        // Create context instance for a : separated list of packages
        JAXBContext jaxbContext = JAXBContext.newInstance(
                "com.foo.bar:com.foo.foo");

        Marshaller marshaller = jaxbContext.createMarshaller();

        // Pretty print the output for debugging purposes
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
                  Boolean.TRUE);

        // NOTE: only works with the JAXB Reference Implementation of Sun
        marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper",
                new DefaultNamespacePrefixMapper());

        marshaller.marshal(object, System.out);
    } catch (JAXBException e) {
        log.error(e);
    }

}

Resources