Sérialiser des dates avec Castor XML

Baptiste Autin, le 4 avril 2010

Castor est un outil léger et efficace pour faire du data binding Java / XML.
L’une des principales embûches sur laquelle on risque de tomber est la conversion des dates.
Castor propose un mécanisme permettant de personnaliser les opérations de sérialisation/désérialisation via une classe gestionnaire (handler). La solution la plus pratique que j’ai expérimentée consiste à étendre la classe GeneralizedFieldHandler, comme dans l’exemple ci-dessous.

Voici notre Java bean d’exemple :

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;
	}
}

Le fichier de mapping Castor /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>

Le fichier de données XML d’exemple (utilisé pour l’opération d’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>

La classe handler proprement dite :

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;
	}
}

Et enfin le fichier exécutable de test :

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;
	}
}

Les traces attendues lors à l’exécution de CastorTest :

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>

On remarque que la date volontairement erronée du troisième élément <version> de la liste <versions> a bien été prise en charge, et qu’on récupère un null à la place, comme prévu.
L’avantage d’étendre GeneralizedFieldHandler comme nous le faisons est que nous pouvons réutiliser notre classe DateHandler avec d’autres champs de type date. En revanche, si des formats de date différents sont attendus, il faudra implémenter ConfigurableFieldHandler.

Plus d’information :

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

Laisser une réponse

«     »