SAXParseException: value is not a valid value for 'date'

usr-local-ΕΨΗΕΛΩΝ picture usr-local-ΕΨΗΕΛΩΝ · Jul 3, 2013 · Viewed 18.2k times · Source

I have an object tree of POJOs that represents an XML Schema. This was created with the following jaxb ant script.

I want to validate the root POJO and its children entities against the schema for missing attributes.

My code is the following: (try/catch block omitted, inspired by SO question How to validate against schema in JAXB 2.0 without marshalling?)

public boolean validateAgainstSchema(Pojo pojo)
{
        JAXBContext jc;
        jc = JAXBContext.newInstance(Pojo.class);
        SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        Schema schema = sf.newSchema(new ClassPathResource("schema.xsd").getFile());

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setSchema(schema);
        marshaller.marshal(schema, new DefaultHandler());
        return true;
}

One of my attributes (pojo.childEntity.someAttribute) is a date

XSD

<xsd:attribute name="some_date" use="required">
  <xsd:simpleType>
    <xsd:restriction base="xsd:date" />
  </xsd:simpleType>
</xsd:attribute>

Java

@XmlAttribute(name = "someDate", required = true)
protected XMLGregorianCalendar someDate;

It gets populate from a java.util.Date object from another POJO (one that is mapped with Hibernate).

private static final XMLGregorianCalendar dateToCalendar(Date date)
{
    if (date == null)
        return null;
    try
    {
        GregorianCalendar c = new GregorianCalendar();
        c.setTime(date);

        return DatatypeFactory.newInstance()
                .newXMLGregorianCalendar(c);
    }
    catch (DatatypeConfigurationException e)
    {
        e.printStackTrace();
        return null;
    }

}

The exception is:

javax.xml.bind.MarshalException
 - with linked exception:
[org.xml.sax.SAXParseException: cvc-datatype-valid.1.2.1: '2001-05-11T00:00:00.000+02:00' is not a valid value for 'date'.]

This looks like JAXB tries to set both date and time for a field that must carry only the date, and XMLGregorianCalendard is simply a datetime container.

The question is: what causes the error? How to fix?

Answer

bdoughan picture bdoughan · Jul 3, 2013

By default the output for an XMLGregorianCalendar property will be based on how you create it. If you populate the time portion, then the time portion will be output to XML. You can call the getXMLSchemaType() method to see what the corresponding XML representation is:

You can use the @XmlSchemaType annotation to override the representation.

Java Model (Root)

Below is an object with 3 XMLGregorianCalendar fields. On the 3rd I will use an @XmlSchemaType annotation to specify the schema type.

import javax.xml.bind.annotation.*;
import javax.xml.datatype.XMLGregorianCalendar;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {

    protected XMLGregorianCalendar default1;

    protected XMLGregorianCalendar default2;

    @XmlSchemaType(name="date")
    protected XMLGregorianCalendar schemaTypeDate;

}

Demo Code

In the demo code below we will create 2 instances of XMLGregorianCalendar. One will have schema type date the other dateTime. By default this is the XML representation used when marshalling to XML. On the schemaTypeDate field we will use the @XmlSchemaType annotation to override the default.

import javax.xml.bind.*;
import javax.xml.datatype.*;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Root.class);

        DatatypeFactory df = DatatypeFactory.newInstance();
        XMLGregorianCalendar date  = df.newXMLGregorianCalendar("2013-07-03");
        XMLGregorianCalendar dateTime = df.newXMLGregorianCalendar("1999-12-31T23:59:00");

        Root root = new Root();
        root.default1 = date;
        root.default2 = dateTime;
        root.schemaTypeDate = dateTime;

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(root, System.out);
    }

}

Output

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
    <default1>2013-07-03</default1>
    <default2>1999-12-31T23:59:00</default2>
    <schemaTypeDate>1999-12-31</schemaTypeDate>
</root>

UPDATE

Ok, since I have loooooooooooooooooooooooooooooooooots of XmlGregorianCalendars is there a way to tell XJC to add the xmlSchemaType attribute to all XGCs?

JAXB will do this for you when the schema type is one of the build in XML Schema types i.e. xsd:date or xsd:dateTime, but not when you have extended one of these types.

XML Schema (schema.xsd)

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema">
    <complexType name="root">
        <sequence>
            <element name="dateElement" type="date" />
            <element name="dateTimeElement" type="dateTime" />
            <element name="extendedDateElement">
                <simpleType>
                    <restriction base="date" />
                </simpleType>
            </element>
        </sequence>
        <attribute name="dateAttribute" type="date" />
        <attribute name="dateTimeAttribute" type="dateTime" />
        <attribute name="extendedDateAttribute">
            <simpleType>
                <restriction base="date" />
            </simpleType>
        </attribute>
    </complexType>
</schema>

Root

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "root", propOrder = {
    "dateElement",
    "dateTimeElement",
    "extendedDateElement"
})
public class Root {

    @XmlElement(required = true)
    @XmlSchemaType(name = "date")
    protected XMLGregorianCalendar dateElement;

    @XmlElement(required = true)
    @XmlSchemaType(name = "dateTime")
    protected XMLGregorianCalendar dateTimeElement;

    @XmlElement(required = true)
    protected XMLGregorianCalendar extendedDateElement;

    @XmlAttribute(name = "dateAttribute")
    @XmlSchemaType(name = "date")
    protected XMLGregorianCalendar dateAttribute;

    @XmlAttribute(name = "dateTimeAttribute")
    @XmlSchemaType(name = "dateTime")
    protected XMLGregorianCalendar dateTimeAttribute;

    @XmlAttribute(name = "extendedDateAttribute")
    protected XMLGregorianCalendar extendedDateAttribute;

}