TOP

JAXB and CDATA Sections

Because I spended a few hours for that, I decided to write a small blog post about that. The challenge was to create a JAXB implementation independent solution for writing CDATA sections with a JAXB marshaller. There are a lot of solutions out there but they are not satisfying me. A lot of them reference the MOXy CDATA annotation or using the Apache XMLSerializer. They are either not implementation independent, does not work, reference deprecated code or have other pitfalls.  The following solution fixes these by using a custom implementation of javax.xml.stream.XMLStreamWriter and has not 3rd party library dependencies. Only the classes of the Java 6 JDK are used. The solution is able to create the following xml:

<test xmlns="http://www.example.org/test" msg="foo">
	<text><![CDATA[<o>]]></text>
	<text>foo</text>
</test>


It is also possible to validate the generated XML while marshalling it. That’s the schema used for the validation:

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org/test" xmlns:tns="http://www.example.org/test"
   elementFormDefault="qualified">
   <element name="test">
      <complexType>
         <sequence maxOccurs="unbounded">
            <element name="text" type="tns:AString" />
         </sequence>
         <attribute name="msg" type="string" />
      </complexType>
   </element>
 
   <simpleType name="AString">
      <restriction base="string">
         <maxLength value="3" />
      </restriction>
   </simpleType>
</schema>

That’s the simple class which creates the XML posted above.

import java.net.URI;
import java.util.List;
 
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
 
import org.example.ObjectFactory;
import org.example.Test;
import org.example.util.CDataXMLStreamWriter;
 
public class Main
{
   public static void main( String[] args ) throws JAXBException, XMLStreamException
   {
      JAXBContext jaxbContext = getContext( ObjectFactory.class );
      Marshaller marshaller = newInstanceMarshaller( jaxbContext, "test.xsd" );
 
      ObjectFactory objectFactory = new ObjectFactory();
      Test test = objectFactory.createTest();
      test.setMsg( "foo" );
      List<String> texts = test.getText();
      texts.add( "<o>" );
      texts.add( "foo" );
 
      XMLOutputFactory xof = XMLOutputFactory.newInstance();
      XMLStreamWriter streamWriter = xof.createXMLStreamWriter( System.out );
      CDataXMLStreamWriter cdataStreamWriter = new CDataXMLStreamWriter( streamWriter );
      marshaller.marshal( test, cdataStreamWriter );
      cdataStreamWriter.flush();
      cdataStreamWriter.close();
   }
 
   private static final JAXBContext getContext( final Class&lt;?&gt; jaxbContext ) throws JAXBException
   {
      return JAXBContext.newInstance( jaxbContext );
   }
 
   private static Marshaller newInstanceMarshaller( final JAXBContext jaxbContext, final String schemaLocation )
   {
      try
      {
         final Marshaller result = jaxbContext.createMarshaller();
 
         final SchemaFactory schemaFactory = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI );
         try
         {
            URI uri = Main.class.getResource( schemaLocation ).toURI();
            final Schema s = schemaFactory.newSchema( toStreamSources( uri ) );
            result.setSchema( s );
            result.setProperty( Marshaller.JAXB_FRAGMENT, true );
         }
         catch( Exception e )
         {
            throw new RuntimeException( e );
         }
         return result;
      }
      catch( JAXBException e )
      {
         throw new RuntimeException( "Exception occured creating JAXB unmarshaller for context=" + jaxbContext, e );
      }
   }
 
   private static Source[] toStreamSources( final URI stream )
   {
      return new Source[] { new StreamSource( stream.toString() ) };
   }
}

The class CDataXMLStreamWriter is just a delegate implementation of the interface XMLStreamWriter. See the following code:

package org.example.util;
 
import java.util.regex.Pattern;
 
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
 
/**
 * Implementation which is able to decide to use a CDATA section for a string.
 */
public class CDataXMLStreamWriter extends DelegatingXMLStreamWriter
{
   private static final Pattern XML_CHARS = Pattern.compile( "[&<>]" );
 
   public CDataXMLStreamWriter( XMLStreamWriter del )
   {
      super( del );
   }
 
   @Override
   public void writeCharacters( String text ) throws XMLStreamException
   {
      boolean useCData = XML_CHARS.matcher( text ).find();
      if( useCData )
      {
         super.writeCData( text );
      }
      else
      {
         super.writeCharacters( text );
      }
   }
}

Last but not least, the delegate class:

package org.example.util;
 
import javax.xml.namespace.NamespaceContext;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
 
/**
 * Delegating {@link XMLStreamWriter}.
 */
abstract class DelegatingXMLStreamWriter implements XMLStreamWriter
{
   private final XMLStreamWriter writer;
 
   public DelegatingXMLStreamWriter( XMLStreamWriter writer )
   {
      this.writer = writer;
   }
 
   public void writeStartElement( String localName ) throws XMLStreamException
   {
      writer.writeStartElement( localName );
   }
 
   public void writeStartElement( String namespaceURI, String localName ) throws XMLStreamException
   {
      writer.writeStartElement( namespaceURI, localName );
   }
 
   public void writeStartElement( String prefix, String localName, String namespaceURI ) throws XMLStreamException
   {
      writer.writeStartElement( prefix, localName, namespaceURI );
   }
 
   public void writeEmptyElement( String namespaceURI, String localName ) throws XMLStreamException
   {
      writer.writeEmptyElement( namespaceURI, localName );
   }
 
   public void writeEmptyElement( String prefix, String localName, String namespaceURI ) throws XMLStreamException
   {
      writer.writeEmptyElement( prefix, localName, namespaceURI );
   }
 
   public void writeEmptyElement( String localName ) throws XMLStreamException
   {
      writer.writeEmptyElement( localName );
   }
 
   public void writeEndElement() throws XMLStreamException
   {
      writer.writeEndElement();
   }
 
   public void writeEndDocument() throws XMLStreamException
   {
      writer.writeEndDocument();
   }
 
   public void close() throws XMLStreamException
   {
      writer.close();
   }
 
   public void flush() throws XMLStreamException
   {
      writer.flush();
   }
 
   public void writeAttribute( String localName, String value ) throws XMLStreamException
   {
      writer.writeAttribute( localName, value );
   }
 
   public void writeAttribute( String prefix, String namespaceURI, String localName, String value )
      throws XMLStreamException
   {
      writer.writeAttribute( prefix, namespaceURI, localName, value );
   }
 
   public void writeAttribute( String namespaceURI, String localName, String value ) throws XMLStreamException
   {
      writer.writeAttribute( namespaceURI, localName, value );
   }
 
   public void writeNamespace( String prefix, String namespaceURI ) throws XMLStreamException
   {
      writer.writeNamespace( prefix, namespaceURI );
   }
 
   public void writeDefaultNamespace( String namespaceURI ) throws XMLStreamException
   {
      writer.writeDefaultNamespace( namespaceURI );
   }
 
   public void writeComment( String data ) throws XMLStreamException
   {
      writer.writeComment( data );
   }
 
   public void writeProcessingInstruction( String target ) throws XMLStreamException
   {
      writer.writeProcessingInstruction( target );
   }
 
   public void writeProcessingInstruction( String target, String data ) throws XMLStreamException
   {
      writer.writeProcessingInstruction( target, data );
   }
 
   public void writeCData( String data ) throws XMLStreamException
   {
      writer.writeCData( data );
   }
 
   public void writeDTD( String dtd ) throws XMLStreamException
   {
      writer.writeDTD( dtd );
   }
 
   public void writeEntityRef( String name ) throws XMLStreamException
   {
      writer.writeEntityRef( name );
   }
 
   public void writeStartDocument() throws XMLStreamException
   {
      writer.writeStartDocument();
   }
 
   public void writeStartDocument( String version ) throws XMLStreamException
   {
      writer.writeStartDocument( version );
   }
 
   public void writeStartDocument( String encoding, String version ) throws XMLStreamException
   {
      writer.writeStartDocument( encoding, version );
   }
 
   public void writeCharacters( String text ) throws XMLStreamException
   {
      writer.writeCharacters( text );
   }
 
   public void writeCharacters( char[] text, int start, int len ) throws XMLStreamException
   {
      writer.writeCharacters( text, start, len );
   }
 
   public String getPrefix( String uri ) throws XMLStreamException
   {
      return writer.getPrefix( uri );
   }
 
   public void setPrefix( String prefix, String uri ) throws XMLStreamException
   {
      writer.setPrefix( prefix, uri );
   }
 
   public void setDefaultNamespace( String uri ) throws XMLStreamException
   {
      writer.setDefaultNamespace( uri );
   }
 
   public void setNamespaceContext( NamespaceContext context ) throws XMLStreamException
   {
      writer.setNamespaceContext( context );
   }
 
   public NamespaceContext getNamespaceContext()
   {
      return writer.getNamespaceContext();
   }
 
   public Object getProperty( String name ) throws IllegalArgumentException
   {
      return writer.getProperty( name );
   }
}

Leave a Reply

Your email is never published nor shared.

You may use these HTML tags and attributes:<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*