19

I'm trying to create an XmlAdapter that takes in an XMLGregorianCalendar and outputs an XMLGregorianCalendar. The purpose is simlply to remove timezone data from the element when unmarshalling data.

It looks like this:

public class TimezoneRemoverAdapter extends XmlAdapter<XMLGregorianCalendar, XMLGregorianCalendar> {
    public XMLGregorianCalendar unmarshal(XMLGregorianCalendar xgc) {
        if(xgc == null) {
            return null;
        }
        xgc.setTimezone(DatatypeConstants.FIELD_UNDEFINED);
        return xgc;
    }

    public XMLGregorianCalendar marshal(XMLGregorianCalendar xgc) {
        return xgc;
    }
}

This works fine for the following code:

public class FooElement {
    @XmlElement(name="bar-date")
    @XmlJavaTypeAdapter(TimezoneRemoverAdapter.class)
    @XmlSchemaType(name = "date")
    protected XMLGregorianCalendar barDate;
}

Unfortunately, when I generate the code using a jaxb-bindings.xml file, the above code looks like this:

public class FooElement {
    @XmlElement(name="bar-date", type=java.lang.String.class)
    @XmlJavaTypeAdapter(TimezoneRemoverAdapter.class)
    @XmlSchemaType(name = "date")
    protected XMLGregorianCalendar barDate;
}

It sets the type to String, so my above method doesn't work. The type String setting is overriding the XMLGregorianCalendar type that it should be. I can manually change it, but I'd rather not have to remember to update it every time the jaxb files are regenerated. Does anyone know if there's an option to manually set the @XmlElement type or have it ignored?

Here is the relevant portion of the jaxb-bindings.xml file:

<jxb:bindings node=".//xs:element[@name=bar-date]">
    <jxb:property>
        <jxb:baseType>
            <jxb:javaType name="javax.xml.datatype.XMLGregorianCalendar" adapter="foo.bar.TimezoneRemoverAdapter" />
        </jxb:baseType>
    </jxb:property>
</jxb:bindings>
Daniel Szalay
  • 4,041
  • 12
  • 57
  • 103
summer
  • 711
  • 1
  • 5
  • 15
  • 1
    It might be that the XPath term in your Binding is not finding the required element. You don't need the dot since `//` searches in the entire document. And more importantly try surrounding the element name with `''`. Resulting term should look like this: `node="//xs:element[@name='bar-date']"` – W Almir Aug 20 '13 at 10:24
  • 4
    What does the XSD look like? Is the bar-date element defined as xs:string or a complexType? – Sam Nunnally Nov 13 '13 at 22:03
  • maybe i'm wrong, but i think marshall/unmarshall pursuit a different goal, what you are trying to do is conceptually incorrect. maybe the timezone removal itself is incorrect, why would someone do it? – Michele Mariotti Dec 04 '13 at 08:54
  • Please provide the xsd, without it its difficult to guess the issue, or recreate it. – Akshay Dec 06 '13 at 01:33
  • the bar-date element should be defined with a `xsd:date` type. Is this the case ? – Grooveek Dec 06 '13 at 10:29
  • Changing the XPath yields the same result. bar-date is an xsd:date as annotations on FooElement imply. XML allows a timezone to be used on dates and the business requirement is to remove them when present. – summer Apr 21 '14 at 11:58

2 Answers2

1

UPDATE

summarizing:

  1. you have a schema that uses date style somewhere, and you cannot change the schema
  2. you have some XML data that uses that schema and specify some date with timezone (so it's yyyy-MM-ddXXX format)
  3. you want to remove the XXX part from the representation of the date in that file (date itself does not ship any timezone, date is just a number)

so this could be a sample schema:

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
    <element name="foo">
        <complexType>
            <sequence>
                <element name="bar" type="date" minOccurs="1" maxOccurs="1"/>
            </sequence>
        </complexType>
    </element>
</schema>

this could be a sample data:

<?xml version="1.0" encoding="UTF-8"?>
<foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="foo.xsd">
    <bar>2014-01-01+06:00</bar>
</foo>

this is JAXB annotated class

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Foo implements Serializable
{
    private static final long serialVersionUID = 1L;

    @XmlElement(name = "bar")
    @XmlJavaTypeAdapter(DateAdapter.class)
    @XmlSchemaType(name = "date")
    private Date bar;

    // getters/setters
}

this is date adapter

public class DateAdapter extends XmlAdapter<String, Date>
{
    @Override
    public String marshal(Date date)
    {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        df.setTimeZone(TimeZone.getTimeZone("GMT"));
        return df.format(date);
    }

    @Override
    public Date unmarshal(String date) throws ParseException
    {
        DateFormat df = new SimpleDateFormat("yyyy-MM-ddXXX");
        return df.parse(date);
    }
}

this is the main, validating against the schema:

public static void main(String[] args) throws JAXBException, SAXException
{
    JAXBContext context = JAXBContext.newInstance(Foo.class);

    SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
    Schema schema = sf.newSchema(Foo.class.getResource("/foo.xsd"));

    Unmarshaller unmarshaller = context.createUnmarshaller();
    unmarshaller.setSchema(schema);
    Foo foo = (Foo) unmarshaller.unmarshal(Foo.class.getResource("/foo.xml"));
    System.out.println("unmarshalled: " + foo.getBar());

    Marshaller marshaller = context.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    marshaller.setProperty(Marshaller.JAXB_NO_NAMESPACE_SCHEMA_LOCATION, "foo.xsd");
    marshaller.setSchema(schema);
    marshaller.marshal(foo, System.out);
}

and this is the output, timezone has been removed and date representation has obviously changed

unmarshalled: Tue Dec 31 19:00:00 CET 2013
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="foo.xsd">
    <bar>2013-12-31</bar>
</foo>

maybe this date representation change is not what you'd expect, but this is not a JAXB concern, the date represented has not changed.

i was forgetting the bindings to reverse generate Foo:

<jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" jaxb:extensionBindingPrefixes="xjc" jaxb:version="2.0">
    <jaxb:globalBindings>
        <xjc:javaType name="java.util.Date" xmlType="xsd:date" adapter="aaa.DateAdapter" />
    </jaxb:globalBindings>
</jaxb:bindings>

END OF UPDATE


sorry, too long for a comment...

i can't understand:

  1. why the hell are you using XmlGregorianCalendar?
  2. why should you marshal/unmarshal (serialize/deserialize) to the very same data structure?
  3. why should you remove timezone??

and

  1. i use straight and simple java.util.Date
  2. marshal/unmarshal should always involve Strings (at least for XML)
  3. i really don' see a good reason to arbitrarily remove a piece of a date representation. maybe you want to serialize it in an absolute way.

however

public class DateAdapter extends XmlAdapter<String, Date>
{
    @Override
    public String marshal(Date date)
    {
        DateFormat df = DateFormat.getDateTimeInstance();
        df.setTimeZone(TimeZone.getTimeZone("GMT"));

        return df.format(date);
    }

    @Override
    public Date unmarshal(String date) throws ParseException
    {
        DateFormat df = DateFormat.getDateTimeInstance();
        df.setTimeZone(TimeZone.getTimeZone("GMT"));

        return df.parse(date);
    }

    public static void main(String[] args) throws ParseException
    {
        DateAdapter adapter = new DateAdapter();

        String str = adapter.marshal(new Date());
        System.out.println(str); // 16-dic-2013 10.02.09  --> to gmt

        Date date = adapter.unmarshal(str);
        System.out.println(date); // Mon Dec 16 11:02:09 CET 2013  --> correct, i'm gmt+1
    }
}
Michele Mariotti
  • 7,372
  • 5
  • 41
  • 73
  • This solution will not work since it does not use the XML date format. 1. The format of date is xsd:date as XmlSchemaType implies. That's why I'm using XMLGregorianCalendar. 2. I'm attempting to remove the timezone only. JAXB will umarshal/marshal the data for me if I tell it to use XMLGregorianCalendar for the to/from type. 3. Business requirements. – summer Apr 21 '14 at 12:00
1

You have to specify the java type differently, in your case:

<jxb:javaType 
     name="javax.xml.datatype.XMLGregorianCalendar" 
     xmlType="xs:date"  
     printMethod="foo.bar.TimezoneRemoverAdapter.marshall" 
     parseMethod="foo.bar.TimezoneRemoverAdapter.unmarshall" 
/>

It works fine for me and I did something similar with more adapters:

<jaxb:globalBindings>    
     <xjc:serializable uid="12343" />
     <jaxb:javaType name="java.util.Date" xmlType="xs:date" printMethod="com.foo.DateAdapter.printDate" parseMethod="com.foo.DateAdapter.parseDate" />
     <jaxb:javaType name="java.util.Date" xmlType="xs:dateTime" printMethod="com.foo.DateAdapter.printDate" parseMethod="com.foo.DateAdapter.parseDate" />
     <jaxb:javaType name="java.util.Date" xmlType="xs:time" printMethod="com.foo.TimeAdapter.printTime" parseMethod="com.foo.TimeAdapter.parseTime" />
</jaxb:globalBindings>

I put the above bindings as a globalBindings in a different file with .xjb extension and I use it everywhere I need it.

Giuseppe Adaldo
  • 295
  • 3
  • 16