JAXB 2, zabawy z datą i konwersją typów

Jedną z bolączek JAXB jest problematyczna obsługa dat i czasów. Przypomnijmy sobie schemat użyty w jednej z wcześniejszych not.

 <!-- Definicja książki -->
 <complextype name="Book">
 <sequence>
 <element name="title" type="string" minOccurs="1" />
 <element name="isbn" type="string" />
 <element name="releaseDate" type="date" />
 <element name="author" type="tns:Author" maxOccurs="unbounded" />
 </sequence>
 </complextype>

Jak widać, każda książka ma określoną datę wydania. Jakkolwiek typ date z XML Schema nie jest bezpośrednio odwzorowany do java.util.Date czy też java.util.Calendar. W przypadku implementacji dostarczonej przez Sun mamy do czynienia z obiektami klasy XMLGregorianCalendarImpl. Rozwiązaniem problemu jest dodanie odpowiedniego adaptera. Możemy robić to ręcznie ale najwygodniejszym rozwiązaniem będzie skorzystanie z gotowca - klasy javax.xml.bind.DatatypeConverter. Jej użycie wygląda następująco:

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://dywicki.pl/court"
 xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="2.0"
 xmlns:tns="http://dywicki.pl/court" elementFormDefault="qualified">
 <annotation>
 <appinfo>
 <jaxb:globalBindings>
 <jaxb:javaType name="java.util.Calendar" xmlType="date"
 parseMethod="javax.xml.bind.DatatypeConverter.parseDate"
 printMethod="javax.xml.bind.DatatypeConverter.printDate" />
 </jaxb:globalBindings>
 </appinfo>
 </annotation>

 <!-- tutaj deklaracje typów -->
</schema>

Podobne rozwiązanie można wykorzystać na poziomie pojedyńczego elementu aby zmapować konkretny element do obiektu. Element annotation można również dodać do deklaracji simpleType (powiedzmy reprezentacja numeru pesel + proste restrykcje).

 <complextype name="File">
 <sequence>
 <element name="category" type="string">
 <annotation>
 <appinfo>
 <jaxb:javaType
 name="pl.dywicki.court.Category"
 parseMethod="pl.dywicki.court.Category.parse"
 printMethod="pl.dywicki.court.Category.print"
 />
 </appinfo>
 </annotation>
 </element>
 </sequence>
 </complextype>

JAXB wygeneruje wówczas odpowiednie adnotacje dla tworzonych klas oraz adaptery, które odwołują się do metod wskazanych w atrybutach elementu javaType.

W moim przypadku kod klasy Category wygląda następująco:

package pl.dywicki.court;

/**
 * Reprezentacja kategorii sprawy.
 *
 * @author Lukasz Dywicki <a href="mailto:luke@code-house.net">luke@code-house.net</a>
 **/
public class Category {

 private String name;

 public Category(String value) {
 this.name = value;
 }

 public static Category parse(String value) {
 return new Category(value);
 }

 public static String print(Category value) {
 return value.name;
 }
}

Jest to oczywiście maksymalnie uproszczony przykład. W konstruktorze bądź metodach, które konwertują string do obiektu możemy podpiąć walidację i bronić się wyjątkami przed nieprawidłowymi wartościami by uniemożliwić deserializację dokumentu.