JAXB: Marshalling and Unmarshalling CDATA block

11 November 2012 By Nithya Vasudevan 18,754 views 3 Comments
14 Flares Twitter 4 Facebook 5 Google+ 5 14 Flares ×

Project Description

  • In this JAXB tutorial we will see an example on how to marshal and unmarshal CDATA block using Sun built-in default JAXB implementation, i.e.) JAXB Reference Implementation (JAXB RI).
  • CDATA sections may occur anywhere character data may occur; they are used to escape blocks of text containing characters which would otherwise be recognized as markup. CDATA sections begin with the string <![CDATA[ and end with the string ]]>
  • JAXB automatically escapes the content inside the tags. But in some cases we may want to place the content as it is, inside the tags using CDATA instead of escaping it.
  • When using default JAXB RI implementation, we have to create a custom adapter for handling CDATA block.
  • An alternate method is to use MOXy JAXB implementation which is available as part of Eclipselink project. When using this implementation, there is no need to write custom adapter and just an annotation is sufficient to handle CDATA.

Environment Used:

  • JDK 6 (Java SE 6) or later.
  • Eclipse Indigo IDE for Java EE Developers.

New project in Eclipse

Create a new Java project in Eclipse IDE and name it as JAXBCData.

Create CData Adapter class

To wrap the content in CDATA element and to avoid escaping, we need to specify a custom adapter for text to surround the value in a CDATA element as shown below.

Create a new Class “CDATAAdapter.java” in the package “com.theopentutorials.jaxb.xml” and copy the following code.

package com.theopentutorials.jaxb.xml;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class CDATAAdapter extends XmlAdapter<String, String> {

	@Override
	public String marshal(String v) throws Exception {
		return "<![CDATA[" + v + "]]>";
	}

	@Override
	public String unmarshal(String v) throws Exception {
		return v;
	}
}

Create Bean class

Create a new Class “Book.java” in the package “com.theopentutorials.jaxb.to” and copy the following code.

package com.theopentutorials.jaxb.to;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import com.theopentutorials.jaxb.xml.CDATAAdapter;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "book")
public class Book {

	private String name;
	private String author;
	private String publisher;
	private String isbn;
	
	@XmlJavaTypeAdapter(value=CDATAAdapter.class)
	private String description;

	public String getName() {
		return name;
	}

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

	public String getAuthor() {
		return author;
	}

	public void setAuthor(String author) {
		this.author = author;
	}

	public String getPublisher() {
		return publisher;
	}

	public void setPublisher(String publisher) {
		this.publisher = publisher;
	}

	public String getIsbn() {
		return isbn;
	}

	public void setIsbn(String isbn) {
		this.isbn = isbn;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	@Override
	public String toString() {
		return "Book [name=" + name + ", author=" + author + ", publisher="
			+ publisher + ", isbn=" + isbn + ", description=" + description
			+ "]";
	}	
}

This is a simple bean class containing JAXB annotations whose object is marshalled and unmarshalled. The ‘description’ property is annotated with @XmlJavaTypeAdapter(value=CDATAAdapter.class) which is used for specifying custom adapter.

Create JAXB Handler (Helper class)

Create a new Class “JAXBXMLHandler.java” in the package “com.theopentutorials.jaxb.xml” and copy the following code.

package com.theopentutorials.jaxb.xml;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

import com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler;
import com.theopentutorials.jaxb.to.Book;

public class JAXBXMLHandler {

	public static void marshal(Book book, File selectedFile)
			throws IOException, JAXBException {
		JAXBContext context;
		BufferedWriter writer = null;
		try {
			writer = new BufferedWriter(new FileWriter(selectedFile));
			context = JAXBContext.newInstance(Book.class);
			Marshaller m = context.createMarshaller();
			m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
			m.setProperty("com.sun.xml.internal.bind.characterEscapeHandler",
				new CharacterEscapeHandler() {
					@Override
					public void escape(char[] ch, int start, int length,
							boolean isAttVal, Writer writer)
							throws IOException {
						writer.write(ch, start, length);
					}
				});
			m.marshal(book, writer);
		} finally {
			try {
				writer.close();
			} catch (IOException io) { /* ignore */
			}
		}
	}

	public static Book unmarshal(File importFile) throws JAXBException {
		Book book = new Book();
		JAXBContext context;

		context = JAXBContext.newInstance(Book.class);
		Unmarshaller um = context.createUnmarshaller();
		book = (Book) um.unmarshal(importFile);

		return book;
	}
}
  • This is a helper class which has methods to perform marshalling and unmarshalling. These methods are called from client code (in this case, main() method).
  • By default, the marshaller implementation of the JAXB RI tries to escape characters.
  • To change this behavior, we do the following;
    • Write a class that implements the com.sun.xml.bind.marshaller.CharacterEscapeHandler interface. This interface definition is shown below.
      public interface CharacterEscapeHandler { /** * @param ch The array of characters. * @param start The starting position. * @param length The number of characters to use. * @param isAttVal true if this is an attribute value literal. */ void escape( char[] ch, int start, int length, boolean isAttVal, Writer out ) throws IOException; }
    • Create a new instance of the implemented class.
    • Set that instance to the Marshaller property by one of the following ways.

      m.setProperty ( “com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler”, instance );
      or
      m.setProperty(CharacterEscapeHandler.class.getName(), instance);
      or
      m.setProperty(“com.sun.xml.internal.bind.characterEscapeHandler”, instance);

      These properties are vendor specific, works only with JAXB RI and won’t work with other implementations like Moxy.

    • We have used “Anonymous Inner Type” (refer lines 28-37 in above code) instead of giving the instance of a class that implements CharacterEscapeHandler.

If you have any error importing the class com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler in Eclipse, follow this step.
Right click on Project -> Properties -> Java Build Path from left pane -> Libraries from right pane. Select Java System Library and click on Edit and select “Workspace default JRE” and click on Finish or remove the JRE and add it again.

Create Java Application client (main())

Create a new Class “JAXBCDataDemo.java” in the package “com.theopentutorials.jaxb.main” and copy the following code.

package com.theopentutorials.jaxb.main;

import java.io.File;
import java.io.IOException;
import javax.xml.bind.JAXBException;

import com.theopentutorials.jaxb.to.Book;
import com.theopentutorials.jaxb.xml.JAXBXMLHandler;

public class JAXBCDataDemo {
	public static void main(String[] args) {

		Book book = new Book();
		book.setAuthor("Kathy Sierra");
		book.setName("SCJP");
		book.setPublisher("Tata McGraw Hill");
		book.setIsbn("856-545456736");

		String desc = "<p>With hundreds of practice questions and hands-on exercises, "
				+ "<b>SCJP Sun Certified Programmer for Java 6 Study Guide</b> covers " +
				"what you need to know"
				+ "--and shows you how to prepare--for this challenging exam. </p>";
		book.setDescription(desc);

		try {
			//Marshalling: Writing Java object to XML file
			JAXBXMLHandler.marshal(book, new File("book.xml"));
			
			//Unmarshalling: Converting XML content to Java objects
			Book book2 = JAXBXMLHandler.unmarshal(new File("book.xml"));
			System.out.println(book2);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (JAXBException e) {
			e.printStackTrace();
		}
	}
}
  • This class creates book object with description property containing HTML tags (description tag contains content which can have html tags so we put that content in CDATA) and calls marshal method from JAXBXMLHandler helper class passing book object and the file to write the object.
  • We call unmarshal method passing the marshalled file name which returns a book object.
  • Finally, printing the book object (which calls toString() from Book class).

Output

Run the JAXBCDataDemo.java.

Book [name=SCJP, author=Kathy Sierra, publisher=Tata McGraw Hill, isbn=856-545456736, description=
<p>With hundreds of practice questions and hands-on exercises, <b>SCJP Sun Certified Programmer for Java 6 Study Guide</b> covers what you need to know--and shows you how to prepare--for this challenging exam. </p>]

Refresh your project in Project Explorer (press F5 on your project) to see the generated XML file.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<book>
    <name>SCJP</name>
    <author>Kathy Sierra</author>
    <publisher>Tata McGraw Hill</publisher>
    <isbn>856-545456736</isbn>
    <description><![CDATA[<p>With hundreds of practice questions 
        and hands-on exercises, <b>SCJP Sun Certified Programmer 
        for Java 6 Study Guide</b> covers what you need to know--
        and shows you how to prepare --for this challenging exam. </p>]]>
    </description>
</book>

Folder Structure

The complete folder structure of this example is shown below.

JAXB RI default behavior

As mentioned above, the default marshaller implementation of the JAXB RI tries to escape characters.
To check this, follow these steps.

  • From the Book class, comment line 18 (i.e.) @XmlJavaTypeAdapter(value=CDATAAdapter.class) annotation )
  • From JAXBXMLHandler class, comment line 28-37, i.e.) CharacterEscapeHandler property.
  • Run you application and refresh your project. Open generated XML file (book.xml) to see the output.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<book>
    <name>SCJP</name>
    <author>Kathy Sierra</author>
    <publisher>Tata McGraw Hill</publisher>
    <isbn>856-545456736</isbn>
    <description>&lt;p&gt;With hundreds of practice questions 
        and hands-on exercises, &lt;b&gt;SCJP Sun Certified Programmer 
        for Java 6 Study Guide&lt;/b&gt; covers what you need to know--
        and shows you how to prepare--for this challenging exam. &lt;/p&gt;
    </description>
</book>

Tags: , , , ,

  • fokot

    You helped me. And actually most tutorials suggest to use something proprietary to JAXB implementation. Thanks :)

  • Dirk Dittert

    Unfortunately, your example is severly broken: Your adapter does not handle CDATA end tags ]]> properly. This breaks the generated XML if that string is contained in the input.

    More severely, the CharacterEscapeHandler instance turns off escaping completely for all tags. As a consequence you must use CDATA tags for all members that might contain special charactes like (greater than).

    Unfortunately, there there seems to be no feasible way to use escaping for some tags and CDATA for others with your approach. The only workaround would be something like parsing the char array in the escape method of the CharacterEscapeHandler to check whether it starts with a CDATA tag but I’d rather not recommend to go that way.

  • David

    Hi

    I tried your example, but it doesn’t seem to work if I skip the marshalling part in main and do only the unmarshalling part. So my main is only like the following

    Book book2 = JAXBXMLHandler.unmarshal(new File(“book.xml”));

    System.out.println(book2);
    book2.getDescription(); <– is null, I thought it should print out whatever in the CDATA