Java: Using XMLAdapter to process custom datatypes using JAXB

JAXB provides a framework for two-way binding between XML and Java structures, making it easy for developers to serialize and deserialize their XML into Java objects.

Decorating a class with @XmlElement and @XmlAttribute annotations is all it usually takes to build a rich domain model from XML, but sometimes you get into a situation where more custom processing is required.

A custom XMLJavaTypeAdapter can help resolve these issues by providing custom methods for marshalling and unmarshalling the XML into the correct Java type.

Here are several examples of XMLJavaTypeAdapter usage:

  • An attribute containing a password string needs to be encrypted while on disk, but decrypted when loaded into the Java domain model
  • A string attribute value which represents a TRUE/FALSE boolean is sloppily represented by many different string values in the XML: “y”, “n”, “0”, “1”, “yes”, “no”, “n/a”, “”
  • A Base64 string value in an element represents an image, but needs to be converted to a byte array in the Java domain model
  • An attribute contains a local filename, this file needs to be read from disk and used as the true attribute value
  • There are a limited number of choices for an attribute, and you want to represent this as a Java enumeration

Sample Project

In this article I’ll be presenting a sample Java project that deserializes and serializes an address book, similar to what might be used in an instant messenger application to talk to your friends and contacts.

Each address book entry has two fields that need custom serialization:

  • The gender attribute string which needs to transformed to a Java enumeration type
  • The profile picture for a user in PNG format, which is base64 encoded in the XML but needs to be represented in Java as a byte array

A simple JUnit test exercises the serialization, from XML to Java objects and back to XML.

XML Explanation

An <addressbook> is the root object, containing <entries>.  Then each <entry> represents a person.  Each user has a name, an optionally specified gender, and optionally a small png image as their profile picture in Base64 format.

<addressbook>
<entries>
 <entry name="Amy" gender="F"/>
 <entry name="Bob">
 <avatarIcon type="png">iVBORw0KGg...U5ErkJggg==</avatarIcon>
 </entry>
 <entry name="Candice" gender="F">
 <avatarIcon type="gif">iVBORw0KGg...ORK5CYII=</avatarIcon>
 </entry>
 <entry name="David" gender="n/a"/>
 <entry name="Ernest" gender="m"/>
</entries>
</addressbook>

We will have JAXB turn this into a Java object graph where AddressBook is the main class, which contains a list of AddressEntry.

Each AddressEntry has a String value representing the name, a Gender class that contains a Java enumeration, and an AvatarIcon that contains an image byte array of their profile picture.

Notice that the ‘gender’ field is an XML attribute, whereas <avatarIcon> is an element.  We will create custom adapters for both these types.

Run Test

Before we go into the mechanics of adapters, let’s first download the sample project from github, package it, then run the JUnit test.

$ sudo apt-get install git openjdk-7-jdk maven -y
$ git clone https://github.com/fabianlee/xmladaptertest.git
$ cd xmladaptertest
$ mvn test

This should result with the following JUnit message toward the bottom:

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

With the XML being output in three different forms: text, XML, and then HTML.

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running xmladaptertest.TestReadXML
--TOSTRING----------
Address book:
 [name=Amy,gender=FEMALE,avatar=<null>]
 [name=Bob,gender=UNSPECIFIED,avatar=580 bytes]
 [name=Candice,gender=FEMALE,avatar=695 bytes]
 [name=David,gender=CHOOSE_NOT_TO_SPECIFY,avatar=<null>]
 [name=Ernest,gender=MALE,avatar=<null>]

The HTML version written to the console.

--HTML----------
<html><head/><body>
<h1>Address Book</h1>
<table width="400" border="1">
<tr><td>Amy</td><td>FEMALE</td><td>&nbsp;</td></tr>
<tr><td>Bob</td><td>UNSPECIFIED</td><td><img src="Bob.png"/></td></tr>
<tr><td>Candice</td><td>FEMALE</td><td><img src="Candice.png"/></td></tr>
<tr><td>David</td><td>CHOOSE_NOT_TO_SPECIFY</td><td>&nbsp;</td></tr>
<tr><td>Ernest</td><td>MALE</td><td>&nbsp;</td></tr>
</table>
</body></html>

The HTML is also written to a file named “addressbook.html” in the root project directory.  When opened with a browser it should look like the image below.

 

 

 

 

 

Custom Objects in object graph

Now let’s look at AddressEntry.java, which is where we want to apply custom binding logic to our Gender attribute and AvatarIcon element.

public class AddressEntry {
 
 @XmlAttribute
 protected String name;


 
 @XmlAttribute(required=false)  
@XmlJavaTypeAdapter(org.fabianlee.xmladaptertest.adapters.GenderEnumAdapter.class)
 protected Gender gender; 



@XmlElement(name="avatarIcon",required=false)
  protected AvatarIcon avatar;
Gender

The Gender class is annotated as an @XmlAttribute.  Then we annotate a the field level with @XmlJavaTypeAdapter and instruct JAXB that we want to use the GenerEnumAdapter.class for marshalling and unmarshalling this XML attribute.

GenderEnumAdapter as shown below extends XmlAdapter:

public class GenderEnumAdapter extends XmlAdapter<String,Gender>

And then has an overriden unmarshal() method, that tells JAXB how to take the String coming from the XML and turn it into a Gender object that has an enumeration representation.

@Override
    public Gender unmarshal( String value )
    {
    	if(null==value) {
    		return new Gender(GenderEnum.UNSPECIFIED);
    	}else if(value.equalsIgnoreCase("m") || value.equalsIgnoreCase("male")) {
    		return new Gender(GenderEnum.MALE);
    	}else if(value.equalsIgnoreCase("f") || value.equalsIgnoreCase("female")) {
    		return new Gender(GenderEnum.FEMALE);
    	}else {
    		return new Gender(GenderEnum.CHOOSE_NOT_TO_SPECIFY);
    	}
    }

Conversely, it also has an overriden marshal() method which tells JAXB how to take the Gender object and turn it back into a String for XML.

@Override
    public String marshal( Gender value )
    {
    	if(null==value) {
    		return GenderEnum.UNSPECIFIED.name();
    	}else {
    		return value.toString();
    	}
    }
AvatarIcon

The AvatarIcon class is annotated as an @XmlElement.  Notice that we did not annotate it with @XmlJavaTypeAdapter as above.  Instead, we have taken an alternate method and created a special class called package-info.java that specifies for a whole package that anytime a certain Java type is serialized, that a specific adapter should be used.

Inside package-info.java, you’ll see this line:

@javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter(value=org.fabianlee.xmladaptertest.adapters.AvatarIconAdapter.class,type=org.fabianlee.xmladaptertest.domain.AvatarIcon.class)

The AvatarIconAdapter extends XmlAdapter:

public class AvatarIconAdapter extends XmlAdapter<String,AvatarIcon>

And then has an overriden unmarshal() method, that tells JAXB how to take the String coming from the XML and turn it into an AvatarIcon object that contains a byte array representation of the image.

@Override
    public AvatarIcon unmarshal( String value )
    {
    	if(null==value) return null;
    	return new AvatarIcon(javax.xml.bind.DatatypeConverter.parseBase64Binary(value));
    }

Conversely, it also has an overriden marshal() method which tells JAXB how to take the Avataricon object and turn it back into a base64 string for XML.

@Override
    public String marshal( AvatarIcon value )
    {
    	if(null==value.getImage()) return null;
    	return javax.xml.bind.DatatypeConverter.printBase64Binary(value.getImage());
    }

 

REFERENCES

https://docs.oracle.com/javase/tutorial/jaxb/intro/arch.html (JAXB Arch)

https://docs.oracle.com/javase/8/docs/technotes/guides/xml/jaxb/index.html (JAXB tech nodes Java8)

https://javaee.github.io/jaxb-v2/doc/user-guide/ch03.html (User Guide)

https://area-51.blog/2010/01/04/binding-custom-objects-to-attributes-in-jaxb/ (custom XMLAdapter)

https://dzone.com/articles/jaxb-and-package-level (package-info.java)

http://blog.bdoughan.com/2010/12/jaxb-and-immutable-objects.html (XMLJavaTypeAdapter on class)

https://stackoverflow.com/questions/31344518/un-marshalling-base64-encoded-binary-data-as-stream (@XmlJavaTypeAdapter for Base64)

https://stackoverflow.com/questions/8499633/how-to-display-base64-images-in-html (embed base64 into HTML img tag)

https://github.com/tinosteinort/jaxb-examples/blob/master/pom.xml (xjc task from pom, schema to java)

https://www.iconfinder.com/icons/2698684/avatar_boy_max_icon#size=256 (base64 icons)