If you deal with legacy applications, especially those handling XML JMS messages, then at one point you will encounter the xml DataTypes (date/datetime).
As java.util.Date and java.util.Calender are considered legacy; we should aim to use java.time package instead.
For that we need to override the specified default bindings of XML Schema built-in datatypes by customizing JAXB Bindings.
We will cover how to achieve this by using legacy jaxb:bindings version=1 and jaxb:bindings version=2.1
Version 1: Define parseDate, printDate, parseDateTime, printDateTime methods in a single class.
The class will not extend/override anything. Its purpose is to convert between String to various java.time DateTypes and vice-versa.
public final class DateTypeConverter {
public static LocalDate parseDate(String inputDate) {
return inputDate != null ? DateTimeFormatter.ISO_DATE.parse(inputDate, LocalDate::from) : null;
}
public static String printDate(LocalDate inputDate) {
return inputDate != null ? DateTimeFormatter.ISO_DATE.format(inputDate) : null;
}
public static LocalDateTime parseDateTime(String inputDate) {
return inputDate != null ? DateTimeFormatter.ofPattern("yyyy-MM-ddTHH.mm.ss.SSS").parse(inputDate, LocalDateTime::from) : null;
}
public static String printDateTime(LocalDateTime inputDate) {
return inputDate != null ? DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(inputDate) : null;
}
}
Define a bindings.xjb file and <jaxb:globalBindings> section. The global bindings should look like below:
<?xml version="1.0"?>
<jaxb:bindings version="1.0" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
jaxb:extensionBindingPrefixes="xjc" xmlns:annox="http://annox.dev.java.net" xmlns:inheritance="http://jaxb2-commons.dev.java.net/basic/inheritance" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<jaxb:globalBindings typesafeEnumMemberName="generateName">
<jaxb:javaType name="java.time.LocalDateTime" xmlType="xs:dateTime" parseMethod="com.hello.adapter.border.util.DateTypeConverter.parseDateTime" printMethod="com.hello.adapter.border.util.DateTypeConverter.printDateTime" />
<jaxb:javaType name="java.time.LocalDate" xmlType="xs:date" parseMethod="com.hello.adapter.border.util.DateTypeConverter.parseDate" printMethod="com.hello.adapter.border.util.DateTypeConverter.printDate" />
</jaxb:globalBindings>
Version 2.1: Define 2 adapters. DateAdapter, and DateTimeAdapter. Each adapter will extend XMLAdapter and override the marshal/unmarshal methods:
package com.hello.adapter.border.util;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class LocalDateAdapter
extends XmlAdapter<String, LocalDate>
{
public LocalDate unmarshal(String inputDate) {
return inputDate != null ? DateTimeFormatter.ISO_DATE.parse(inputDate, LocalDate::from) : null ;
}
public String marshal(LocalDate inputDate) {
return inputDate != null ? DateTimeFormatter.ISO_DATE.format(inputDate) : null;
}
}
package com.hello.adapter.border.util;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class LocalDateTimeAdapter
extends XmlAdapter<String, LocalDateTime>
{
public LocalDateTime unmarshal(String inputDate) {
return inputDate != null ? DateTimeFormatter.ofPattern("yyyy-MM-ddTHH.mm.ss.SSS").parse(inputDate, LocalDateTime::from) : null;
}
public String marshal(LocalDateTime inputDate) {
return inputDate != null ? DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(inputDate) : null;
}
}
Define a bindings.xjb file and <jaxb:globalBindings> section. The global bindings should look like below:
<?xml version="1.0"?>
<jaxb:bindings version="2.1" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
jaxb:extensionBindingPrefixes="xjc" xmlns:annox="http://annox.dev.java.net" xmlns:inheritance="http://jaxb2-commons.dev.java.net/basic/inheritance" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<jaxb:globalBindings typesafeEnumMemberName="generateName">
<xjc:javaType name="java.time.LocalDate" xmlType="xs:date" adapter="com.hello.adapter.border.util.LocalDateAdapter" />
<xjc:javaType name="java.time.LocalDateTime" xmlType="xs:dateTime" adapter="com.hello.adapter.border.util.LocalDateTimeAdapter" />
</jaxb:globalBindings>
Finally, for either version, we simply need to point towards the bindings.xjb file in our maven-jaxb2-plugin configuration.
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>${maven-jaxb2-plugin.version}</version>
<executions>
<execution>
<id>generate-from-xml</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<generateDirectory>target/generated-sources/xjc</generateDirectory>
<schemaDirectory>${basedir}/src/main/resources/schemas/xml/xsd</schemaDirectory>
<schemaIncludes>
<include>**/*.xsd</include>
</schemaIncludes>
<bindingDirectory>${basedir}/src/main/resources/schemas/xml/xjb</bindingDirectory>
<bindingIncludes>
<include>**/*.xjb</include>
</bindingIncludes>
<plugins>
<plugin>
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-basics</artifactId>
<version>${jaxb2-basics.version}</version>
</plugin>
<plugin>
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-basics-annotate</artifactId>
<version>${jaxb2-basics-annotate.version}</version>
</plugin>
</plugins>
<args>
<arg>-Xannotate</arg>
<arg>-Xinheritance</arg>
</args>
</configuration>
</execution>
</executions>
</plugin>
After mvn clean install, all /target/generated/schema classes should contain LocalDate/LocalDateTime attributes, and a usage of the annotation @XmlJavaTypeAdapter
The following xml definition attributes,
<xs:element name="ApprovalDate" type="xs:date" minOccurs="0"/>
<xs:element name="SendTime" type="xs:dateTime">
will generate the following Java attributes:
If using jaxb:bindings version=1
@XmlElement(name = "ApprovalDate", type = String.class)
@XmlJavaTypeAdapter(Adapter2 .class)
@XmlSchemaType(name = "date")
protected LocalDate approvalDate;
@XmlElement(name = "SendTime", required = true, type = String.class)
@XmlJavaTypeAdapter(Adapter1 .class)
@XmlSchemaType(name = "dateTime")
protected LocalDateTime sendTime;
For version=1, we will get autogenerated Adapter1 and Adapter2 which use our final class with methods printDate/parseDate methods
while if using jaxb:bindings version=2.1, then there are no intermediate classes, and JAXB will point to our custom LocalDateAdapter and LocalDateTimeAdapters.
@XmlElement(name = "ApprovalDate", type = String.class)
@XmlJavaTypeAdapter(LocalDateAdapter.class)
@XmlSchemaType(name = "date")
protected LocalDate approvalDate;
@XmlElement(name = "SendTime", required = true, type = String.class)
@XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
@XmlSchemaType(name = "dateTime")
protected LocalDateTime sendTime;
Thank you very much. This article was really helpful to me.
Why did you use the Pattern “yyyy-MM-ddTHH.mm.ss.SSS”?
Why did you this:
DateTimeFormatter.ofPattern(“yyyy-MM-ddTHH.mm.ss.SSS”).parse(inputDate, LocalDateTime::from)
Instead of this:
DateTimeFormatter.ISO_LOCAL_DATE_TIME.parse(inputDate, LocalDateTime::from)
LikeLike
@Joel, This is because our application required it. Nothing else. You can use whatever pattern your application accepts/requires.
LikeLike