Monday, February 6, 2012

JAX vs Extreme

http://blog.bdoughan.com/2010/10/how-does-jaxb-compare-to-xstream.html

How Does JAXB Compare to XStream?

The XStream FAQ states the following when being compared to JAXB:

JAXB is a Java binding tool. It generates Java code from a schema and you are able to transform from those classes into XML matching the processed schema and back. Note, that you cannot use your own objects, you have to use what is generated.
In this post I will provide an alternative answer to that question.


Java Model

We will use the following model for this example. The classes represent customer data. The get/set methods have been omitted to save space.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package comparison;
import java.util.ArrayList;
import java.util.List;
public class Customer {
private long id;
private String name;
private Address address;
private List<phonenumber> phoneNumbers;
public Customer() {
phoneNumbers = new ArrayList<PHONENUMBER>();
}
}
</PHONENUMBER>

1
2
3
4
5
6
7
8
package comparison;
public class Address {
private String city;
private String street;
}

1
2
3
4
5
6
7
8
package comparison;
public class PhoneNumber {
private String type;
private String number;
}

Customer Data

The following instance of Customer will be marshalled to XML using both JAXB and XStream.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package comparison;
public class Data {
public static Customer CUSTOMER;
static {
CUSTOMER = new Customer();
CUSTOMER.setId(123);
CUSTOMER.setName("Jane Doe");
Address address = new Address();
address.setStreet("1 A Street");
address.setCity("Any Town");
CUSTOMER.setAddress(address);
PhoneNumber workPhoneNumber = new PhoneNumber();
workPhoneNumber.setType("work");
workPhoneNumber.setNumber("555-WORK");
CUSTOMER.getPhoneNumbers().add(workPhoneNumber);
PhoneNumber cellPhoneNumber = new PhoneNumber();
cellPhoneNumber.setType("cell");
cellPhoneNumber.setNumber("555-CELL");
CUSTOMER.getPhoneNumbers().add(cellPhoneNumber);
}
}

Marshal Code

This is the code we will use to convert the objects to XML.

XStream

The following code will be used to marshal the instance of Customer to an OutputStream. The XStream code is quite compact.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package comparison.xstream;
import com.thoughtworks.xstream.XStream;
import static comparison.Data.CUSTOMER;
public class XStreamDemo {
public static void main(String[] args) {
XStream xstream = new XStream();
xstream.autodetectAnnotations(true);
xstream.toXML(CUSTOMER, System.out);
}
}

JAXB

The following code will be used to marshal the instance of Customer to an OutputStream. A couple of differences are already apparent:
  1. A JAXBContext needs to be initialized on the binding metadata before the marshal operation can occur.
  2. Unlike XStream JAXB does not format the XML by default, so we will enable this feature.
  3. With no metadata specified we need to supply JAXB with a root element name (and namespace).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package comparison.jaxb;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.namespace.QName;
import comparison.Customer;
import static comparison.Data.CUSTOMER;
public class JAXBDemo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Customer.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
JAXBElement<Customer> jaxbElement = new JAXBElement<Customer>(new QName("customer"), Customer.class, CUSTOMER);
marshaller.marshal(jaxbElement, System.out);
}
}

Default XML Output

First we will examine the XML output produced by both XStream and JAXB if no metadata is used to customize the output.

XStream

XStream will produce the following XML. Note some of the elements are named after the fully qualified class name.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<comparison.Customer>
<id>123</id>
<name>Jane Doe</name>
<address>
<city>Any Town</city>
<street>1 A Street</street>
</address>
<phoneNumbers>
<comparison.PhoneNumber>
<type>work</type>
<number>555-WORK</number>
</comparison.PhoneNumber>
<comparison.PhoneNumber>
<type>cell</type>
<number>555-CELL</number>
</comparison.PhoneNumber>
</phoneNumbers>
</comparison.Customer>
JAXB

JAXB produces the following XML. The representation is straight forward and more compact than the XStream representation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<customer>
<id>123</id>
<address>
<city>Any Town</city>
<street>1 A Street</street>
</address>
<name>Jane Doe</name>
<phoneNumbers>
<number>555-WORK</number>
<type>work</type>
</phoneNumbers>
<phoneNumbers>
<number>555-CELL</number>
<type>cell</type>
</phoneNumbers>
</customer>

Field Access

For this example we will configure our XML binding tools to interact directly with the fields (instance variables).

XStream

XStream uses field access by default.

JAXB

By default JAXB will access public fields and properties. We can configure JAXB to use field access with the following package level annotation:

1
2
3
4
5
@XmlAccessorType(XmlAccessType.FIELD)
package comparison;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;

Renaming Elements

Next we will look at how to tweak the XML output using the appropriate mapping metadata. First we will rename some elements. As you will see the amount of configuration required is almost identical.

XStream

For XStream we will use @XStreamAlias to configure the root element, and @XStreamImplicit to configure the phoneNumbers property.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.ArrayList;
import java.util.List;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
@XStreamAlias("customer")
public class Customer {
private long id;
private String name;
private Address address;
@XStreamImplicit(itemFieldName="phone-number")
private List<PHONENUMBER> phoneNumbers;
public Customer() {
phoneNumbers = new ArrayList<PHONENUMBER>();
}
}
</PHONENUMBER></PHONENUMBER>

JAXB

For JAXB we will use @XmlRootElement to configure the root element, and @XmlElement to configure the phoneNumbers property.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Customer {
private long id;
private String name;
private Address address;
@XmlElement(name="phone-number")
private List<PHONENUMBER> phoneNumbers;
public Customer() {
phoneNumbers = new ArrayList<PHONENUMBER>();
}
}
</PHONENUMBER></PHONENUMBER>

Note: The JAXB Marshal code can now be simplified since we no longer need to supply the root element information:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package comparison.jaxb;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import comparison.Customer;
import static comparison.Data.CUSTOMER;
public class JAXBDemo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Customer.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(CUSTOMER, System.out);
}
}

XML Output

At this point the same XML is being produced by XStream and JAXB.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<customer>
<id>123</id>
<name>Jane Doe</name>
<address>
<city>Any Town</city>
<street>1 A Street</street>
</address>
<phone-number>
<type>work</type>
<number>555-WORK</number>
</phone-number>
<phone-number>
<type>cell</type>
<number>555-CELL</number>
</phone-number>
</customer>

Change the Order of Elements

We will tweak the document again to make sure that when marshalling an Address object the "street" element will always appear before the "city" element.

XStream

For XStream we will need to change our marshal code. The following is based on instructions given in the XStream FAQ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package comparison.xstream;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.reflection.FieldDictionary;
import com.thoughtworks.xstream.converters.reflection.SortableFieldKeySorter;
import com.thoughtworks.xstream.converters.reflection.Sun14ReflectionProvider;
import comparison.Address;
import static comparison.Data.CUSTOMER;
public class XStreamDemo {
public static void main(String[] args) {
SortableFieldKeySorter sorter = new SortableFieldKeySorter();
sorter.registerFieldOrder(Address.class, new String[] { "street", "city"});
FieldDictionary fieldDictionary = new FieldDictionary(sorter);
Sun14ReflectionProvider reflectionProvider = new Sun14ReflectionProvider(fieldDictionary);
XStream xstream = new XStream(reflectionProvider);
xstream.autodetectAnnotations(true);
xstream.toXML(CUSTOMER, System.out);
}
}

JAXB

For JAXB we can configure the ordering of elements right on the model class. We will use @XmlType to do this.

1
2
3
4
5
6
7
8
9
10
11
package comparison;
import javax.xml.bind.annotation.XmlType;
@XmlType(propOrder={"street", "city"})
public class Address {
private String city;
private String street;
}

XML Output

The XML output is the same for both JAXB and XStream.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<customer>
<id>123</id>
<name>Jane Doe</name>
<address>
<street>1 A Street</street>
<city>Any Town</city>
</address>
<phone-number>
<type>work</type>
<number>555-WORK</number>
</phone-number>
<phone-number>
<type>cell</type>
<number>555-CELL</number>
</phone-number>
</customer>

Mapping to an Attribute

Now we will look at how to tweak the XML output using the appropriate mapping metadata to produce XML attributes. As you will see the amount of configuration required is almost identical.

XStream

For XStream we will use @XStreamAsAttribute to configure the id property to be represented as an XML attribute.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package comparison;
import java.util.ArrayList;
import java.util.List;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
@XStreamAlias("customer")
public class Customer {
@XStreamAsAttribute
private long id;
private String name;
private Address address;
@XStreamImplicit(itemFieldName="phone-number")
private List<PHONENUMBER> phoneNumbers;
public Customer() {
phoneNumbers = new ArrayList<PHONENUMBER>();
}
}
</PHONENUMBER></PHONENUMBER>

JAXB

For JAXB we will use @XmlAttribute to configure the id property to be represented as an XML attribute.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package comparison;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Customer {
@XmlAttribute
private long id;
private String name;
private Address address;
@XmlElement(name="phone-number")
private List<PHONENUMBER> phoneNumbers;
public Customer() {
phoneNumbers = new ArrayList<PHONENUMBER>();
}
}
</PHONENUMBER></PHONENUMBER>

XML Output

The XML output is the same for both JAXB and XStream.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<customer id="123">
<name>Jane Doe</name>
<address>
<city>Any Town</city>
<street>1 A Street</street>
</address>
<phone-number>
<type>work</type>
<number>555-WORK</number>
</phone-number>
<phone-number>
<type>cell</type>
<number>555-CELL</number>
</phone-number>
</customer>

Mapping Objects to Simple Content

To compact our document even further we will map the PhoneNumber class to a complex type with simple content.

XStream

With XStream we can do this using a converter. Examples of creating converters can be found on the XStream website:
We can reference the converter on the phoneNumbers property using @XStreamConverter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package comparison;
import java.util.ArrayList;
import java.util.List;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
import com.thoughtworks.xstream.annotations.XStreamConverter;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
@XStreamAlias("customer")
public class Customer {
@XmlAttribute
private long id;
private String name;
private Address address;
@XStreamImplicit(itemFieldName="phone-number")
@XStreamConverter(PhoneNumberConverter.class)
private List<PHONENUMBER> phoneNumbers;
public Customer() {
phoneNumbers = new ArrayList<PHONENUMBER>();
}
}
</PHONENUMBER></PHONENUMBER>

The converter can be implemented as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package comparison;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
public class PhoneNumberConverter implements Converter {
public boolean canConvert(Class clazz) {
return PhoneNumber.class == clazz;
}
public void marshal(Object object, HierarchicalStreamWriter hsw, MarshallingContext mc) {
PhoneNumber phoneNumber = (PhoneNumber) object;
hsw.addAttribute("type", phoneNumber.getType());
hsw.setValue(phoneNumber.getNumber());
}
public Object unmarshal(HierarchicalStreamReader hsr, UnmarshallingContext uc) {
PhoneNumber phoneNumber = new PhoneNumber();
phoneNumber.setType(hsr.getAttribute("type"));
phoneNumber.setNumber(hsr.getValue());
return phoneNumber;
}
}

JAXB

For JAXB we will use the @XmlAttribute and @XmlValue annotations on the PhoneNumber class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package comparison;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;
public class PhoneNumber {
@XmlAttribute
private String type;
@XmlValue
private String number;
}

XML Output:

The XML output is the same for both JAXB and XStream.

1
2
3
4
5
6
7
8
9
<customer id="123">
<name>Jane Doe</name>
<address>
<street>1 A Street</street>
<city>Any Town</city>
</address>
<phone-number type="work">555-WORK</phone-number>
<phone-number type="cell">555-CELL</phone-number>
</customer>

Applying Namespaces

We will now namespace qualify the XML document.

XStream

Namespace support in XStream appears to be limited to the StaxDriver. For more information see:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package comparison.xstream;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.reflection.FieldDictionary;
import com.thoughtworks.xstream.converters.reflection.SortableFieldKeySorter;
import com.thoughtworks.xstream.converters.reflection.Sun14ReflectionProvider;
import com.thoughtworks.xstream.io.xml.QNameMap;
import com.thoughtworks.xstream.io.xml.StaxDriver;
import comparison.Address;
import static comparison.Data.CUSTOMER;
public class XStreamDemo {
public static void main(String[] args) {
QNameMap nsm = new QNameMap();
nsm.setDefaultNamespace("http://www.example.com");
StaxDriver staxDriver = new StaxDriver(nsm);
SortableFieldKeySorter sorter = new SortableFieldKeySorter();
sorter.registerFieldOrder(Address.class, new String[] { "street", "city"});
FieldDictionary fieldDictionary = new FieldDictionary(sorter);
Sun14ReflectionProvider reflectionProvider = new Sun14ReflectionProvider(fieldDictionary);
XStream xstream = new XStream(reflectionProvider, staxDriver);
xstream.autodetectAnnotations(true);
xstream.toXML(CUSTOMER, System.out);
}
}

Note: With this change I lost XML formatting, the following was produced.

1
2
<customer xmlns="http://www.example.com" id="123"><name>Jane Doe</name><address><street>1 A Street</street><city>Any Town</city></address><phone-number type="work">555-WORK</phone-number><phone-number type="cell">555-CELL</phone-number></customer>

JAXB

We can configure the namespace information using the @XmlSchema package level annotation:
1
2
3
4
5
6
7
8
9
@XmlAccessorType(XmlAccessType.FIELD)
@XmlSchema(namespace="http://www.example.com",
elementFormDefault=XmlNsForm.QUALIFIED)
package comparison;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;

The following XML was produced

1
2
3
4
5
6
7
8
9
<customer xmlns="http://www.example.com" id="123">
<name>Jane Doe</name>
<address>
<street>1 A Street</street>
<city>Any Town</city>
</address>
<phone-number type="work">555-WORK</phone-number>
<phone-number type="cell">555-CELL</phone-number>
</customer>

No comments:

Post a Comment