8

I am working on an application in which we give call to Third Party SAP system using files generated through wsdl using Spring webservices.

One of the file generated using wsdl through ws import have Date attribute of type "XMLGregorianCalendar" and in response we are getting null value for corresponding field .

I want to convert date from XmlGregorianCalendar to java.util.Date.

Have referred : how replace XmlGregorianCalendar by Date? but not able to provide appropriate xjb bindings through wsdl.

If anyone can suggest the conversion of Dates generated by wsdl ,it would be of great help..... Thanks In Advance ! Shuchi

Community
  • 1
  • 1
shuchi
  • 81
  • 1
  • 1
  • 3
  • 3
    FYI… `java.util.Date` is supplanted by [`java.time.Instant`](https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html) in Java 8 and later, both representing a moment on the timeline in [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time). And [`XMLGregorianCalendar`](https://docs.oracle.com/javase/8/docs/api/javax/xml/datatype/XMLGregorianCalendar.html) maps more appropriately to [`java.time.ZonedDateTime`](http://docs.oracle.com/javase/8/docs/api/java/time/ZonedDateTime.html) to retain time zone info. Convert: `myXmlGregCal.toGregorianCalendar().toZonedDateTime()` – Basil Bourque Nov 11 '16 at 19:15

6 Answers6

19

WSDL has no deal with xjb. xjb is for xjc compiler passed as -b parameter. i.e.

xjc -b <file>

documentation: Customizing JAXB Binding

if you use Maven plugins to generate your JAXB Java classes any of them have Binding configuration i.e.

<groupId>org.apache.cxf</groupId>
                <artifactId>cxf-codegen-plugin</artifactId>
                <configuration>
                    <defaultOptions>
                        <bindingFiles>
                            <bindingFile>${project.interfaces.basedir}Configuration/Bindings/common-binding.xjb</bindingFile>
                        </bindingFiles>

or

<plugin>
            <groupId>org.jvnet.jaxb2.maven2</groupId>
            <artifactId>maven-jaxb2-plugin</artifactId>
            <configuration>
                <schemaDirectory>${basedir}/src/main/resources/XMLSchema</schemaDirectory>
                <bindingDirectory>${basedir}/src/main/resources/Bindings</bindingDirectory>
            </configuration>

and so on...

xjb for it is very simple:

<jaxb:bindings xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="2.0"
xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
jaxb:extensionBindingPrefixes="xjc">
<jaxb:globalBindings>
    <jaxb:serializable uid="1" />
    <jaxb:javaType name="java.util.Calendar" xmlType="xsd:dateTime"
        parseMethod="javax.xml.bind.DatatypeConverter.parseDateTime"
        printMethod="javax.xml.bind.DatatypeConverter.printDateTime" />
    <jaxb:javaType name="java.util.Calendar" xmlType="xsd:date"
        parseMethod="javax.xml.bind.DatatypeConverter.parseDate" printMethod="javax.xml.bind.DatatypeConverter.printDate" />
    <jaxb:javaType name="java.util.Calendar" xmlType="xsd:time"
        parseMethod="javax.xml.bind.DatatypeConverter.parseTime" printMethod="javax.xml.bind.DatatypeConverter.printTime" />            
</jaxb:globalBindings>

as you can see it defines conversions from xsd:dateTime, xsd:date and xsd:time types to java.util.Calendar.

I do not recommend to use java.util.Date. There are many troubles with Date handling (especially with different timeZones). It is better to use java.util.Calendar. Calendar is much easier to handle and default converter implementation is there in JDK:

javax.xml.bind.DatatypeConverter

But, if you still want to use java.Util.Date you need to have your own small converter with two static methods "parse" and "print" and then set it in xjb. i.e.

public class MyDateConverter {
    public static java.util.Date parse(String xmlDateTime) {
        return javax.xml.bind.DatatypeConverter.parseDateTime(xmlDateTime).getTime();
    }

    public static String print(Date javaDate) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(javaDate.getTime());
        return javax.xml.bind.DatatypeConverter.printDateTime(calendar);
    }
}

your conversions in xjb will look like:

<jaxb:javaType name="java.util.Date" xmlType="xsd:dateTime"
    parseMethod="MyDatatypeConverter.parse"
    printMethod="MyDatatypeConverter.print" />
Community
  • 1
  • 1
Vadim
  • 4,027
  • 2
  • 10
  • 26
  • 3
    “WSDL has no deal with xjb.” Not sure what you mean by that. wsimport invokes xjc. wsimport accepts **-b** *bindingsfile* arguments which are passed to xjc. – VGR Nov 11 '16 at 14:52
  • WSDL - WebService Definition Language. That's it. xjb is specific for only one implementation in Java - JAXB, when JAXB generates Java classes. There are more implementations for Java: Axis, Axis2, XBeans and some others... WSDL is for everybody to implement. C, C++, C# developers have no idea about xjb. Enven those who use Axis2 either. :) – Vadim Nov 11 '16 at 14:56
  • Ah, I understand. Your point was that WSDL itself is language neutral. – VGR Nov 11 '16 at 14:58
  • 1
    yes exactly right. Actually I was wondering first time, why JAXB uses XmlGregorianCalendar by default instead of just Calendar. :) – Vadim Nov 11 '16 at 15:11
  • Because an XML schema has many date-time types: dateTime, date, time, gYear, gYearMonth, gMonth, gMonthDay, gDay. Calendar can only represent a full date-time, not partial information. – VGR Nov 11 '16 at 15:19
  • I do not agree. java.util.Date is the same. Java does not have Time type or any others. xml:date type can be represent in Java only by Date, Calendar, long or String. xml:date in java is only java.util.Date or Calendar with no Time portion (i.e. 2016-11-11T00:00:00.000) and so on... – Vadim Nov 11 '16 at 15:24
2

You have to create a custom Data type adaptor and add in binding file.

 <jaxb:globalBindings>
    <xjc:serializable uid="-6026937020915831338" />
    <xjc:javaType name="java.util.Date" xmlType="xs:date" 
          adapter="com.test.util.jaxb.DateDataTypeAdapter" />
  </jaxb:globalBindings>
</jaxb:bindings>

Class DateDataTypeAdapter

package com.test.util.jaxb;

import java.util.Calendar;
import java.util.Date;
import javax.xml.bind.DatatypeConverter;

public final class DataTypeAdapter {
    private DataTypeAdapter() { }

    public static Date parseDate(String s) {
        if (s == null) {
            return null;
        }

        return DatatypeConverter.parseDate(s).getTime();
    }

    public static String printDate(Date dt) {
        if (dt == null) {
            return null;
        }

        Calendar c = Calendar.getInstance();
        c.setTime(dt);

        return DatatypeConverter.printDate(c);
    }

    public static Date parseDateTime(String s) {
        if (s == null) {
            return null;
        }

        return DatatypeConverter.parseDateTime(s).getTime();
    }

    public static String printDateTime(Date dt) {
        if (dt == null) {
            return null;
        }

        Calendar c = Calendar.getInstance();
        c.setTime(dt);

        return DatatypeConverter.printDateTime(c);
    }
}
sahoora
  • 245
  • 1
  • 5
  • Thanks for your reply ......As I am new to this can you please help me how can I associate this globalBindings with the wsdl so that the Date returned from thirdPartySap get mapped to java.util.Date inspite of XMLGregorianCalendar Date type. – shuchi Nov 11 '16 at 14:25
  • You have to pass the binding file as parameter to wsdl2java utility. -b binding.xml – sahoora Nov 11 '16 at 14:35
2

The @vadim answer worked for me with a few additional details...

I was using spring boot 1.5.3 and the 2.3.1 version of the jaxb2-maven-plugin, and in that case I had to declare my xjb file as follows:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jaxb2-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>xjc</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <packageName>...</packageName>
        <sources>
            <source>src/main/resources/file.xsd</source>
        </sources>
        <xjbSources>
            <xjbSource>src/main/resources/file.xjb</xjbSource>
        </xjbSources>
        <addGeneratedAnnotation>true</addGeneratedAnnotation>
        <locale>es</locale>
    </configuration>
</plugin>

In my case the xjb file content was:

<jaxb:bindings xmlns:xsd="http://www.w3.org/2001/XMLSchema"
               xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" 
               jaxb:version="2.0"
               xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
               jaxb:extensionBindingPrefixes="xjc">
  <jaxb:globalBindings>
    <jaxb:serializable uid="1"/>
    <jaxb:javaType name="java.util.Date" xmlType="xsd:dateTime"
        parseMethod="package.path.bind.DataTypeConverter.parse"
        printMethod="package.path.bind.DataTypeConverter.print"/>
  </jaxb:globalBindings>
</jaxb:bindings>

And the DataTypeConverter content was:

import javax.xml.bind.DatatypeConverter;
import java.util.Calendar;
import java.util.Date;

public class DataTypeConverter {

    public static Date parse(String isoFormatDatetime) {
        return DatatypeConverter.parseDateTime(isoFormatDatetime).getTime();
    }
    public static String print(Date date) {
        return DatatypeConverter.printDateTime(toCalendar(date));
    }

    private static Calendar toCalendar(Date date){
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        return cal;
    }
}

Hope this helps someone!! :)

Julio Villane
  • 994
  • 16
  • 28
  • 1
    Yes you are right. Different plugins use different configuration settings to give xjb to the xjc compiler. You use `jaxb2-maven-plugin` which generates JAXB classes right out of XSD not Web Service classes from WSDL. I use it as well when I need classes from XSD. But for WSDL it depends on what Web Service implementation I need to use. e.g. CXF in my example... – Vadim Mar 19 '18 at 22:57
  • 1
    Yes, that's why I voted up your answer, but maybe someone in a similar case could find useful this specification :) – Julio Villane Mar 21 '18 at 06:31
1

This is probably a bit of a hack, but it works.

After generating your code with wsimport, you can do a find and replace in files, replacing all references to XmlGregorianCalendar with java.util.Date. JAXB will happily do all the processing for you and automagically do the conversions. No adapters needed. I haven't ran into any problems using this method.

spy
  • 3,199
  • 1
  • 18
  • 26
  • 1
    If you down vote this, at least have the courtesy to comment why. This method works for Date and Calendar. – spy Feb 13 '18 at 21:17
  • 3
    I did not down vote, but main bad point here is: "After generating ... find and replace in files". It means you need to generate classes once, then place them somewhere in the source and then do not touch them. It requires do it over and over again when XSD changes. It kicks build process out of any automation provided by Maven, Jenkins etc. Xjb is available exactly for that purpose. JAXB classes generation project does not need to hold generated classes. They can regenerate as many times as needed. All code/projects which use them just need a Maven dependency on that project. – Vadim Mar 19 '18 at 22:51
  • True, I've always found it a frequent need to modify generated code, especially jaxb – spy Mar 19 '18 at 22:59
  • I already have to do this kind of hack to replace checked exception with runtime exception in the generated client code of an awful API. I'm using the groovy-maven-plugin to run arbitrary code between the generation and the compilation. I'll do this suggestion for the date: it'll look even less hackish than messing around with the xjb file ! – Guillaume Dec 19 '18 at 15:20
  • 1
    in @spy's defense, Kohsuke suggest the same exact solution back in 2006: https://web.archive.org/web/20170302003913/https://community.oracle.com/blogs/kohsuke/2006/03/22/how-do-i-map-xsdate-javautildate – Mahmoud Mar 22 '22 at 16:29
1

I have change the bean's java type from XMLGregorianCalendar adding the configuration for jaxb directly on the xsd's contracts.

I have done like this, note the xs:annotation:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://hello.eomm.it/springws"
targetNamespace="http://hello.eomm.it/springws" elementFormDefault="qualified" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
jaxb:version="2.0">

<xs:annotation> 
    <xs:appinfo>
        <jaxb:globalBindings>
            <jaxb:serializable uid="1" />
            <jaxb:javaType name="java.util.Calendar" xmlType="xs:dateTime"
                parseMethod="javax.xml.bind.DatatypeConverter.parseDateTime" printMethod="javax.xml.bind.DatatypeConverter.printDateTime" />
            <jaxb:javaType name="java.util.Calendar" xmlType="xs:date"
                parseMethod="javax.xml.bind.DatatypeConverter.parseDate" printMethod="javax.xml.bind.DatatypeConverter.printDate" />
            <jaxb:javaType name="java.util.Calendar" xmlType="xs:time"
                parseMethod="javax.xml.bind.DatatypeConverter.parseTime" printMethod="javax.xml.bind.DatatypeConverter.printTime" />
        </jaxb:globalBindings>
    </xs:appinfo>
</xs:annotation>


<xs:element name="getCountryRequest">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="name" type="xs:string" />
        </xs:sequence>
    </xs:complexType>
</xs:element>


<xs:element name="getCountryResponse">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="country" type="tns:country" />
        </xs:sequence>
    </xs:complexType>
</xs:element>

<xs:complexType name="country">
    <xs:sequence>
        <xs:element name="name" type="xs:string" />
        <xs:element name="population" type="xs:int" />
        <xs:element name="capital" type="xs:string" />
        <xs:element name="foundation" type="xs:date" />
        <xs:element name="currency" type="tns:currency" />
    </xs:sequence>
</xs:complexType>

[...]

It is also needed to add the -Djavax.xml.accessExternalSchema=all parameter on JVM when you run the maven builds.

Manuel Spigolon
  • 11,003
  • 5
  • 50
  • 73
1

Thanks to Vadim's answer, I ended up using LocalDate and LocalDateTime:

xjb:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<jaxb:bindings version="3.0" xmlns:jaxb="https://jakarta.ee/xml/ns/jaxb"
    xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" xmlns:xs="http://www.w3.org/2001/XMLSchema"
    jaxb:extensionBindingPrefixes="xjc">

    <jaxb:globalBindings>
        <xjc:serializable uid="-1" />
        
        <jaxb:javaType
            name="java.time.LocalDateTime"
            xmlType="xs:dateTime"
            parseMethod="JaxbDateConverter.parseDateTime"
            printMethod="JaxbDateConverter.printDateTime"
        />
        
        <jaxb:javaType
            name="java.time.LocalDate"
            xmlType="xs:date"
            parseMethod="JaxbDateConverter.parseDate"
            printMethod="JaxbDateConverter.printDate"
        />
    
        <jaxb:javaType
            name="java.time.LocalDate"
            xmlType="xs:gYearMonth"
            parseMethod="JaxbDateConverter.parseDate"
            printMethod="JaxbDateConverter.printDate"
        />
    
        <jaxb:javaType
            name="java.time.LocalDate"
            xmlType="xs:gYear"
            parseMethod="JaxbDateConverter.parseDate"
            printMethod="JaxbDateConverter.printDate"
        />
    
        <jaxb:javaType
            name="java.time.MonthDay"
            xmlType="xs:gMonthDay"
            parseMethod="JaxbDateConverter.parseMonthDay"
            printMethod="JaxbDateConverter.printMonthDay"
        />
    </jaxb:globalBindings>
</jaxb:bindings>

JaxbDateConverter:

import jakarta.xml.bind.DatatypeConverter;
import lombok.val;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Calendar;

public class JaxbDateConverter {
    public static LocalDateTime parseDateTime(String xml) {
        val calendar = DatatypeConverter.parseDateTime(xml);
        val tz = ZoneId.systemDefault();
        val res = LocalDateTime.ofInstant(calendar.toInstant(), tz);
        
        return res;
    }
    
    public static String printDateTime(LocalDateTime date) {
        val calendar = Calendar.getInstance();
        val tz = ZoneId.systemDefault();
        val zoneOffSet = tz.getRules().getOffset(date);
        val ms = date.toEpochSecond(zoneOffSet) * 1000;
        calendar.setTimeInMillis(ms);
        val res = DatatypeConverter.printDateTime(calendar);
        
        return res;
    }
    
    public static LocalDate parseDate(String xml) {
        val calendar = DatatypeConverter.parseDate(xml);
        val tz = ZoneId.systemDefault();
        val res = LocalDate.ofInstant(calendar.toInstant(), tz);
        
        return res;
    }
    
    public static String printDate(LocalDate date) {
        val calendar = Calendar.getInstance();
        val tz = ZoneId.systemDefault();
        val dateTime = date.atStartOfDay();
        val zoneOffSet = tz.getRules().getOffset(dateTime);
        val timeStartOfDay = LocalTime.of(0, 0, 0);
        val ms = date.toEpochSecond(timeStartOfDay, zoneOffSet) * 1000;
        calendar.setTimeInMillis(ms);
        val res = DatatypeConverter.printDate(calendar);
        
        return res;
    }

    public static MonthDay parseMonthDay(String xml) {
        val res = MonthDay.parse(xml);
        
        return res;
    }
    
    public static String printMonthDay(MonthDay monthDay) {
        val res = monthDay.toString();
        
        return res;
    }
}
Marco Sulla
  • 15,299
  • 14
  • 65
  • 100