Date conversion with Castor XML

4 April 2010, by Baptiste Autin

Castor is a lightweight and efficient Java / XML data binding tool.
One of the first pitfalls you can fall into is conversion date.
Castor provides a mechanism to customize serialize / deserialize fields via a handler class. The most practical solution I’ve experienced is to extend the class GeneralizedFieldHandler, as in the example below.

Here is our Java bean instance:

package articles;

import java.util.Date;

public class Version {

	private String name;
	private Date releaseDate;

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Date getReleaseDate() {
		return releaseDate;
	}
	public void setReleaseDate(Date date) {
		this.releaseDate = date;
	}
}

The Castor mapping file /articles/mapping.xml:

<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN" "http://castor.org/mapping.dtd">
<mapping>
	<description></description>
	<class name="articles.Version">
		<map-to xml="version" />
		<field name="releaseDate" type="string" handler="articles.DateHandler">
			<bind-xml name="releaseDate" />
		</field>
		<field name="name" type="string">
			<bind-xml name="name" />
		</field>
	</class>
</mapping>

The sample XML file (for unmarshalling) /articles/history.xml:

<?xml version="1.0" standalone="yes"?>
<versions>
	<version>
		<name>JDK 1.1.4</name>
		<releaseDate>12-09-1997</releaseDate>
	</version>
	<version>
		<name>JDK 1.1.5</name>
		<releaseDate>03-12-1997</releaseDate>
	</version>
	<version>
		<name>JDK 1.1.6</name>
		<releaseDate>typo</releaseDate>
	</version>
</versions>

The handler class:

package articles; 

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.exolab.castor.mapping.GeneralizedFieldHandler;

public class DateHandler extends GeneralizedFieldHandler {

	private static final Log logger = LogFactory.getLog(DateHandler.class);

	private static final String FORMAT = "dd-MM-yyyy";

	private SimpleDateFormat formatter = new SimpleDateFormat(FORMAT);

	public Object convertUponGet(Object value) {
		if (value == null) {
			return "13-07-1974";	// default value if null date
		}
		Date date = (Date) value;
		return formatter.format(date);
	}

	public Object convertUponSet(Object value) {
		Date date = null;
		try {
			date = formatter.parse((String) value);
		}
		catch (ParseException px) {
			logger.error("Parse Exception (bad date format) : " + (String) value);
			return null;  // default value for empty/incorrect date
		}
		return date;
	}

	public Class<?> getFieldType() {
		return Date.class;
	}

	public Object newInstance(Object parent) throws IllegalStateException {
		return null;
	}
}

And finally the executable:

package articles;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Date;

import org.apache.log4j.Logger;
import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.Marshaller;
import org.exolab.castor.xml.Unmarshaller;
import org.exolab.castor.xml.ValidationException;
import org.xml.sax.InputSource;

public class CastorTest {

	private static Logger logger = Logger.getLogger(CastorTest.class);
	private static Mapping mapping = getMapping();

	final private static String MAPPING_FILE = "/articles/mapping.xml";

	public static void main(String[] args) throws MappingException, MarshalException, ValidationException, IOException {

		/**
		 * Unmarshalling (XML -> Java)
		 */

		Unmarshaller unmarshaller = new Unmarshaller(ArrayList.class);
		unmarshaller.setIgnoreExtraElements(true);
		unmarshaller.setMapping(mapping);

		String dataFile = CastorTest.class.getResource("/articles/history.xml").getPath();
		InputSource source = new InputSource(dataFile);
		ArrayList<Version> list = (ArrayList<Version>) unmarshaller.unmarshal(source);

		logger.debug("Unmarshalling :");
		for (Version item : list) {
			logger.debug("Name = " + item.getName());
			logger.debug("Date = " + item.getReleaseDate());
		}

		/**
		 * Marshalling (Java -> XML)
		 */

		Marshaller marshaller = new Marshaller();
		Writer writer = new StringWriter();

		marshaller.setWriter(writer);
		marshaller.setMapping(mapping);

		Version v = new Version();
		v.setName("New name");
		v.setReleaseDate(new Date());

		marshaller.marshal(v);

		logger.debug("Marshalling :");
		logger.debug(writer.toString());
	}

	static protected Mapping getMapping() {

		String mapFile = CastorTest.class.getResource(MAPPING_FILE).getPath();
		InputSource is = new InputSource(mapFile);

		Mapping mapping = new Mapping();
		mapping.loadMapping(is);

		return mapping;
	}
}

Expected traces at runtime :

DEBUG [main] (CastorTest.java:39) [] - Unmarshalling :
DEBUG [main] (CastorTest.java:41) [] - Name = JDK 1.1.4
DEBUG [main] (CastorTest.java:42) [] - Date = Fri Sep 12 00:00:00 CEST 1997
DEBUG [main] (CastorTest.java:41) [] - Name = JDK 1.1.5
DEBUG [main] (CastorTest.java:42) [] - Date = Wed Dec 03 00:00:00 CET 1997
DEBUG [main] (CastorTest.java:41) [] - Name = JDK 1.1.6
DEBUG [main] (CastorTest.java:42) [] - Date = null
(...)
DEBUG [main] (CastorTest.java:61) [] - Marshalling :
DEBUG [main] (CastorTest.java:62) [] - <?xml version="1.0" encoding="UTF-8"?>
<version><releaseDate>04-04-2011</releaseDate><name>New name</name></version>

Note how the deliberately erroneous date in the third <version> of the list has been handled, and how we get a null instead, as expected.
The advantage of extending GeneralizedFieldHandler, as we do, is that DateHandler can be reused for other date fields. However, if different date formats were expected, we would have to implement ConfigurableFieldHandler.

More information:
http://www.castor.org/xml-fieldhandlers.html

Laisser une réponse

«     »