org.apache.juneau.xml.package.html Maven / Gradle / Ivy
XML serialization and parsing support
Table of Contents
-
-
-
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:
-
Java primitive objects (e.g.
String
, Integer
, Boolean
,
Float
).
-
Java collections framework objects (e.g.
HashSet
, TreeMap
) containing anything
on this list.
-
Multi-dimensional arrays of any type on this list.
-
Java Beans with properties of any type on this list.
-
Classes with standard transformations to and from
Strings
(e.g. classes containing
toString()
, fromString()
, valueOf()
,
constructor(String)
).
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 juneau-examples-rest
microservice project.
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:
-
{@link org.apache.juneau.xml.XmlSerializer#DEFAULT}
- All default settings.
-
{@link org.apache.juneau.xml.XmlSerializer#DEFAULT_SQ}
- Use single quotes on attributes. Typically useful for testing since it makes string comparison simpler.
-
{@link org.apache.juneau.xml.XmlSerializer#DEFAULT_SQ_READABLE}
- Use single quotes on attributes and add whitespace for readability.
-
{@link org.apache.juneau.xml.XmlSerializer#DEFAULT_NS}
- Same as DEFAULT but with namespaces enabled.
-
{@link org.apache.juneau.xml.XmlSerializer#DEFAULT_NS_SQ}
- Same as DEFAULT_SQ but with namespaces enabled.
-
{@link org.apache.juneau.xml.XmlSerializer#DEFAULT_NS_SQ_READABLE}
- Same as DEFAULT_SQ_READABLE but with namespaces enabled.
In addition, DTO beans are provided that use the XML serializer and parser for the following languages:
- org.apache.juneau.dto.atom
- ATOM beans.
- org.apache.juneau.dto.cognos
- Cognos beans.
- org.apache.juneau.dto.html5
- HTML5 beans.
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 XmlSerializerBuilder()
.ws() // or .useWhitespace(true)
.sq() // or .quoteChar('\'')
.ns() // or .enableNamespaces(false)
.build();
// 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
.builder()
.ns()
.build();
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:
- Root element
- Object in an array, collection, or map.
The generalized name reflects the JSON-equivalent data type.
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-equivalence.
2.1 - XML support methodology
The following examples show how different data types are represented in XML.
They mirror how the data structures are represented in JSON.
Simple types
The representation of loose (not a direct bean property value) simple types are shown below:
Data type
JSON example
XML
string
'foo'
<string> foo</string>
boolean
true
<boolean> true</boolean>
integer
123
<number> 123</number>
float
1.23
<number> 1.23</number>
null
null
<null/>
Maps
Loose maps and beans use the element <object> for encapsulation.
_type attributes are added to bean properties or map entries if the type cannot be inferred
through reflection (e.g. an Object
or superclass/interface value type).
Data type
JSON example
XML
Map<String,String>
{
k1: 'v1'
k2: null
}
<object>
<k1>v1 </k1>
<k2 _type ='null' />
</object>
Map<String,Number>
{
k1: 123,
k2: 1.23,
k3: null
}
<object>
<k1>123 </k1>
<k2>1.23 </k2>
<k3 _type ='null' />
</object>
Map<String,Object>
{
k1: 'v1'
k2: 123,
k3: 1.23,
k4: true ,
k5: null
}
<object>
<k1>v1 </k1>
<k2 _type ='number' >123 </k2>
<k3 _type ='number' >1.23 </k3>
<k4 _type ='boolean' >true </k4>
<k5 _type ='null' />
</object>
Arrays
Loose collections and arrays use the element <array> for encapsulation.
Data type
JSON example
XML
String[]
[
'foo'
null
]
<array>
<string>foo </string>
<null/>
</array>
Number[]
[
123,
1.23,
null
]
<array>
<number>123 </number>
<number>1.23 </number>
<null/>
</array>
Object[]
[
'foo' ,
123,
1.23,
true ,
null
]
<array>
<string>foo </string>
<number>123 </number>
<number>1.23 </number>
<boolean>true </boolean>
<null/>
</array>
String[][]
[
['foo' , null ],
null ,
]
<array>
<array>
<string>foo </string>
<null/>
</array>
<null/>
</array>
int []
[
123
]
<array>
<number>123 </number>
</array>
boolean []
[
true
]
<array>
<boolean>true </boolean>
</array>
List<String>
[
'foo'
null
]
<array>
<string>foo </string>
<null/>
</array>
List<Number>
[
123,
1.23,
null
]
<array>
<number>123 </number>
<number>1.23 </number>
<null/>
</array>
List<Object>
[
'foo' ,
123,
1.23,
true ,
null
]
<array>
<string>foo </string>
<number>123 </number>
<number>1.23 </number>
<boolean>true </boolean>
<null/>
</array>
Beans
Data type
JSON example
XML
class MyBean {
public String a;
public int b;
public Object c; // String value
public Object d; // Integer value
public MyBean2 e;
public String[] f;
public int [] g;
}
class MyBean2 {
String h;
}
{
a: 'foo' ,
b: 123,
c: 'bar' ,
d: 456,
e: {
h: 'baz'
}
f: ['qux' ]
g: [789]
}
<object>
<a>foo </a>
<b>123 </b>
<c>bar </c>
<d _type ='number' >456 </d>
<e>
<h>baz </h>
</e>
<f>
<string>qux </string>
</f>
<g>
<number>789 </number>
</g>
</object>
Beans with Map properties
Data type
JSON example
XML
class MyBean {
public Map<String,String> a;
public Map<String,Number> b;
public Map<String,Object> c;
}
{
a: {
k1: 'foo'
},
b: {
k2: 123
},
c: {
k3: 'bar' ,
k4: 456,
k5: true ,
k6: null
}
}
<object>
<a>
<k1>foo </k1>
</a>
<b>
<k2>123 </k2>
</b>
<c>
<k3>bar </k3>
<k4 _type ='number' >456 </k4>
<k5 _type ='boolean' >true </k5>
<k6 _type ='null' />
</c>
</object>
2.2 - @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
org.apache.juneau.xml.annotation package for customizing the output.
2.2.1 - @Bean.typeName()
The {@link org.apache.juneau.annotation.Bean#typeName() @Bean.typeName()} annotation can be used to
override the Juneau default name on bean elements.
Types names serve two distinct purposes:
- To override the element name.
- To serve as a class identifier so that the bean class can be inferred during parsing if it
cannot automatically be inferred through reflection.
Example
Data type
JSON example
Without annotation
With annotation
@Bean (typeName="X" )
class MyBean {
public String a;
public int b;
}
{
a: 'foo' ,
b: 123
}
<object>
<a>foo </id>
<b>123 </name>
</object>
<X>
<a>foo </id>
<b>123 </name>
</X>
On bean properties, a _type attribute will be added if a type name is present and the bean
class cannot be inferred through reflection.
In the following example, a type attribute is used on property 'b' but not property 'a' since
'b' is of type Object
and therefore the bean class cannot be inferred.
Example
Java
Without annotation
With annotation
class MyBean {
public BeanX a = new BeanX();
public Object b = new BeanX();
}
@Bean (typeName="X" )
class BeanX {
public String fx = "foo" ;
}
<object>
<a>
<fx>foo </fx>
</a>
<b>
<fx>foo </fx>
</b>
</object>
<object>
<a>
<fx>foo </fx>
</a>
<b _type ='X' >
<fx>foo </fx>
</b>
</object>
-
string
, number
, boolean
, object
,
array
, and null
are reserved keywords that cannot be used as type names.
Beans with type names are often used in conjunction with the
{@link org.apache.juneau.annotation.Bean#beanDictionary() @Bean.beanDictionary()} and
{@link org.apache.juneau.annotation.BeanProperty#beanDictionary() @BeanProperty.beanDictionary()}
annotations so that the beans can be resolved at parse time.
These annotations are not necessary during serialization, but are needed during parsing in order to
resolve the bean types.
The following examples show how type names are used under various circumstances.
Pay special attention to when _type attributes are and are not used.
Examples
Java
XML
@Bean (beanDictionary={BeanX.class })
class BeanWithArrayPropertiesWithTypeNames {
public BeanX[] b1 = new BeanX[]{
new BeanX()
};
public Object[] b2 = new BeanX[]{
new BeanX()
};
public Object[] b3 = new Object[]{
new BeanX()
};
}
<object>
<b1>
<X>
<fx>foo </fx>
</X>
</b1>
<b2>
<X>
<fx>foo </fx>
</X>
</b2>
<b3>
<X>
<fx>foo </fx>
</X>
</b3>
</object>
@Bean (beanDictionary={BeanX.class })
class BeanWith2dArrayPropertiesWithTypeNames {
public BeanX[][] b1 = new BeanX[][]{{
new BeanX()
}};
public Object[][] b2 = new BeanX[][]{{
new BeanX()
}};
public Object[][] b3 = new Object[][]{{
new BeanX()
}};
}
<object>
<b1>
<array>
<X>
<fx>foo </fx>
</X>
</array>
</b1>
<b2>
<array>
<X>
<fx>foo </fx>
</X>
</array>
</b2>
<b3>
<array>
<X>
<fx>foo </fx>
</X>
</array>
</b3>
</object>
@Bean (beanDictionary={BeanX.class })
class BeanWithMapPropertiesWithTypeNames {
public Map<String,BeanX> b1 = new HashMap<>() {{
put("k1" , new BeanX());
}};
public Map<String,Object> b2 = new HashMap<>() {{
put("k2" , new BeanX());
}}
}
<object>
<b1>
<k1>
<fx>foo </fx>
</k1>
</b1>
<b2>
<k2 _type ='X' >
<fx>foo </fx>
</k2>
</b2>
</object>
Bean type names are also used for resolution when abstract fields are used.
The following examples show how they are used in a variety of circumstances.
Java
XML
@Bean (beanDictionary={A.class })
class BeanWithAbstractFields {
public A a = new A();
public IA ia = new A();
public AA aa = new A();
public Object o = new A();
}
interface IA {}
abstract class AA implements IA {}
@Bean (typeName="A" )
class A extends AA {
public String fa = "foo" ;
}
<object>
<a>
<fa>foo </fa>
</a>
<ia _type ='A' >
<fa>foo </fa>
</ia>
<aa _type ='A' >
<fa>foo </fa>
</aa>
<o _type ='A' >
<fa>foo </fa>
</o>
</object>
@Bean (beanDictionary={A.class })
class BeanWithAbstractArrayFields {
public A[] a = new A[]{new A()};
public IA[] ia1 = new A[]{new A()};
public IA[] ia2 = new IA[]{new A()};
public AA[] aa1 = new A[]{new A()};
public AA[] aa2 = new AA[]{new A()};
public Object[] o1 = new A[]{new A()};
public Object[] o2 = new Object[]{new A()};
}
<object>
<a>
<A>
<fa>foo </fa>
</A>
</a>
<ia1>
<A>
<fa>foo </fa>
</A>
</ia1>
<ia2>
<A>
<fa>foo </fa>
</A>
</ia2>
<aa1>
<A>
<fa>foo </fa>
</A>
</aa1>
<aa2>
<A>
<fa>foo </fa>
</A>
</aa2>
<o1>
<A>
<fa>foo </fa>
</A>
</o1>
<o2>
<A>
<fa>foo </fa>
</A>
</o2>
</object>
@Bean (beanDictionary={A.class })
class BeanWithAbstractMapFields {
public Map<String,A> a = new HashMap<>() {{
put("k1" , new A());
}};
public Map<String,AA> b = new HashMap<>() {{
put("k2" , new A());
}};
public Map<String,Object> c = new HashMap<>() {{
put("k3" , new A());
}};
}
<object>
<a>
<k1>
<fa>foo </fa>
</k1>
</a>
<b>
<k2 _type ='A' >
<fa>foo </fa>
</k2>
</b>
<c>
<k3 _type ='A' >
<fa>foo </fa>
</k3>
</c>
</object>
@Bean (beanDictionary={A.class })
class BeanWithAbstractMapArrayFields {
public Map<String,A[]> a = new LinkedHashMap<>() {{
put("a1" , new A[]{new A()});
}};
public Map<String,IA[]> ia = new LinkedHashMap<>() {{
put("ia1" , new A[]{new A()});
put("ia2" , new IA[]{new A()});
}};
public Map<String,AA[]> aa = new LinkedHashMap<>() {{
put("aa1" , new A[]{new A()});
put("aa2" , new AA[]{new A()});
}};
public Map<String,Object[]> o = new LinkedHashMap<>() {{
put("o1" , new A[]{new A()});
put("o2" , new AA[]{new A()});
}};
}
<object>
<a>
<a1>
<A>
<fa>foo </fa>
</A>
</a1>
</a>
<ia>
<ia1>
<A>
<fa>foo </fa>
</A>
</ia1>
<ia2>
<A>
<fa>foo </fa>
</A>
</ia2>
</ia>
<aa>
<aa1>
<A>
<fa>foo </fa>
</A>
</aa1>
<aa2>
<A>
<fa>foo </fa>
</A>
</aa2>
</aa>
<o>
<o1>
<A>
<fa>foo </fa>
</A>
</o1>
<o2>
<A>
<fa>foo </fa>
</A>
</o2>
</o>
</object>
On a side note, characters that cannot be represented in XML 1.0 are encoded using a simple encoding.
Note in the examples below, some characters such as '\n' , '\t ', and '\r'
can be represented as XML entities when used in text but not in element names. Other characters such as
'\b' and '\f' cannot be encoded in XML 1.0 at all without inventing our own notation.
Whitespace characters in element names are encoded as well as whitespace end characters in text.
Java
XML
class BeanWithSpecialCharacters {
public String a = " \b\f\n\t\r " ;
}
<object>
<a>_x0020_ _x0008__x000C_
	
 _x0020_ </a>
</object>
@Bean (typeName=" \b\f\n\t\r " )
class BeanWithNamesWithSpecialCharacters {
@BeanProperty (name=" \b\f\n\t\r " )
public String a = " \b\f\n\t\r " ;
}
<_x0020__x0020__x0008__x000C__x000A__x0009__x000D__x0020__x0020_>
<_x0020__x0020__x0008__x000C__x000A__x0009__x000D__x0020__x0020_>_x0020_ _x0008__x000C_
	
 _x0020_ </_x0020__x0020__x0008__x000C__x000A__x0009__x000D__x0020__x0020_>
</_x0020__x0020__x0008__x000C__x000A__x0009__x000D__x0020__x0020_>
While it's true that these characters CAN be represented in XML 1.1, it's impossible to parse XML 1.1
text in Java without the XML containing an XML declaration.
Unfortunately, this, and the uselessness of the
{@link javax.xml.stream.XMLInputFactory#IS_REPLACING_ENTITY_REFERENCES} setting in Java
forced us to make some hard design decisions that may not be the most elegant.
2.2.2 - @Xml.childName()
The {@link org.apache.juneau.xml.annotation.Xml#childName() @Xml.childName()} annotation can be used to
specify the name of XML child elements for bean properties of type collection or array.
Example
Data type
JSON example
Without annotation
With annotation
class MyBean {
@Xml (childName="X" )
public String[] a;
@Xml (childName="Y" )
public int[] b;
}
{
a: ['foo' ,'bar' ],
b: [123,456]
}
<object>
<a>
<string>foo </string>
<string>bar </string>
</a>
<b>
<number>123 </number>
<number>456 </number>
</b>
</object>
<object>
<a>
<X>foo </X>
<X>bar </X>
</a>
<b>
<Y>123 </Y>
<Y>456 </Y>
</b>
</object>
class MyBean {
@Xml (childName="child" )
public int[] a;
}
{
a: [123,456]
}
<object>
<a>
<string>foo </string>
<string>bar </string>
</a>
</object>
<object>
<a>
<child>foo </child>
<child>bar </child>
</a>
</object>
2.2.3 - @Xml.format()
The {@link org.apache.juneau.xml.annotation.Xml#format() @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.
The {@link org.apache.juneau.xml.annotation.XmlFormat#ATTR} format can be applied to bean properties to
serialize them as XML attributes instead of elements.
Note that this only supports properties of simple types (e.g. strings, numbers, booleans).
Example
Data type
JSON example
Without annotation
With annotation
class MyBean {
@Xml (format=XmlFormat.ATTR )
public String a;
}
{
a: 'foo'
}
<object>
<a>foo </a>
</object>
<object a ='foo' />
The {@link org.apache.juneau.xml.annotation.XmlFormat#ATTRS} format can be applied to bean classes to
force all bean properties to be serialized as XML attributes instead of child elements.
Example
Data type
JSON example
Without annotation
With annotation
@Xml (format=XmlFormat.ATTRS )
class MyBean {
public String a;
public int b;
}
{
a: 'foo' ,
b: 123
}
<object>
<a>foo </a>
<b>123 </b>
</object>
<object a ='foo' b ='123' />
The {@link org.apache.juneau.xml.annotation.XmlFormat#ELEMENT} format can be applied to bean properties
to override the {@link org.apache.juneau.xml.annotation.XmlFormat#ATTRS} format applied on the bean
class.
Example
Data type
JSON example
Without annotation
With annotation
@Xml (format=XmlFormat.ATTRS )
class MyBean {
public String a;
@Xml (format=XmlFormat.ELEMENT )
public int b;
}
{
a: 'foo' ,
b: 123
}
<object>
<a>foo </a>
<b>123 </b>
</object>
<object a ='foo' >
<b>123 </b>
</object>
The {@link org.apache.juneau.xml.annotation.XmlFormat#ATTRS} format can be applied to a single bean
property of type Map<String,Object>
to denote arbitrary XML attribute values on the
element.
These can be mixed with other {@link org.apache.juneau.xml.annotation.XmlFormat#ATTR} annotated
properties, but there must not be an overlap in bean property names and map keys.
Example
Data type
JSON example
Without annotation
With annotation
class MyBean {
@Xml (format=XmlFormat.ATTRS )
public Map<String,Object> a;
@Xml (format=XmlFormat.ATTR )
public int b;
}
{
a: {
k1: 'foo' ,
k2: 123,
},
b: 456
}
<object>
<a>
<k1>foo </k1>
<k2 _type ='number' >123 </k2>
</a>
<b>456 </b>
</object>
<object k1 ='foo' k2 ='123' b ='456' />
The {@link org.apache.juneau.xml.annotation.XmlFormat#COLLAPSED} format can be applied to bean properties
of type array/Collection.
This causes the child objects to be serialized directly inside the bean element.
This format must be used in conjunction with {@link org.apache.juneau.xml.annotation.Xml#childName()}
to differentiate which collection the values came from if you plan on parsing the output back into beans.
Note that child names must not conflict with other property names.
Data type
JSON example
Without annotation
With annotation
class MyBean {
@Xml (childName="A" ,format=XmlFormat.COLLAPSED )
public String[] a;
@Xml (childName="B" ,format=XmlFormat.COLLAPSED )
public int [] b;
}
{
a: ['foo' ,'bar' ],
b: [123,456]
}
<object>
<a>
<string>foo </string>
<string>bar </string>
</a>
<b>
<number>123 </number>
<number>456 </number>
</b>
</object>
<object>
<A>foo </A>
<A>bar </A>
<B>123 </B>
<B>456 </B>
</object>
The {@link org.apache.juneau.xml.annotation.XmlFormat#ELEMENTS} format can be applied to a single bean
property of either a simple type or array/Collection.
It allows free-form child elements to be formed.
All other properties on the bean MUST be serialized as attributes.
Data type
JSON example
With annotation
class MyBean {
@Xml (format=XmlFormat.ATTR )
public String a;
@Xml (format=XmlFormat.ELEMENTS )
public String b;
}
{
a: 'foo' ,
b: 'bar'
}
<object a ='foo' >
<string>bar </string>
</object>
class MyBean {
@Xml (format=XmlFormat.ATTR )
public String a;
@Xml (format=XmlFormat.ELEMENTS )
public Object[] b;
}
{
a: 'foo' ,
b: [
'bar' ,
'baz' ,
123,
true ,
null
]
}
<object a ='foo' >
<string>bar </string>
<string>baz </string>
<number>123 </number>
<boolean>true </boolean>
<null/>
</object>
The {@link org.apache.juneau.xml.annotation.XmlFormat#MIXED} format is similar to
{@link org.apache.juneau.xml.annotation.XmlFormat#ELEMENTS} except elements names on primitive types
(string/number/boolean/null) are stripped from the output.
This format particularly useful when combined with bean dictionaries to produce mixed content.
The bean dictionary isn't used during serialization, but it is needed during parsing to resolve bean
types.
The {@link org.apache.juneau.xml.annotation.XmlFormat#MIXED_PWS} format identical to
{@link org.apache.juneau.xml.annotation.XmlFormat#MIXED} except whitespace characters are preserved in
the output.
Data type
JSON example
Without annotations
With annotations
class MyBean {
@Xml (format=XmlFormat.MIXED )
@BeanProperty (beanDictionary={MyBeanX.class , MyBeanY.class })
public Object[] a;
}
@Bean (typeName="X" )
class MyBeanX {
@Xml (format=XmlFormat.ATTR )
public String b;
}
@Bean (typeName="Y" )
class MyBeanY {
@Xml (format=XmlFormat.ATTR )
public String c;
}
{
a: [
'foo' ,
{ _type:'X' , b:'bar' }
'baz' ,
{ _type:'Y' , b:'qux' },
'quux'
]
}
<object>
<a>
<string>foo </string>
<object>
<b>bar </b>
</object>
<string>baz </string>
<object>
<b>qux </b>
</object>
<string>quux </string>
</a>
</object>
<object>foo <X b ='bar' />baz <Y c ='qux' />quux </object>
Whitespace (tabs and newlines) are not added to MIXED child nodes in readable-output mode.
This helps ensures strings in the serialized output can be losslessly parsed back into their original
forms when they contain whitespace characters.
If the {@link javax.xml.stream.XMLInputFactory#IS_REPLACING_ENTITY_REFERENCES} setting was not useless
in Java, we could support lossless readable XML for MIXED content.
But as of Java 8, it still does not work.
XML suffers from other deficiencies as well that affect MIXED content.
For example, <X></X> and <X/> are equivalent in XML and
indistinguishable by the Java XML parsers.
This makes it impossible to differentiate between an empty element and an element containing an empty
string.
This causes empty strings to get lost in translation.
To alleviate this, we use the constructs "_xE000_" to represent an empty string, and
"_x0020_" to represent leading and trailing spaces.
The examples below show how whitespace is handled under various circumstances:
Data type
XML
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.TEXT )
public String a = null ;
}
<X/>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.TEXT )
public String a = "" ;
}
<X>_xE000_ </X>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.TEXT )
public String a = " " ;
}
<X>_x0020_ </X>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.TEXT )
public String a = " " ;
}
<X>_x0020__x0020_ </X>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.TEXT )
public String a = " foobar " ;
}
<X>_x0020_ foobar _x0020_ </X>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.TEXT_PWS )
public String a = null ;
}
<X/>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.TEXT_PWS )
public String a = "" ;
}
<X>_xE000_ </X>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.TEXT_PWS )
public String a = " " ;
}
<X> </X>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.TEXT_PWS )
public String a = " " ;
}
<X> </X>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.TEXT_PWS )
public String a = " foobar " ;
}
<X> foobar </X>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.MIXED )
public String[] a = null ;
}
<X/>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.MIXED )
public String a[] = new String[]{"" };
}
<X>_xE000_ </X>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.MIXED )
public String a[] = new String[]{" " };
}
<X>_x0020_ </X>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.MIXED )
public String a[] = new String[]{" " };
}
<X>_x0020__x0020_ </X>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.MIXED )
public String a[] = new String[]{
" foobar "
};
}
<X>_x0020_ foobar _x0020_ </X>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.MIXED_PWS )
public String[] a = null ;
}
<X/>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.MIXED_PWS )
public String a[] = new String[]{"" };
}
<X>_xE000_ </X>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.MIXED_PWS )
public String a[] = new String[]{" " };
}
<X> </X>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.MIXED_PWS )
public String a[] = new String[]{" " };
}
<X> </X>
@Bean (typeName="X" )
class MyBean {
@Xml (format=XmlFormat.MIXED_PWS )
public String a[] = new String[]{
" foobar "
};
}
<X> foobar </X>
It should be noted that when using MIXED , you are not guaranteed to parse back the exact
same content since side-by-side strings in the content will end up concatenated when parsed.
The {@link org.apache.juneau.xml.annotation.XmlFormat#TEXT} format is similar to
{@link org.apache.juneau.xml.annotation.XmlFormat#MIXED} except it's meant for solitary objects that
get serialized as simple child text nodes.
Any object that can be serialize to a String
can be used.
The {@link org.apache.juneau.xml.annotation.XmlFormat#TEXT_PWS} is the same except whitespace is
preserved in the output.
Data type
JSON example
Without annotations
With annotations
class MyBean {
@Xml (format=XmlFormat.TEXT )
public String a;
}
{
a: 'foo'
}
<object>
<a>foo </a>
</object>
<object>foo </object>
The {@link org.apache.juneau.xml.annotation.XmlFormat#XMLTEXT} format is similar to
{@link org.apache.juneau.xml.annotation.XmlFormat#TEXT} except it's meant for strings containing XML
that should be serialized as-is to the document.
Any object that can be serialize to a String
can be used.
During parsing, the element content gets parsed with the rest of the document and then re-serialized to
XML before being set as the property value.
This process may not be perfect (e.g. double quotes may be replaced by single quotes, etc...).
Data type
JSON example
With TEXT annotation
With XMLTEXT annotation
class MyBean {
@Xml (format=XmlFormat.XMLTEXT )
public String a;
}
{
a: 'Some <b>XML</b> text'
}
<object>Some <b>XML</b> text </object>
<object>Some <b> XML</b> text </object>
2.3 - 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.
// Note that this is identical to XmlSerializer.DEFAULT_NS_SQ_READABLE.
XmlSerializer s = new XmlSerializerBuilder().ns().ws().sq().build();
// 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' >
<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.
Namespaces can be defined at the following levels:
-
At the package level by using the {@link org.apache.juneau.xml.annotation.XmlSchema @XmlSchema}
annotation.
-
At the class level by using the {@link org.apache.juneau.xml.annotation.Xml @Xml} annotation.
-
At the bean property level by using the {@link org.apache.juneau.xml.annotation.Xml @Xml} annotation.
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.examples.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 modeled after the equivalent JAXB annotation, but is
defined in the org.apache.juneau.xml.annotation package.
Other XML annotations are also modeled 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/' >
<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 XmlSerializerBuilder()
.ws()
.sq()
.ns()
.defaultNamespaceUri("http://www.apache.org/person/" )
.build();
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' >
<id> 1</id>
<name> John Smith</name>
</person>
2.3.1 - Auto-detection of namespaces
One important property on the XML serializer class is
{@link org.apache.juneau.xml.XmlSerializer#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 pre-scan step.
// Create a new serializer with readable output, this time with namespaces enabled.
XmlSerializer s = new XmlSerializerBuilder()
.ws()
.sq()
.autoDetectNamespaces(false )
.namespaces("{per:'http://www.apache.org/person/'}" )
.build();
2.4 - @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:
-
Hiding bean properties.
-
Specifying the ordering of bean properties.
-
Overriding the names of bean properties.
-
Associating transforms at both the class and property level (to convert non-serializable POJOs to
serializable forms).
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/'
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.PropertyNamerDLC} 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=PropertyNamerDLC.class )
public class Person {
...
Results
<per:person
xmlns ='http://www.apache.org/2013/Juneau'
xmlns:per ='http://www.apache.org/person/'
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.5 - 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 XmlSerializerBuilder().ws().sq().build();
// 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/'
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.6 - 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 describes.
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 XmlSerializerBuilder()
.ws()
.ns()
.sq()
.addNamespaceUrisToRoot(true )
.build();
// 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'
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.examples.addressbook.Person' />
<complexType name ='org.apache.juneau.examples.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.examples.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.examples.addressbook.Address_x003E_' >
<sequence>
<choice minOccurs ='0' maxOccurs ='unbounded' >
<element name ='address' _type ='juneau:org.apache.juneau.examples.addressbook.Address' />
<element name ='null' _type ='string' />
</choice>
</sequence>
</complexType>
<complexType name ='org.apache.juneau.examples.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/'
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.examples.addressbook.Address_x003E_' >
<sequence>
<choice minOccurs ='0' maxOccurs ='unbounded' >
<element name ='address' _type ='addr:org.apache.juneau.examples.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/' >
<impor t 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.examples.addressbook.Person' />
<complexType name ='org.apache.juneau.examples.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.examples.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.examples.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.7 - 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.Serializer#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.Serializer#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 XmlSerializerBuilder()
.ws()
.sq()
.ns()
.detectRecursions(true )
.build();
// 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.8 - Configurable properties
See the following classes for all configurable properties that can be used on this serializer:
- {@link org.apache.juneau.BeanContext} - Bean context properties.
- {@link org.apache.juneau.xml.XmlSerializerContext} - Serializer context properties.
2.9 - Other notes
-
Like all other Juneau serializers, the XML serializer is thread safe and maintains an internal cache of
bean classes encountered.
For performance reasons, it's recommended that serializers be reused whenever possible instead of
always creating new instances.
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:
- {@link org.apache.juneau.xml.XmlParser#DEFAULT}
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 XmlSerializerBuilder().ws().sq().ns().build();
// 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/'
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.2 - Configurable properties
See the following classes for all configurable properties that can be used on this parser:
- {@link org.apache.juneau.BeanContext} - Bean context properties.
- {@link org.apache.juneau.xml.XmlParserContext} - Parser context properties.
3.3 - Other notes
-
Like all other Juneau parsers, the XML parser is thread safe and maintains an internal cache of bean
classes encountered.
For performance reasons, it's recommended that parser be reused whenever possible instead of always
creating new instances.
*** fín ***