April 15, 2011

@XmlAnyElement and non-DOM Properties

This post will cover how to use an alternate DOM representation (i.e. String) with @XmlAnyElement.


XML (input.xml)

The following XML document will be used for this example.  Nested within the "bio" element is HTML that contains information about the customer.

<?xml version="1.0" encoding="UTF-8"?>
<customer>
    <name>Jane Doe</name>
    <bio>
        <html>...</html>
    </bio>
</customer>

Java Model

The following domain model will be used for this example.  Note how the "bio" property is of type String and not Element.  This can be done because we have supplied a DOMHandler class (through the @XmlAnyElement) annotation that specifies how String can be used to represent a DOM.

package blog.domhandler;

import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlRootElement
@XmlType(propOrder={"name", "bio"})
public class Customer {

    private String name;
    private String bio;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @XmlAnyElement(BioHandler.class)
    public String getBio() {
        return bio;
    }

    public void setBio(String bio) {
        this.bio = bio;
    }

}

DomHandler

Below is the implementation of the DomHandler.  Essentially it involves converting your DOM representation from a Source and to a Result.

package blog.domhandler;

import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.bind.ValidationEventHandler;
import javax.xml.bind.annotation.DomHandler;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

public class BioHandler implements DomHandler<String, StreamResult> {

    private static final String BIO_START_TAG = "<bio>";
    private static final String BIO_END_TAG = "</bio>";

    private StringWriter xmlWriter = new StringWriter(); 

    public StreamResult createUnmarshaller(ValidationEventHandler errorHandler) {
        return new StreamResult(xmlWriter);
    }

    public String getElement(StreamResult rt) {
        String xml = rt.getWriter().toString();
        int beginIndex = xml.indexOf(BIO_START_TAG) + BIO_START_TAG.length();
        int endIndex = xml.indexOf(BIO_END_TAG);
        return xml.substring(beginIndex, endIndex);
    }

    public Source marshal(String n, ValidationEventHandler errorHandler) {
        try {
            String xml = BIO_START_TAG + n.trim() + BIO_END_TAG;
            StringReader xmlReader = new StringReader(xml);
            return new StreamSource(xmlReader);
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }

}

jaxb.properties

There are bugs in both the Metro and EclipseLink MOXy implementations of JAXB related to the marshalling aspect of this example.  The MOXy issue has been fixed in the EclipseLink 2.3.0 stream and you can obtain a download from:
To specify MOXy as the JAXB implementation you must include a file called jaxb.properties in the same package as your domain model with the following entry:

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

Demo Code

package blog.domhandler;

import java.io.File;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Customer.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Customer customer = (Customer) unmarshaller.unmarshal(new File("src/blog/domhandler/input.xml"));

        System.out.println("Name:  " + customer.getName());
        System.out.println("Bio:   " + customer.getBio());

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(customer, System.out);
    }
}

Output

The following is the output from the demo code:

Name:  Jane Doe
Bio:   <html>...</html>
    
<?xml version="1.0" encoding="UTF-8"?>
<customer>
   <name>Jane Doe
   <bio>
      <html>...</html>
   </bio>
</customer> 

Further Reading

If you enjoyed this post you may also be interested in:

8 comments:

  1. following is more flexible :
    public StreamResult createUnmarshaller(ValidationEventHandler errorHandler) {
    xmlWrite.getBuffer().setLength(0);
    return new StreamResult(xmlWriter);
    }

    this work even if the bio element is a set.

    ReplyDelete
  2. Thank you for this post, very clear and precise. It actually saved me hours of work, as my knowledge of JAXB is still a little blurry.

    ReplyDelete
  3. Also if in source xml "bio" element may be empty, then in getElement method you should check:
    if(xml.indexOf("") > 0) return "";
    The reason is converting to by SAXParser (or by JAXB, not sure about that)

    ReplyDelete
  4. Hi,
    I use groovy/Grails tool Suite,
    When I am trying to run this example this error is trown out :
    unable to marshal type "java.lang.String" as an element because it is missing an @XmlRootElement annotation]

    ReplyDelete
    Replies
    1. Hi,

      Have you tried this in a plain Java environment? Also which implementation and version of JAXB are you using?

      -Blaise

      Delete
  5. I'm trying to make use of the above code for something similar except in my case the @XmlAccessorType value is used and set to XmlAccessType.FIELD

    The error that I'm getting is Property INTRODUCTION is present but not specified in @XmlType.propOrder

    The environment is Rational Software Architect for WebSphere, although I'm running this sample as just a simple java application.

    I'm not sure how to post the code here, otherwise I would do so.

    ReplyDelete

Note: Only a member of this blog may post a comment.