Annotations Tutorial

The problem

Sometimes it gets ugly to call all those XStream aliases/register converter methods or you might simply like the new trend on configuring POJOs: java annotations.

This tutorial will show you how to use some of the annotations provided by XStream in order to make configuration easier. Let's start with a custom Message class:

package com.thoughtworks.xstream;
package com.thoughtworks.xstream;
public class RendezvousMessage {

	private int messageType;
	
	public RendezvousMessage(int messageType) {
		this.messageType = messageType;
	}
	
}

Let's code the xstream calls which generate the xml file:

package com.thoughtworks.xstream;
public class Tutorial {

	
	public static void main(String[] args) {
		XStream stream = new XStream(new DomDriver());
		RendezvousMessage msg = new RendezvousMessage(15);
		System.out.println(stream.toXML(msg));
	}

}

Results in the following XML:

<com.thoughtworks.xstream.RendezvousMessage>
  <messageType>15</messageType>
</com.thoughtworks.xstream.RendezvousMessage>

Aliasing annotations

The most basic annotation is the one responsible for type and field aliasing: @XStreamAlias. Let's annotate both our type and field and run the tutorial method again.

@XStreamAlias("message")
class RendezvousMessage {

	@XStreamAlias("type")
	private int messageType;
	
	public RendezvousMessage(int messageType) {
		this.messageType = messageType;
	}
	
}

In some strange way, the result is the same. What happened here? XStream does not read this annotation by default until now as it would be hard (impossible?) to unserialize the XML code. We need to tell XStream to read the annotations from this type:

	public static void main(String[] args) {
		XStream stream = new XStream(new DomDriver());
		Annotations.configureAliases(stream, RendezvousMessage.class);
		RendezvousMessage msg = new RendezvousMessage(15);
		System.out.println(stream.toXML(msg));
	}

Note that we have called the configureAliases static method in the Annotations class. This method registers all aliases annotations in the XStream instance passed as first argument. This method uses the var-args technique in order to provide a faster way to register many types. The resulting XML is what we have expected:

<message>
  <type>15</type>
</message>

Implicit collections

Let's add a List of content to our RendezvousMessage. We desire the same functionality obtained with implicit collections.

@XStreamAlias("message")
class RendezvousMessage {

	@XStreamAlias("type")
	private int messageType;        
	
	private List<String> content;
	
	public RendezvousMessage(int messageType, String ... content) {
		this.messageType = messageType;
		this.content = Arrays.asList(content);
	}
	
}
	public static void main(String[] args) {
		XStream stream = new XStream(new DomDriver());
		Annotations.configureAliases(stream, RendezvousMessage.class);
		RendezvousMessage msg = new RendezvousMessage(15, "firstPart","secondPart");
		System.out.println(stream.toXML(msg));
	}

The resulting XML shows the collection name before its elements:

<message>
  <type>15</type>
  <content class="java.util.Arrays$ArrayList">
    <a class="string-array">
      <string>firstPart</string>
      <string>secondPart</string>
    </a>
  </content>
</message>

This is not what we desire therefore we will annotate the content list to be recalled as an implicit collection:

@XStreamAlias("message")
class RendezvousMessage {

	@XStreamAlias("type")
	private int messageType;

	@XStreamImplicit
	private List<String> content;

	public RendezvousMessage(int messageType, String... content) {
		this.messageType = messageType;
		this.content = Arrays.asList(content);
	}

}

Resulting in an XML which ignores the field name (content):

<message>
  <type>15</type>
  <a class="string-array">
    <string>firstPart</string>
    <string>secondPart</string>
  </a>
</message>

We are almost there... we still want to remove the 'a' tag, and define each content part with the tag 'part'. In order to do so, let's add another attribute to our implicit collection annotation. The attribute field defines the name of the tag used for data contained inside this collection:

@XStreamAlias("message")
class RendezvousMessage {

	@XStreamAlias("type")
	private int messageType;

	@XStreamImplicit(itemFieldName="part")
	private List<String> content;

	public RendezvousMessage(int messageType, String... content) {
		this.messageType = messageType;
		this.content = Arrays.asList(content);
	}

}

Resulting in a cleaner XML:

<message>
  <type>15</type>
  <part>firstPart</part>
  <part>secondPart</part>
</message>

Custom converters

Let's create another attribute which defines the timestamp when the message was created:

@XStreamAlias("message")
class RendezvousMessage {

	@XStreamAlias("type")
	private int messageType;

	@XStreamImplicit(itemFieldName="part")
	private List<String> content;
	
	private Calendar created = new GregorianCalendar();

	public RendezvousMessage(int messageType, String... content) {
		this.messageType = messageType;
		this.content = Arrays.asList(content);
	}

}

Resulting in the following xml:

<message>
  <type>15</type>
  <part>firstPart</part>
  <part>secondPart</part>
  <created>
    <time>1154097812245</time>
    <timezone>America/Sao_Paulo</timezone>
  </created>
</message>

Now we face the following problem: we want to use a custom converter for this Calendar, but only for this Calendar, this exact field in this exact type. Easy... let's annotate it with the custom converter annotation:

@XStreamAlias("message")
class RendezvousMessage {

	@XStreamAlias("type")
	private int messageType;

	@XStreamImplicit(itemFieldName="part")
	private List<String> content;

	@XStreamConverter(SingleValueCalendarConverter.class)
	private Calendar created = new GregorianCalendar();

	public RendezvousMessage(int messageType, String... content) {
		this.messageType = messageType;
		this.content = Arrays.asList(content);
	}

}

Let's create the custom converter:

public class SingleValueCalendarConverter implements Converter {

    public void marshal(Object source, HierarchicalStreamWriter writer,
            MarshallingContext context) {
        Calendar calendar = (Calendar) source;
        writer.setValue(String.valueOf(calendar.getTime().getTime()));
    }

    public Object unmarshal(HierarchicalStreamReader reader,
            UnmarshallingContext context) {
        GregorianCalendar calendar = new GregorianCalendar();
        calendar.setTime(new Date(Long.parseLong(reader.getValue())));
        return calendar;
    }

    public boolean canConvert(Class type) {
        return type.equals(GregorianCalendar.class);
    }
}

And we end up with the converter being used and generating the following XML:

<message>
  <type>15</type>
  <part>firstPart</part>
  <part>secondPart</part>
  <created>1154097812245</created>
</message>

Note that the @XStreamConverter annotation on fields is automatically read, without the need for calling any Annotations static method. XStream can easily check for Java 1.5 support during runtime and load its parts accordingly.

If the client asks for the type tag to be an attribute inside the message tag, as follows:

<message type="15">
  <part>firstPart</part>
  <part>secondPart</part>
  <created>1154097812245</created>
</message>

All you need to do is add the @XStreamAsAttribute annotation:

@XStreamAlias("message")
class RendezvousMessage {

	@XStreamAlias("type")
   	@XStreamAsAttribute
	private int messageType;

	@XStreamImplicit(itemFieldName="part")
	private List<String> content;

	@XStreamConverter(SingleValueCalendarConverter.class)
	private Calendar created = new GregorianCalendar();

	public RendezvousMessage(int messageType, String... content) {
		this.messageType = messageType;
		this.content = Arrays.asList(content);
	}

}

Summing up

The XStream annotations support might help you configuring your class mappings in some ways, as the custom configuration will appear in your types, but might not be the solution for other problems, i.e. when you need to map the same type to two different xml 'standards'. Others might claim that the configuration should be clearly stated in a java class and not mixed with your model, its up to you to pick the best approach in your case: annotations or direct method calls to the XStream instance.