XML serialization and parsing support

Table of Contents
  1. XML support overview

    1. Example

  2. XmlSerializer class

    1. @Xml annotations

      1. @Xml.childName()

      2. @Xml.format()

      3. @Xml.contentHandler()

    2. Namespaces

      1. Auto-detection of namespaces

    3. @Bean and @BeanProperty annotations

    4. Collections

    5. XML-Schema support

    6. Non-tree models and recursion detection

    7. Configurable properties

    8. Other notes

  3. XmlParser class

    1. Parsing into generic POJO models

      1. Serializing with JSON-type attributes

    2. Configurable properties

    3. Other notes

  4. REST API support

    1. REST server support

      1. Using RestServletDefault

      2. Using RestServlet with annotations

      3. Using JAX-RS DefaultProvider

      4. Using JAX-RS BaseProvider with annotations

    2. REST client support

1 - XML support overview

Juneau supports converting arbitrary POJOs to and from XML using ultra-efficient serializers and parsers.
The XML serializer converts POJOs directly to XML without the need for intermediate DOM objects.
Likewise, the XML parser uses a STaX parser and creates POJOs directly without intermediate DOM objects.

Unlike frameworks such as JAXB, Juneau does not require POJO classes to be annotated to produce and consume XML.
For example, it can serialize and parse instances of any of the following POJO types:

In addition to the types shown above, Juneau includes the ability to define transforms to transform non-standard object and property types to serializable forms (e.g. to transform Calendars to and from ISO8601 strings, or byte[] arrays to and from base-64 encoded strings).
These transforms can be associated with serializers/parsers, or can be associated with classes or bean properties through type and method annotations.

Refer to POJO Categories for a complete definition of supported POJOs.

While annotations are not required to produce or consume XML, several XML annotations are provided for handling namespaces and fine-tuning the format of the XML produced.

Prerequisites

The Juneau XML serialization and parsing support does not require any external prerequisites. It only requires Java 1.6 or above.

1.1 - XML support overview - example

The example shown here is from the Address Book resource located in the org.apache.juneau.sample.war application.
The POJO model consists of a List of Person beans, with each Person containing zero or more Address beans.

When you point a browser at /sample/addressBook, the POJO is rendered as HTML:

By appending ?Accept=mediaType&plainText=true to the URL, you can view the data in the various supported XML formats:

Normal XML
Simplified XML

In addition to serializing POJOs to XML, Juneau includes support for serializing the POJO metamodel to XML Schema, with support for multiple namespaces.

XML Schema

2 - XmlSerializer class

{@link org.apache.juneau.xml.XmlSerializer} is the class used to convert POJOs to XML.
{@link org.apache.juneau.xml.XmlDocSerializer} is a subclass that adds an XML declaration element to the output before the POJO is serialized.

The XML serializer includes many configurable settings.
Static reusable instances of XML serializers are provided with commonly-used settings:

In addition, DTO beans are provided that use the XML serializer and parser for the following languages:

Refer to the package-level Javadocs for more information about those formats.

Notes about examples

The examples shown in this document will use single-quote, readable settings.
For brevity, the examples will use public fields instead of getters/setters to reduce the size of the examples.
In the real world, you'll typically want to use standard bean getters and setters.

To start off simple, we'll begin with the following simplified bean and build upon it.

public class Person { // Bean properties public int id; public String name; // Bean constructor (needed by parser) public Person() {} // Normal constructor public Person(int id, String name) { this.id = id; this.name = name; } }

The following code shows how to convert this to simple XML (no namespaces):

// Create a new serializer with readable output, no namespaces yet. XmlSerializer s = new XmlSerializer() .setProperty(SerializerContext.SERIALIZER_useIndentation, true) .setProperty(SerializerContext.SERIALIZER_quoteChar, '\'') .setProperty(XmlSerializerContext.XML_enableNamespaces, false); // Create our bean. Person p = new Person(1, "John Smith"); // Serialize the bean to XML. String xml = s.serialize(p);

Side note: Serializers can also be created by cloning existing serializers:

// Create a new serializer with readable output, no namespaces yet, but use cloning method. XmlSerializer s = XmlSerializer.DEFAULT_SQ_READABLE.clone() .setProperty(XmlSerializerContext.XML_enableNamespaces, false);

The code above produces the following output:

<object> <id>1</id> <name>John Smith</name> </object>

The first thing you may notice is how the bean instance is represented by the element <object>.
When objects have no name associated with them, Juneau provides a default generalized name that maps to the equivalent JSON data type.
Some cases when objects do not have names:

The generalized name reflects the JSON-equivalent data type.
The full list of generalized element names are:

Juneau produces JSON-equivalent XML, meaning any valid JSON document can be losslessly converted into an XML equivalent.
In fact, all of the Juneau serializers and parsers are built upon this JSON-equivalency.

2.1 - @Xml annotations

Just because Juneau allows you to serialize ordinary POJOs to XML doesn't mean you are limited to just JSON-equivalent XML.
Several annotations are provided in the {@link org.apache.juneau.xml.annotation} package for customizing the output.

2.1.1 - @Bean.typeName()

The {@link org.apache.juneau.annotation.Bean#typeName()} annotation can be used to override the Juneau default name on unnamed objects.

Example

@Bean(typeName="person") public class Person { ...

Result

<person> <id>1</id> <name>John Smith</name> </person>

2.1.2 - @Xml.childName()

The {@link org.apache.juneau.xml.annotation.Xml#childName()} annotation can be used to specify the name of XML child elements for bean properties of type collection or array.

Example

public class MyBean { @Xml(childName="child"} public String[] children = {"foo","bar"}; }

Results without annotation

<object> <children> <string>foo</string> <string>bar</string> </children> </object>

Results with annotation

<object> <children> <child>foo</child> <child>bar</child> </children> </object>

2.1.3 - @Xml.format()

The {@link org.apache.juneau.xml.annotation.Xml#format()} annotation can be used to tweak the XML format of a POJO.
The value is set to an enum value of type {@link org.apache.juneau.xml.annotation.XmlFormat}.
This annotation can be applied to both classes and bean properties.

Possible values
  • {@link org.apache.juneau.xml.annotation.XmlFormat#NORMAL} - Normal formatting (default).
  • {@link org.apache.juneau.xml.annotation.XmlFormat#ATTR} - Render as an XML attribute when it would normally be rendered as an element.
    Can be applied to classes and properties that serialize to simple types (e.g. String, Number).
  • {@link org.apache.juneau.xml.annotation.XmlFormat#ELEMENT} - Render as an XML element when it would normally be rendered as an attribute.
    Can be applied to URL and ID bean properties that would normally be rendered as attributes.
  • {@link org.apache.juneau.xml.annotation.XmlFormat#COLLAPSED} - Prevents collections and arrays from being enclosed in an <array> (or named equivalent) element.
    Can be applied to properties of type collection or array, or to classes that subclass from Collection.
  • {@link org.apache.juneau.xml.annotation.XmlFormat#CONTENT} - Render property value directly as content of element.
    Can be used in combination with {@link org.apache.juneau.xml.annotation.Xml#contentHandler()} to produce something other than plain text, such as embedded XML.
Example

public class MyBean { // Normally, bean properties would be rendered as child elements of the bean element. // Override so that it's rendered as a "f1='123'" attribute on the bean element instead. @Xml(format=XmlFormat.ATTR} public int f1 = 123; // Normally, bean URL properties would be rendered as XML attributes on the bean element. // Override so that it's rendered as an <href>http://foo</href> child element instead. @BeanProperty(uri=true) @Xml(format=XmlFormat.ELEMENT} public URL href = new URL("http://foo"); // Normally, collection properties would be grouped under a single <children> child element on the bean element. // Override so that entries are directly children of the bean element with each entry having an element name of <child>. @Xml(format=XmlFormat.COLLAPSED, childName="child"} public String[] children = "foo","bar"}; }

Results without annotation

<object href='http://foo'> <f1>123</f1> <children> <string>foo</string> <string>bar</string> </children> </object>

Results with annotation

<object f1='123'> <href>http://foo</href> <child>foo</child> <child>bar</child> </object>

The {@link org.apache.juneau.xml.annotation.XmlFormat#CONTENT} annotation can be used to serialize a bean straight to text without any child elements.

Example

@Bean(typeName="MyBean") public class MyBean { @Xml(format=XmlFormat.CONTENT) public String beanContents = "This is my bean"; }

Results

<MyBean> This is my bean </MyBean>

There are some restrictions when using the CONTENT format:

  • A bean class can only have one property denoted with CONTENT format.
  • A bean class cannot have any other properties that would serialize to an element (attributes are okay).

The class type on the property can be anything that serializes to a simple value, like a String or Number.
Note that transforms can usually be used to convert more complex class types to simple types.

2.1.4 - @Xml.contentHandler()

The {@link org.apache.juneau.xml.annotation.Xml#contentHandler()} annotation is an advanced feature that allows you to define your own code for serializing and parsing the contents of a serialized bean.

For example, the ATOM specification allows for the media type of Text elements to be specified via a type attribute:

<feed> <entry> <title type='text'> A &lt;em&gt;really&lt;/em&gt; great title </title> <content type='xhtml'> <div xmlns="http://www.w3.org/1999/xhtml"><p><i>This is the contents of this entry.</i></p></div> </content> </entry> </feed>

To accomplish this, the {@link org.apache.juneau.dto.atom.Text} class uses a content handler that serializes and parses the value by inspecting the type value and then handling the content accordingly:

public class Text extends Common { private String type; private String text; @Xml(format=ATTR) public String getType() { return type; } public Text setType(String type) { this.type = type; return this; } @Xml(format=CONTENT, contentHandler=TextContentHandler.class) public String getText() { return text; } public Text setText(String text) { this.text = text; return this; } // Converts the text to and from the appropriate media type. public class TextContentHandler implements XmlContentHandler<Text> { @Override public void parse(XMLStreamReader r, Text text) throws Exception { String type = text.type; if (type != null && type.equals("xhtml")) text.text = decode(readXmlContents(r).trim()); else text.text = decode(r.getElementText().trim()); } @Override public void serialize(XmlSerializerWriter w, Text text) throws Exception { String type = text.type; String content = text.text; if (type != null && type.equals("xhtml")) w.encodeTextInvalidChars(content); else w.encodeText(content); } } }

Refer to {@link org.apache.juneau.xml.XmlContentHandler} for more information.

2.2 - Namespaces

Let's go back to the example of our original Person bean class:

public class Person { // Bean properties public int id; public String name; // Bean constructor (needed by parser) public Person() {} // Normal constructor public Person(int id, String name) { this.id = id; this.name = name; } }

However, this time we'll leave namespaces enabled on the serializer:

// Create a new serializer with readable output, this time with namespaces enabled. XmlSerializer s = new XmlSerializer() .setProperty(SerializerContext.SERIALIZER_useIndentation, true) .setProperty(SerializerContext.SERIALIZER_quoteChar, '\''); // Create our bean. Person p = new Person(1, "John Smith"); // Serialize the bean to XML. String xml = s.serialize(p);

Now when we run this code, we'll see namespaces added to our output:

<object xmlns='http://www.apache.org/2013/Juneau' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'> <id>1</id> <name>John Smith</name> </object>

This isn't too exciting yet since we haven't specified any namespaces yet.
Therefore, everything is defined under the default Juneau namespace.
The xsi namespace is always present and is used to mark null property values with nil='true'.

Namespaces can be defined at the following levels:

It's typically best to specify the namespaces used at the package level.
We'll do that here for the package containing our test code.

// XML namespaces used in this package @XmlSchema( prefix="ab", xmlNs={ @XmlNs(prefix="ab", namespaceURI="http://www.apache.org/addressBook/"), @XmlNs(prefix="per", namespaceURI="http://www.apache.org/person/"), @XmlNs(prefix="addr", namespaceURI="http://www.apache.org/address/"), @XmlNs(prefix="mail", namespaceURI="http://www.apache.org/mail/") } ) package org.apache.juneau.samples.addressbook; import org.apache.juneau.xml.annotation.*;

We're defining four namespaces in this package and designating "http://www.apache.org/addressBook/" as the default namespace for all classes and properties within this package.

Take special note that the @XmlSchema is modelled after the equivalent JAXB annotation, but is defined in the {@link org.apache.juneau.xml.annotation} package.
Other XML annotations are also modelled after JAXB. However, since many of the features of JAXB are already implemented for all serializers and parsers at a higher level through various general annotations such as {@link org.apache.juneau.annotation.Bean} and {@link org.apache.juneau.annotation.BeanProperty} it was decided to maintain separate Juneau XML annotations instead of reusing JAXB annotations.
This may change in some future implementation, but for now it was decided that having separate Juneau XML annotations was less confusing.

On our bean class, we'll specify to use the "http://www.apache.org/person/" namespace:

@Xml(prefix="per") @Bean(typeName="person") public class Person { ...

Now when we serialize the bean, we get the following:

<per:person xmlns='http://www.apache.org/2013/Juneau' xmlns:per='http://www.apache.org/person/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'> <per:id>1</per:id> <per:name>John Smith</per:name> </per:person>

We can simplify the output by setting the default namespace on the serializer so that all the elements do not need to be prefixed:

// Create a new serializer with readable output, this time with namespaces enabled. XmlSerializer s = new XmlSerializer() .setProperty(SerializerContext.SERIALIZER_useIndentation, true) .setProperty(SerializerContext.SERIALIZER_quoteChar, '\'') .setProperty(XmlSerializerContext.XML_defaultNamespaceUri, "http://www.apache.org/person/");

This produces the following equivalent where the elements don't need prefixes since they're already in the default document namespace:

<person xmlns='http://www.apache.org/person/' xmlns:juneau='http://www.apache.org/2013/Juneau' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'> <id>1</id> <name>John Smith</name> </person>

2.2.1 - Auto-detection of namespaces

One important property on the XML serializer class is {@link org.apache.juneau.xml.XmlSerializerContext#XML_autoDetectNamespaces XML_autoDetectNamespaces}.
This property tells the serializer to make a first-pass over the data structure to look for namespaces defined on classes and bean properties.
In high-performance environments, you may want to consider disabling auto-detection and providing your own explicit list of namespaces to the serializer to avoid this scanning step.

The following code will produce the same output as before, but will perform slightly better since it avoids this prescan step.

// Create a new serializer with readable output, this time with namespaces enabled. XmlSerializer s = new XmlSerializer() .setProperty(SerializerContext.SERIALIZER_useIndentation, true) .setProperty(SerializerContext.SERIALIZER_quoteChar, '\'') .setProperty(XmlSerializerContext.XML_autoDetectNamespaces, false) .setProperty(XmlSerializerContext.XML_namespaces, "{per:'http://www.apache.org/person/'}");

2.3 - @Bean and @BeanProperty annotations

The {@link org.apache.juneau.annotation.Bean @Bean} and {@link org.apache.juneau.annotation.BeanProperty @BeanProperty} annotations are used to customize the behavior of beans across the entire framework.
In addition to using them to identify the resource URI for the bean shown above, they have various other uses:

For example, we now add a birthDate property, and associate a transform with it to transform it to an ISO8601 date-time string in GMT time.
By default, Calendars are treated as beans by the framework, which is usually not how you want them serialized.
Using transforms, we can convert them to standardized string forms.

@Xml(prefix="per") @Bean(typeName="person") public class Person { // Bean properties @BeanProperty(swap=CalendarSwap.ISO8601DTZ.class) public Calendar birthDate; ... // Normal constructor public Person(int id, String name, String uri, String addressBookUri, String birthDate) throws Exception { ... this.birthDate = new GregorianCalendar(); this.birthDate.setTime(DateFormat.getDateInstance(DateFormat.MEDIUM).parse(birthDate)); } }

Next, we alter our code to pass in the birthdate:

// Create our bean. Person p = new Person(1, "John Smith", "http://sample/addressBook/person/1", "http://sample/addressBook", "Aug 12, 1946");

Now when we rerun the sample code, we'll get the following:

<per:person xmlns='http://www.apache.org/2013/Juneau' xmlns:per='http://www.apache.org/person/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' uri='http://sample/addressBook/person/1'> <per:id>1</per:id> <per:name>John Smith</per:name> <per:addressBookUri>http://sample/addressBook</per:addressBookUri> <per:birthDate>1946-08-12T00:00:00Z</per:birthDate> </per:person>

Another useful feature is the {@link org.apache.juneau.annotation.Bean#propertyNamer()} annotation that allows you to plug in your own logic for determining bean property names.
The {@link org.apache.juneau.PropertyNamerDashedLC} is an example of an alternate property namer. It converts bean property names to lowercase-dashed format.

Example

@Xml(prefix="per") @Bean(typeName="person",propertyNamer=PropertyNamerDashedLC.class) public class Person { ...

Results

<per:person xmlns='http://www.apache.org/2013/Juneau' xmlns:per='http://www.apache.org/person/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' uri='http://sample/addressBook/person/1'> <per:id>1</per:id> <per:name>John Smith</per:name> <per:address-book-uri>http://sample/addressBook</per:address-book-uri> <per:birth-date>1946-08-12T04:00:00Z</per:birth-date> </per:person>

2.4 - Collections

In our example, let's add a list-of-beans property to our sample class:

public class Person { // Bean properties public LinkedList<Address> addresses = new LinkedList<Address>(); ... }

The Address class has the following properties defined:

@Xml(prefix="addr") @Bean(typeName="address") public class Address { // Bean properties @Xml(format=ATTR) public URI uri; public URI personUri; public int id; @Xml(prefix="mail") public String street, city, state; @Xml(prefix="mail") public int zip; public boolean isCurrent; }

Next, add some quick-and-dirty code to add an address to our person bean:

// Create a new serializer with readable output. XmlSerializer s = new XmlSerializer() .setProperty(SerializerContext.SERIALIZER_useIndentation, true) .setProperty(SerializerContext.SERIALIZER_quoteChar, '\''); // Create our bean. Person p = new Person(1, "John Smith", "http://sample/addressBook/person/1", "http://sample/addressBook", "Aug 12, 1946"); Address a = new Address(); a.uri = new URI("http://sample/addressBook/address/1"); a.personUri = new URI("http://sample/addressBook/person/1"); a.id = 1; a.street = "100 Main Street"; a.city = "Anywhereville"; a.state = "NY"; a.zip = 12345; a.isCurrent = true; p.addresses.add(a);

Now when we run the sample code, we get the following:

<per:person xmlns='http://www.apache.org/2013/Juneau' xmlns:per='http://www.apache.org/person/' xmlns:addr='http://www.apache.org/address/' xmlns:mail='http://www.apache.org/mail/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' uri='http://sample/addressBook/person/1'> <per:id>1</per:id> <per:name>John Smith</per:name> <per:addressBookUri>http://sample/addressBook</per:addressBookUri> <per:birthDate>1946-08-12T04:00:00Z</per:birthDate> <per:addresses> <addr:address uri='http://sample/addressBook/address/1'> <addr:personUri>http://sample/addressBook/person/1</addr:personUri> <addr:id>1</addr:id> <mail:street>100 Main Street</mail:street> <mail:city>Anywhereville</mail:city> <mail:state>NY</mail:state> <mail:zip>12345</mail:zip> <addr:isCurrent>true</addr:isCurrent> </addr:address> </per:addresses> </per:person>

2.5 - XML-Schema support

Juneau provides the {@link org.apache.juneau.xml.XmlSchemaSerializer} class for generating XML-Schema documents that describe the output generated by the {@link org.apache.juneau.xml.XmlSerializer} class.
This class shares the same properties as XmlSerializer.
Since the XML output differs based on settings on the XML serializer class, the XML-Schema serializer class must have the same property values as the XML serializer class it's describing.
To help facilitate creating an XML Schema serializer with the same properties as the corresponding XML serializer, the {@link org.apache.juneau.xml.XmlSerializer#getSchemaSerializer()} method has been added.

XML-Schema requires a separate file for each namespace.
Unfortunately, does not mesh well with the Juneau serializer architecture which serializes to single writers.
To get around this limitation, the schema serializer will produce a single output, but with multiple schema documents separated by the null character ('\u0000') to make it simple to split apart.

Lets start with an example where everything is in the same namespace.
We'll use the classes from before, but remove the references to namespaces.
Since we have not defined a default namespace, everything is defined under the default Juneau namespace.

@Bean(typeName="person") public class Person { // Bean properties public int id; public String name; @Xml(format=ATTR) public URI uri; public URI addressBookUri; @BeanProperty(swap=CalendarSwap.ISO8601DTZ.class) public Calendar birthDate; public LinkedList<Address> addresses = new LinkedList<Address>(); // Bean constructor (needed by parser) public Person() {} // Normal constructor public Person(int id, String name, String uri, String addressBookUri, String birthDate) throws Exception { this.id = id; this.name = name; this.uri = new URI(uri); this.addressBookUri = new URI(addressBookUri); this.birthDate = new GregorianCalendar(); this.birthDate.setTime(DateFormat.getDateInstance(DateFormat.MEDIUM).parse(birthDate)); } } @Bean(typeName="address") public class Address { // Bean properties @Xml(format=ATTR) public URI uri; public URI personUri; public int id; public String street, city, state; public int zip; public boolean isCurrent; }

The code for creating our POJO model and generating XML Schema is shown below:

// Create a new serializer with readable output. XmlSerializer s = new XmlSerializer() .setProperty(SerializerContext.SERIALIZER_useIndentation, true) .setProperty(XmlSerializerContext.XML_enableNamespaces, true) .setProperty(XmlSerializerContext.XML_addNamespaceUrisToRoot, true) .setProperty(SerializerContext.SERIALIZER_quoteChar, '\''); // Create the equivalent schema serializer. XmlSchemaSerializer ss = s.getSchemaSerializer(); // Create our bean. Person p = new Person(1, "John Smith", "http://sample/addressBook/person/1", "http://sample/addressBook", "Aug 12, 1946"); Address a = new Address(); a.uri = new URI("http://sample/addressBook/address/1"); a.personUri = new URI("http://sample/addressBook/person/1"); a.id = 1; a.street = "100 Main Street"; a.city = "Anywhereville"; a.state = "NY"; a.zip = 12345; a.isCurrent = true; p.addresses.add(a); // Serialize the bean to XML. String xml = s.serialize(p); // Get the XML Schema corresponding to the XML generated above. String xmlSchema = ss.serialize(p);

XML results

<person xmlns='http://www.apache.org/2013/Juneau' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' uri='http://sample/addressBook/person/1'> <id>1</id> <name>John Smith</name> <addressBookUri>http://sample/addressBook</addressBookUri> <birthDate>1946-08-12T00:00:00Z</birthDate> <addresses> <address uri='http://sample/addressBook/address/1'> <personUri>http://sample/addressBook/person/1</personUri> <id>1</id> <street>100 Main Street</street> <city>Anywhereville</city> <state>NY</state> <zip>12345</zip> <isCurrent>true</isCurrent> </address> </addresses> </person>

XML-Schema results

<schema xmlns='http://www.w3.org/2001/XMLSchema' targetNamespace='http://www.apache.org/2013/Juneau' elementFormDefault='qualified' xmlns:juneau='http://www.apache.org/2013/Juneau'> <element name='person' type='juneau:org.apache.juneau.samples.addressbook.Person'/> <complexType name='org.apache.juneau.samples.addressbook.Person'> <sequence> <element name='id' type='integer' minOccurs='0'/> <element name='name' type='string' minOccurs='0'/> <element name='addressBookUri' type='string' minOccurs='0'/> <element name='birthDate' type='juneau:java.util.Calendar' minOccurs='0'/> <element name='addresses' type='juneau:java.util.LinkedList_x003C_org.apache.juneau.samples.addressbook.Address_x003E_' minOccurs='0'/> </sequence> <attribute name='uri' type='string'/> </complexType> <complexType name='java.util.Calendar'> <sequence> <any processContents='skip' maxOccurs='unbounded' minOccurs='0'/> </sequence> </complexType> <complexType name='java.util.LinkedList_x003C_org.apache.juneau.samples.addressbook.Address_x003E_'> <sequence> <choice minOccurs='0' maxOccurs='unbounded'> <element name='address' type='juneau:org.apache.juneau.samples.addressbook.Address'/> <element name='null' type='string'/> </choice> </sequence> </complexType> <complexType name='org.apache.juneau.samples.addressbook.Address'> <sequence> <element name='personUri' type='string' minOccurs='0'/> <element name='id' type='integer' minOccurs='0'/> <element name='street' type='string' minOccurs='0'/> <element name='city' type='string' minOccurs='0'/> <element name='state' type='string' minOccurs='0'/> <element name='zip' type='integer' minOccurs='0'/> <element name='isCurrent' type='boolean' minOccurs='0'/> </sequence> <attribute name='uri' type='string'/> </complexType> </schema>

Now if we add in some namespaces, we'll see how multiple namespaces are handled.

@Xml(prefix="per") @Bean(typeName="person") public class Person { ... } @Xml(prefix="addr") @Bean(typeName="address") public class Address { ... @Xml(prefix="mail") public String street, city, state; @Xml(prefix="mail") public int zip; ... }

XML results

<per:person xmlns='http://www.apache.org/2013/Juneau' xmlns:per='http://www.apache.org/person/' xmlns:addr='http://www.apache.org/address/' xmlns:mail='http://www.apache.org/mail/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' uri='http://sample/addressBook/person/1'> <per:id>1</per:id> <per:name>John Smith</per:name> <per:addressBookUri>http://sample/addressBook</per:addressBookUri> <per:birthDate>1946-08-12T00:00:00Z</per:birthDate> <per:addresses> <addr:address uri='http://sample/addressBook/address/1'> <addr:personUri>http://sample/addressBook/person/1</addr:personUri> <addr:id>1</addr:id> <mail:street>100 Main Street</mail:street> <mail:city>Anywhereville</mail:city> <mail:state>NY</mail:state> <mail:zip>12345</mail:zip> <addr:isCurrent>true</addr:isCurrent> </addr:address> </per:addresses> </per:person>

The schema consists of 4 documents separated by a '\u0000' character.

XML-Schema results

<schema xmlns='http://www.w3.org/2001/XMLSchema' targetNamespace='http://www.apache.org/2013/Juneau' elementFormDefault='qualified' xmlns:juneau='http://www.apache.org/2013/Juneau' xmlns:per='http://www.apache.org/person/' xmlns:addr='http://www.apache.org/address/' xmlns:mail='http://www.apache.org/mail/'> <import namespace='http://www.apache.org/person/' schemaLocation='per.xsd'/> <import namespace='http://www.apache.org/address/' schemaLocation='addr.xsd'/> <import namespace='http://www.apache.org/mail/' schemaLocation='mail.xsd'/> <complexType name='int'> <simpleContent> <extension base='integer'/> </simpleContent> </complexType> <complexType name='java.lang.String'> <simpleContent> <extension base='string'/> </simpleContent> </complexType> <complexType name='java.net.URI'> <simpleContent> <extension base='string'/> </simpleContent> </complexType> <complexType name='java.util.Calendar'> <sequence> <any processContents='skip' maxOccurs='unbounded' minOccurs='0'/> </sequence> </complexType> <complexType name='java.util.LinkedList_x003C_org.apache.juneau.samples.addressbook.Address_x003E_'> <sequence> <choice minOccurs='0' maxOccurs='unbounded'> <element name='address' type='addr:org.apache.juneau.samples.addressbook.Address'/> <element name='null' type='string'/> </choice> </sequence> </complexType> <complexType name='boolean'> <simpleContent> <extension base='boolean'/> </simpleContent> </complexType> </schema> [\u0000] <schema xmlns='http://www.w3.org/2001/XMLSchema' targetNamespace='http://www.apache.org/person/' elementFormDefault='qualified' attributeFormDefault='qualified' xmlns:juneau='http://www.apache.org/2013/Juneau' xmlns:per='http://www.apache.org/person/' xmlns:addr='http://www.apache.org/address/' xmlns:mail='http://www.apache.org/mail/'> <import namespace='http://www.apache.org/2013/Juneau' schemaLocation='juneau.xsd'/> <import namespace='http://www.apache.org/address/' schemaLocation='addr.xsd'/> <import namespace='http://www.apache.org/mail/' schemaLocation='mail.xsd'/> <element name='person' type='per:org.apache.juneau.samples.addressbook.Person'/> <complexType name='org.apache.juneau.samples.addressbook.Person'> <sequence> <any minOccurs='0' maxOccurs='unbounded'/> </sequence> <attribute name='uri' type='string'/> </complexType> <element name='id' type='juneau:int'/> <element name='name' type='juneau:java.lang.String'/> <element name='addressBookUri' type='juneau:java.net.URI'/> <element name='birthDate' type='juneau:java.util.Calendar'/> <element name='addresses' type='juneau:java.util.LinkedList_x003C_org.apache.juneau.samples.addressbook.Address_x003E_'/> </schema> [\u0000] <schema xmlns='http://www.w3.org/2001/XMLSchema' targetNamespace='http://www.apache.org/address/' elementFormDefault='qualified' attributeFormDefault='qualified' xmlns:juneau='http://www.apache.org/2013/Juneau' xmlns:per='http://www.apache.org/person/' xmlns:addr='http://www.apache.org/address/' xmlns:mail='http://www.apache.org/mail/'> <import namespace='http://www.apache.org/2013/Juneau' schemaLocation='juneau.xsd'/> <import namespace='http://www.apache.org/person/' schemaLocation='per.xsd'/> <import namespace='http://www.apache.org/mail/' schemaLocation='mail.xsd'/> <complexType name='org.apache.juneau.samples.addressbook.Address'> <sequence> <any minOccurs='0' maxOccurs='unbounded'/> </sequence> <attribute name='uri' type='string'/> </complexType> <element name='personUri' type='juneau:java.net.URI'/> <element name='id' type='juneau:int'/> <element name='isCurrent' type='juneau:boolean'/> </schema> [\u0000] <schema xmlns='http://www.w3.org/2001/XMLSchema' targetNamespace='http://www.apache.org/mail/' elementFormDefault='qualified' attributeFormDefault='qualified' xmlns:juneau='http://www.apache.org/2013/Juneau' xmlns:per='http://www.apache.org/person/' xmlns:addr='http://www.apache.org/address/' xmlns:mail='http://www.apache.org/mail/'> <import namespace='http://www.apache.org/2013/Juneau' schemaLocation='juneau.xsd'/> <import namespace='http://www.apache.org/person/' schemaLocation='per.xsd'/> <import namespace='http://www.apache.org/address/' schemaLocation='addr.xsd'/> <element name='street' type='juneau:java.lang.String'/> <element name='city' type='juneau:java.lang.String'/> <element name='state' type='juneau:java.lang.String'/> <element name='zip' type='juneau:int'/> </schema>

For convenience, the {@link org.apache.juneau.xml.XmlSchemaSerializer#getValidator(SerializerSession,Object)} method is provided to create a {@link javax.xml.validation.Validator} using the input from the serialize method.

2.6 - Non-tree models and recursion detection

The XML serializer is designed to be used against POJO tree structures.
It expects that there not be loops in the POJO model (e.g. children with references to parents, etc...).
If you try to serialize models with loops, you will usually cause a StackOverflowError to be thrown (if {@link org.apache.juneau.serializer.SerializerContext#SERIALIZER_maxDepth} is not reached first).

If you still want to use the XML serializer on such models, Juneau provides the {@link org.apache.juneau.serializer.SerializerContext#SERIALIZER_detectRecursions} setting.
It tells the serializer to look for instances of an object in the current branch of the tree and skip serialization when a duplicate is encountered.

For example, let's make a POJO model out of the following classes:

@Bean(typeName="a") public class A { public B b; } public class B { public C c; } public class C { public A a; }

Now we create a model with a loop and serialize the results.

// Create a new serializer with readable output. XmlSerializer s = new XmlSerializer() .setProperty(SerializerContext.SERIALIZER_useIndentation, true) .setProperty(SerializerContext.SERIALIZER_quoteChar, '\'') .setProperty(XmlSerializerContext.XML_enableNamespaces, false) .setProperty(SerializerContext.SERIALIZER_detectRecursions, true); // Create a recursive loop. A a = new A(); a.b = new B(); a.b.c = new C(); a.b.c.a = a; // Serialize to XML. String xml = s.serialize(a);

What we end up with is the following, which does not serialize the contents of the c field:

<a> <b> <c/> </b> </a>

Without recursion detection enabled, this would cause a stack-overflow error.

Recursion detection introduces a performance penalty of around 20%.
For this reason the setting is disabled by default.

2.7 - Configurable properties

See the following classes for all configurable properties that can be used on this serializer:

2.8 - Other notes

3 - XmlParser class

The {@link org.apache.juneau.xml.XmlParser} class is the class used to parse Juneau-generated XML back into POJOs.

A static reusable instance of XmlParser is also provided for convenience:

Let's build upon the previous example and parse the generated XML back into the original bean.
We start with the XML that was generated.

// Create a new serializer with readable output. XmlSerializer s = new XmlSerializer() .setProperty(SerializerContext.SERIALIZER_useIndentation, true) .setProperty(SerializerContext.SERIALIZER_quoteChar, '\''); // Create our bean. Person p = new Person(1, "John Smith", "http://sample/addressBook/person/1", "http://sample/addressBook", "Aug 12, 1946"); Address a = new Address(); a.uri = new URI("http://sample/addressBook/address/1"); a.personUri = new URI("http://sample/addressBook/person/1"); a.id = 1; a.street = "100 Main Street"; a.city = "Anywhereville"; a.state = "NY"; a.zip = 12345; a.isCurrent = true; p.addresses.add(a); // Serialize the bean to XML. String xml = s.serialize(p);

This code produced the following:

<per:person xmlns='http://www.apache.org/2013/Juneau' xmlns:per='http://www.apache.org/person/' xmlns:addr='http://www.apache.org/address/' xmlns:mail='http://www.apache.org/mail/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' uri='http://sample/addressBook/person/1'> <per:id>1</per:id> <per:name>John Smith</per:name> <per:addressBookUri>http://sample/addressBook</per:addressBookUri> <per:birthDate>1946-08-12T04:00:00Z</per:birthDate> <per:addresses> <addr:address uri='http://sample/addressBook/address/1'> <addr:personUri>http://sample/addressBook/person/1</addr:personUri> <addr:id>1</addr:id> <mail:street>100 Main Street</mail:street> <mail:city>Anywhereville</mail:city> <mail:state>NY</mail:state> <mail:zip>12345</mail:zip> <addr:isCurrent>true</addr:isCurrent> </addr:address> </per:addresses> </per:person>

The code to convert this back into a bean is:

// Parse it back into a bean using the reusable XML parser. Person p = XmlParser.DEFAULT.parse(xml, Person.class); // Render it as JSON. String json = JsonSerializer.DEFAULT_LAX_READABLE.serialize(p);

We print it out to JSON to show that all the data has been preserved:

{ id: 1, name: 'John Smith', uri: 'http://sample/addressBook/person/1', addressBookUri: 'http://sample/addressBook', birthDate: '1946-08-12T00:00:00Z', addresses: [ { uri: 'http://sample/addressBook/address/1', personUri: 'http://sample/addressBook/person/1', id: 1, street: '100 Main Street', city: 'Anywhereville', state: 'NY', zip: 12345, isCurrent: true } ] }

3.1 - Parsing into generic POJO models

The XML parser is not limited to parsing back into the original bean classes.
If the bean classes are not available on the parsing side, the parser can also be used to parse into a generic model consisting of Maps, Collections, and primitive objects.

You can parse into any Map type (e.g. HashMap, TreeMap), but using {@link org.apache.juneau.ObjectMap} is recommended since it has many convenience methods for converting values to various types.
The same is true when parsing collections. You can use any Collection (e.g. HashSet, LinkedList) or array (e.g. Object[], String[], String[][]), but using {@link org.apache.juneau.ObjectList} is recommended.

When the map or list type is not specified, or is the abstract Map, Collection, or List types, the parser will use ObjectMap and ObjectList by default.

3.1.1 - Serializing with JSON-type attributes

In some circumstances, the XML parser needs additional information to recreate the original data structure when various annotations (e.g. {@link org.apache.juneau.xml.annotation.Xml#childName()}, {@link org.apache.juneau.xml.annotation.Xml#format()}) are used.

For example, starting with this XML:

<per:person xmlns='http://www.apache.org/2013/Juneau' xmlns:per='http://www.apache.org/person/' xmlns:addr='http://www.apache.org/address/' xmlns:mail='http://www.apache.org/mail/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' uri='http://sample/addressBook/person/1'> <per:id>1</per:id> <per:name>John Smith</per:name> <per:addressBookUri>http://sample/addressBook</per:addressBookUri> <per:birthDate>1946-08-12T04:00:00Z</per:birthDate> <per:addresses> <addr:address uri='http://sample/addressBook/address/1'> <addr:personUri>http://sample/addressBook/person/1</addr:personUri> <addr:id>1</addr:id> <mail:street>100 Main Street</mail:street> <mail:city>Anywhereville</mail:city> <mail:state>NY</mail:state> <mail:zip>12345</mail:zip> <addr:isCurrent>true</addr:isCurrent> </addr:address> </per:addresses> </per:person>

Let's try to parse this into an ObjectMap.

// Parse XML into a generic POJO model. ObjectMap m = XmlParser.DEFAULT.parse(xml, ObjectMap.class); // Convert it to JSON. String json = JsonSerializer.DEFAULT_LAX_READABLE.serialize(m);

What we end up with is the following:

{ uri: 'http://sample/addressBook/person/1', id: '1', name: 'John Smith', addressBookUri: 'http://sample/addressBook', birthDate: '1946-08-12T04:00:00Z', addresses: { address: { uri: 'http://sample/addressBook/address/1', personUri: 'http://sample/addressBook/person/1', id: '1', street: '100 Main Street', city: 'Anywhereville', state: 'NY', zip: '12345', isCurrent: 'true' } } }

Note that the addresses field does not parse correctly as an array of addresses.
Without the beans providing meta-information about the data structure, the XML parser has no way of determining that it's an array.

To preserve this information, the {@link org.apache.juneau.xml.XmlSerializerContext#XML_addJsonTypeAttrs XML_addJsonTypeAttrs} property is provided that will insert type attributes into the XML to preserve JSON equivalency in the document when it cannot be determined automatically.

Example with XML_addJsonTypeAttrs setting enabled

// Create a new serializer with readable output. XmlSerializer s = new XmlSerializer() .setProperty(SerializerContext.SERIALIZER_useIndentation, true) .setProperty(SerializerContext.SERIALIZER_quoteChar, '\'') .setProperty(XmlSerializerContext.XML_addJsonTypeAttrs, true);

Results

<per:person xmlns='http://www.apache.org/2013/Juneau' xmlns:per='http://www.apache.org/person/' xmlns:addr='http://www.apache.org/address/' xmlns:mail='http://www.apache.org/mail/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' uri='http://sample/addressBook/person/1'> <per:id type='number'>1</per:id> <per:name>John Smith</per:name> <per:addressBookUri>http://sample/addressBook</per:addressBookUri> <per:birthDate>1946-08-12T04:00:00Z</per:birthDate> <per:addresses type='array'> <addr:address uri='http://sample/addressBook/address/1'> <addr:personUri>http://sample/addressBook/person/1</addr:personUri> <addr:id type='number'>1</addr:id> <mail:street>100 Main Street</mail:street> <mail:city>Anywhereville</mail:city> <mail:state>NY</mail:state> <mail:zip type='number'>12345</mail:zip> <addr:isCurrent type='boolean'>true</addr:isCurrent> </addr:address> </per:addresses> </per:person>

Notice the various type attributes that have been added to the output.

Now when we parse this into an ObjectMap and serialize it to JSON, we get the following:

{ uri: 'http://sample/addressBook/person/1', id: 1, name: 'John Smith', addressBookUri: 'http://sample/addressBook', birthDate: '1946-08-12T04:00:00Z', addresses: [ { uri: 'http://sample/addressBook/address/1', personUri: 'http://sample/addressBook/person/1', id: 1, street: '100 Main Street', city: 'Anywhereville', state: 'NY', zip: 12345, isCurrent: true } ] }

Once parsed into a generic model, various convenience methods are provided on the ObjectMap and ObjectList classes to retrieve values:

// Parse XML into a generic POJO model. ObjectMap m = XmlParser.DEFAULT.parse(xml, ObjectMap.class); // Get some simple values. String name = m.getString("name"); int id = m.getInt("id"); // Get a value convertable from a String. URI uri = m.get(URI.class, "uri"); // Get a value using a transform. CalendarSwap transform = new CalendarSwap.ISO8601DTZ(); Calendar birthDate = m.get(transform, "birthDate"); // Get the addresses. ObjectList addresses = m.getObjectList("addresses"); // Get the first address and convert it to a bean. Address address = addresses.get(Address.class, 0);

As a general rule, parsing into beans is often more efficient than parsing into generic models.
And working with beans is often less error prone than working with generic models.

3.2 - Configurable properties

See the following classes for all configurable properties that can be used on this parser:

3.3 - Other notes

4 - REST API support

Juneau provides fully-integrated support for XML serialization/parsing in the REST server and client APIs.
The next two sections describe these in detail.

4.1 - REST server support

There are four general ways of defining REST interfaces with support for XML. Two using the built-in Juneau Server API, and two using the JAX-RS integration component.

In general, the Juneau REST server API is much more configurable and easier to use than JAX-RS, but beware that the author may be slightly biased in this statement.

4.1.1 - Using RestServletDefault

The quickest way to implement a REST resource with XML support is to create a subclass of {@link org.apache.juneau.server.RestServletDefault}.
This class provides support for JSON, XML, HTML, URL-Encoding, and others.

The AddressBookResource example shown in the first chapter uses the RestServletJenaDefault class which is a subclass of RestServletDefault with additional support for RDF languages.
The start of the class definition is shown below:

// Proof-of-concept resource that shows off the capabilities of working with POJO resources. // Consists of an in-memory address book repository. @RestResource( messages="nls/AddressBookResource", properties={ @Property(name=SerializerContext.SERIALIZER_quoteChar, value="'"), @Property(name=HtmlSerializerContext.HTML_uriAnchorText, value=TO_STRING), @Property(name=HtmlDocSerializerContext.HTMLDOC_title, value="$L{title}"), @Property(name=HtmlDocSerializerContext.HTMLDOC_description, value="$L{description}"), @Property(name=HtmlDocSerializerContext.HTMLDOC_links, value="{options:'?method=OPTIONS',doc:'doc'}") }, encoders=GzipEncoder.class ) public class AddressBookResource extends RestServletJenaDefault {

Notice how serializer and parser properties can be specified using the @RestResource.properties() annotation.
The SERIALIZER_quoteChar property is common to all serializers.
The remaining properties are specific to the HTML serializer.

The $L{...} variable represent localized strings pulled from the resource bundle identified by the messages annotation. These variables are replaced at runtime based on the HTTP request locale. Several built-in runtime variable types are defined, and the API can be extended to include user-defined variables. See {@link org.apache.juneau.server.RestServlet#getVarResolver()} for more information.

This document won't go into all the details of the Juneau RestServlet class.
Refer to the {@link org.apache.juneau.server} documentation for more information on the REST servlet class in general.

The rest of the code in the resource class consists of REST methods that simply accept and return POJOs.
The framework takes care of all content negotiation, serialization/parsing, and error handling.
Below are 3 of those methods to give you a general idea of the concept:

// GET person request handler @RestMethod(name="GET", path="/people/{id}/*", rc={200,404}) public Person getPerson(RestRequest req, @Path int id) throws Exception { properties.put(HtmlDocSerializerContext.HTMLDOC_title, req.getPathInfo()); return findPerson(id); } // POST person handler @RestMethod(name="POST", path="/people", guards=AdminGuard.class, rc={307,404}) public void createPerson(RestResponse res, @Body CreatePerson cp) throws Exception { Person p = addressBook.createPerson(cp); res.sendRedirect(p.uri); } // DELETE person handler @RestMethod(name="DELETE", path="/people/{id}", guards=AdminGuard.class, rc={200,404}) public String deletePerson(RestResponse res, @Path int id) throws Exception { Person p = findPerson(id); addressBook.remove(p); return "DELETE successful"; }

The resource class can be registered with the web application like any other servlet, or can be defined as a child of another resource through the {@link org.apache.juneau.server.annotation.RestResource#children()} annotation.

4.1.2 - Using RestServlet with annotations

For fine-tuned control of media types, the {@link org.apache.juneau.server.RestServlet} class can be subclassed directly.
The serializers/parsers can be specified through annotations at the class and/or method levels.

An equivalent AddressBookResource class could be defined to only support XML using the following definition:

@RestResource( serializers={XmlSerializer.class}, parsers={XmlParser.class}, properties={ @Property(name=SerializerContext.SERIALIZER_quoteChar, value="'") } ) public class AddressBookResource extends RestServlet {

Likewise, serializers and parsers can be specified/augmented/overridden at the method level like so:

// GET person request handler @RestMethod(name="GET", path="/people/{id}/*", rc={200,404}, serializers={XmlSerializer.class}, parsers={XmlParser.class}, properties={ @Property(name=SerializerContext.SERIALIZER_quoteChar, value="'") } ) public Person getPerson(RestRequest req, @Path int id) throws Exception { properties.put(HtmlDocSerializerContext.HTMLDOC_title, req.getPathInfo()); return findPerson(id); }

The {@link org.apache.juneau.server.annotation.RestMethod#serializersInherit()} and {@link org.apache.juneau.server.annotation.RestMethod#parsersInherit()} control how various artifacts are inherited from the parent class.
Refer to {@link org.apache.juneau.server} for additional information on using these annotations.

4.1.3 - Using JAX-RS DefaultProvider

XML media type support in JAX-RS can be achieved by using the {@link org.apache.juneau.server.jaxrs.DefaultProvider} class.
It implements the JAX-RS MessageBodyReader and MessageBodyWriter interfaces for all Juneau supported media types.

The DefaultProvider class definition is shown below:

@Provider @Produces( "application/json,text/json,"+ // JsonSerializer "application/json+simple,text/json+simple,"+ // JsonSerializer.Simple "application/json+schema,text/json+schema,"+ // JsonSchemaSerializer "text/xml,"+ // XmlDocSerializer "text/xml+simple,"+ // XmlDocSerializer.Simple "text/xml+schema,"+ // XmlSchemaDocSerializer "text/html,"+ // HtmlDocSerializer "application/x-www-form-urlencoded,"+ // UrlEncodingSerializer "text/xml+soap,"+ // SoapXmlSerializer "application/x-java-serialized-object" // JavaSerializedObjectSerializer ) @Consumes( "application/json,text/json,"+ // JsonParser "text/xml,"+ // XmlParser "text/html,"+ // HtmlParser "application/x-www-form-urlencoded,"+ // UrlEncodingParser "application/x-java-serialized-object" // JavaSerializedObjectParser ) @JuneauProvider( serializers={ JsonSerializer.class, JsonSerializer.Simple.class, JsonSchemaSerializer.class, XmlDocSerializer.class, XmlDocSerializer.Simple.class, XmlSchemaDocSerializer.class, HtmlDocSerializer.class, UrlEncodingSerializer.class, SoapXmlSerializer.class, JavaSerializedObjectSerializer.class }, parsers={ JsonParser.class, XmlParser.class, HtmlParser.class, UrlEncodingParser.class, JavaSerializedObjectParser.class, } ) public final class DefaultProvider extends BaseProvider {}

That's the entire class. It consists of only annotations to hook up media types to Juneau serializers and parsers. The @Provider, @Produces, and @Consumes annotations are standard JAX-RS annotations, and the @JuneauProvider annotation is from Juneau.

To enable the provider, you need to make the JAX-RS environment aware of it. In Wink, this is accomplished by adding an entry to a config file.

<web-app version="2.3"> <servlet> <servlet-name>WinkService</servlet-name> <servlet-class>org.apache.wink.server.internal.servlet.RestServlet</servlet-class> <init-param> <param-name>applicationConfigLocation</param-name> <param-value>/WEB-INF/wink.cfg</param-value> </init-param> </servlet>

Simply include a reference to the provider in the configuration file.

org.apache.juneau.server.jaxrs.DefaultProvider

Properties can be specified on providers through the {@link org.apache.juneau.server.jaxrs.JuneauProvider#properties()} annotation.
Properties can also be specified at the method level by using the {@link org.apache.juneau.server.annotation.RestMethod#properties} annotation, like so:

@GET @Produces("*/*") @RestMethod( /* Override some properties */ properties={ @Property(name=SerializerContext.SERIALIZER_quoteChar, value="'") } ) public Message getMessage() { return message; }

Limitations

In general, the Juneau REST API is considerably more flexible than the JAX-RS API, since you can specify and override serializers, parsers, properties, transforms, converters, guards, etc... at both the class and method levels.
Therefore, the JAX-RS API has the following limitations that the Juneau Server API does not:

  • The ability to specify different media type providers at the class and method levels.
    For example, you may want to use XmlSerializer with one set of properties on one class, and another instance with different properties on another class.
    There is currently no way to define this at the class level.
    You can override properties at the method level, but this can be cumbersome since it would have to be done for all methods in the resource.
  • The Juneau Server API allows you to manipulate properties programatically through the {@link org.apache.juneau.server.RestResponse#setProperty(String,Object)} method, and through the {@link org.apache.juneau.server.annotation.Properties} annotation.
    There is no equivalent in JAX-RS.

4.1.4 - Using JAX-RS BaseProvider with annotations

To provide support for only XML media types, you can define your own provider class, like so:

@Provider @Produces( "text/xml,"+ // XmlDocSerializer "text/xml+simple" // XmlDocSerializer.Simple ) @Consumes( "text/xml" // XmlParser ) @JuneauProvider( serializers={ XmlDocSerializer.class, XmlDocSerializer.Simple.class }, parsers={ XmlParser.class, } properties={ @Property(name=SerializerContext.SERIALIZER_quoteChar, value="'") } ) public final class MyRdfProvider extends BaseProvider {}

Then register it with Wink the same way as DefaultProvider.

4.2 - REST client support

The {@link org.apache.juneau.client.RestClient} class provides an easy-to-use REST client interface with pluggable media type handling using any of the Juneau serializers and parsers.
Defining a client to support XML media types on HTTP requests and responses can be done in one line of code:

// Create a client to handle XML requests and responses. RestClient client = new RestClient(XmlSerializer.class, XmlParser.class);

The client handles all content negotiation based on the registered serializers and parsers.

The following code is pulled from the main method of the ClientTest class in the sample web application, and is run against the AddressBookResource class running within the sample app.
It shows how the client can be used to interact with the REST API while completely hiding the negotiated content type and working with nothing more than beans.

Example

String root = "http://localhost:9080/sample/addressBook"; // Get the current contents of the address book AddressBook ab = client.doGet(root).getResponse(AddressBook.class); System.out.println("Number of entries = " + ab.size()); // Delete the existing entries for (Person p : ab) { String r = client.doDelete(p.uri).getResponse(String.class); System.out.println("Deleted person " + p.name + ", response = " + r); } // Make sure they're gone ab = client.doGet(root).getResponse(AddressBook.class); System.out.println("Number of entries = " + ab.size()); // Add 1st person again CreatePerson cp = new CreatePerson( "Barack Obama", toCalendar("Aug 4, 1961"), new CreateAddress("1600 Pennsylvania Ave", "Washington", "DC", 20500, true), new CreateAddress("5046 S Greenwood Ave", "Chicago", "IL", 60615, false) ); Person p = client.doPost(root + "/people", cp).getResponse(Person.class); System.out.println("Created person " + p.name + ", uri = " + p.uri); // Add 2nd person again, but add addresses separately cp = new CreatePerson( "George Walker Bush", toCalendar("Jul 6, 1946") ); p = client.doPost(root + "/people", cp).getResponse(Person.class); System.out.println("Created person " + p.name + ", uri = " + p.uri); // Add addresses to 2nd person CreateAddress ca = new CreateAddress("43 Prairie Chapel Rd", "Crawford", "TX", 76638, true); Address a = client.doPost(p.uri + "/addresses", ca).getResponse(Address.class); System.out.println("Created address " + a.uri); ca = new CreateAddress("1600 Pennsylvania Ave", "Washington", "DC", 20500, false); a = client.doPost(p.uri + "/addresses", ca).getResponse(Address.class); System.out.println("Created address " + a.uri); // Find 1st person, and change name Person[] pp = client.doGet(root + "?q={name:\"'Barack+Obama'\"}").getResponse(Person[].class); String r = client.doPut(pp[0].uri + "/name", "Barack Hussein Obama").getResponse(String.class); System.out.println("Changed name, response = " + r); p = client.doGet(pp[0].uri).getResponse(Person.class); System.out.println("New name = " + p.name);

Results

Number of entries = 2 Deleted person Barack Obama, response = DELETE successful Deleted person George Walker Bush, response = DELETE successful Number of entries = 0 Created person Barack Obama, uri = http://localhost:9080/sample/addressBook/people/3 Created person George Walker Bush, uri = http://localhost:9080/sample/addressBook/people/4 Created address http://localhost:9080/sample/addressBook/addresses/7 Created address http://localhost:9080/sample/addressBook/addresses/8 Changed name, response = PUT successful New name = Barack Hussein Obama

*** fín ***