Tuesday Oct 11, 2011

Slides from JavaOne BOF

Slides from my JavaOne BOF: "Best Practices for XML/Schema Databinding with JAXB"

[Read More]

Wednesday Nov 19, 2008

Handling extended mixed content in JAXB

Recently an issue came up to handle extended mixed content better with JAXB.

Just to clarify in the beginning, my opinion is that when somebody is designing a schema which is supposed to be machine processed (not something for some kind of UI rendering), it is a good decision to avoid use of mixed content, especially when designing a large schema with a lot of type extensions. Mapping that kind of schema to any binding framework is usually complex and leads to complications and slowdown of development. JAXB has never been designed to handle these cases in a convenient way - it is a Java <-> XML mapping framework, and it's not possible to represent this kind of content in a hierarchy of Java objects.

General recommendation for JAXB, if you come up to a corner case is to use customizations. If none of the many customizations help, you shall use the DOM customization (https://jaxb.dev.java.net/guide/Avoid_strong_databinding.html#Mapping_to_DOM) which is able to handle 100% schema structures, like this:


<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
jaxb:version="2.0">
<xs:element>
<xs:complexType>
<xs:sequence>
<xs:any maxOccurs="unbounded" processContents="skip">
<xs:annotation><xs:appinfo>
<jaxb:dom/>
</xs:appinfo></xs:annotation>
</xs:any>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

With this customization, the underlying contents are stored in DOM tree, and thus you get unlimited capabilities to modify/access the tree structure. You loose the convenient JAXB mapping approach of course.

As I mentioned in the beginning, one corner case is handling multiple extensions with mixed content.

Usual way how JAXB handles this, is to generate 'content' property as List where ? is the common denominator of the potential content. Then, using @XmlElementRefs and @XmlElementRef annotations, it specifies which elements are expected in the tree.


@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "MyBaseType", propOrder = {
"content"
})
@XmlSeeAlso({
MyExtendedType.class
})
public class MyBaseType {

@XmlElementRefs({
@XmlElementRef(name = "element2", type = JAXBElement.class),
@XmlElementRef(name = "element1", type = JAXBElement.class)
})
@XmlMixed
protected List content;

public List getContent() {
if (content == null) {
content = new ArrayList();
}
return this.content;
}
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "MyExtendedType")
public class MyExtendedType extends MyBaseType {
}

Since we are talking about mixed content, 'String' is one of the elements, so in the end you end up either with List<Object>, List<Serializable> or List<String>. Not very convenient, but still you can work with it in usual JAXB way.

However, the bigger problem comes when you do need to use a schema like this:


<xs:complexType name="BaseTypeWithElementsExtended" mixed="true">
<xs:sequence>
<xs:element name="element1" type="xs:string" />
<xs:element name="element2" type="xs:string" />
</xs:sequence>
</xs:complexType>

<xs:complexType name="ExtendedTypeWithElements" mixed="true">
<xs:complexContent mixed="true">
<xs:extension base="BaseTypeWithElementsExtended">
<xs:sequence>
<xs:element name="element3" type="xs:string" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>

<xs:complexType name="ExtendedType2" mixed="true">
<xs:complexContent mixed="true">
<xs:extension base="ExtendedTypeWithElements">
<xs:sequence>
<xs:element name="element4" type="xs:string" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>

<xs:element name="MultiExtendedTypeWithElements" type="ExtendedType2"/>

The main difference is that there are multiple extensions (all of them mixed of course). The usual JAXB approach doesn't work here, because JAXB generates this kind of content:


@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ExtendedType2")
public class ExtendedType2 extends ExtendedTypeWithElements {
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ExtendedTypeWithElements")
@XmlSeeAlso({
RestrictedType2 .class,
ExtendedType2 .class
})
public class ExtendedTypeWithElements extends BaseTypeWithElementsExtended {
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "BaseTypeWithElementsExtended", propOrder = {
"content"
})
@XmlSeeAlso({
RestrictedTypeWithElements.class,
ExtendedTypeWithElements.class
})
public class BaseTypeWithElementsExtended {
@XmlElementRefs({
@XmlElementRef(name = "element1", type = JAXBElement.class),
@XmlElementRef(name = "element2", type = JAXBElement.class)
})
@XmlMixed
protected List content;

public List getContent() {
if (content == null) {
content = new ArrayList();
}
return this.content;
}
}

Hmm, now how do you represent element3 or element4?

You've got the "content" property, which is supposed to hold the mixed content, but at the same time, you've got extensions which introduce another type of content. With Java, you can't put another "content" variable with another set of annotations to the subclasses. So, I decided to add one more global customization for those who cannot modify their schemas and need to handle this case directly through jaxb. The customization goes like this:


<?xml version="1.0" encoding="UTF-8"?>
<jaxb:bindings
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="2.0"
xmlns:xjc= "http://java.sun.com/xml/ns/jaxb/xjc" jaxb:extensionBindingPrefixes="xjc">
<jaxb:globalBindings generateMixedExtensions="true"/>
</jaxb:bindings>

When this customization is present (either inline or externally), the behaviour of JAXB changes slightly, and the code it generates for schema above looks like this:


@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ExtendedType2")
public class ExtendedType2 extends ExtendedTypeWithElements {

@XmlElementRefs({
@XmlElementRef(name = "element1", type = JAXBElement.class),
@XmlElementRef(name = "element2", type = JAXBElement.class),
@XmlElementRef(name = "element3", type = JAXBElement.class),
@XmlElementRef(name = "element4", type = JAXBElement.class)
})
@XmlMixed
@OverrideAnnotationOf
protected List contentOverrideForExtendedType2;
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ExtendedTypeWithElements")
@XmlSeeAlso({
RestrictedType2 .class,
ExtendedType2 .class
})
public class ExtendedTypeWithElements extends BaseTypeWithElementsExtended {

@XmlElementRefs({
@XmlElementRef(name = "element1", type = JAXBElement.class),
@XmlElementRef(name = "element2", type = JAXBElement.class),
@XmlElementRef(name = "element3", type = JAXBElement.class)
})
@XmlMixed
@OverrideAnnotationOf
protected List contentOverrideForExtendedTypeWithElements;
}

public class BaseTypeWithElementsExtended {
@XmlElementRefs({
@XmlElementRef(name = "element1", type = JAXBElement.class),
@XmlElementRef(name = "element2", type = JAXBElement.class)
})
@XmlMixed
protected List content;

public List getContent() {
if (content == null) {
content = new ArrayList();
}
return this.content;
}
}

There's a proprietary annotation to allow overriding the content property with a different content. When JAXB runtime finds the dummy content property, it modifies it's internal representation of the original 'content' property to allow a larger set of elements, as defined in the annotations.

The code is in current JAXB nightly builds, so give it a try and we can improve/modify it before next release based on your comments. Either send them to JAXB forums, or directly to me.

About

The views expressed on this [blog; Web site] are my own and do not necessarily reflect the views of Oracle.

Search

Archives
« April 2014
MonTueWedThuFriSatSun
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Today
Bookmarks