4

i'm new to MOXy and JaxB in general and I'm facing a problem with java.util.Date conversion.

I'm unmarshaling an XML file (which I have no control of) to objects using a mapping file (I can neither manually annotate existing classes nor change them).

My XML mapping file looks like this :

<?xml version="1.0"?>
<xml-bindings 
        xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
        version="2.1">
    <java-types>
        <java-type name="Observation">
            <xml-type prop-order="date theoricalTime ci ch cr type" />
            <java-attributes>
                <xml-element java-attribute="date" xml-path="Date/text()" />
                <xml-element java-attribute="theoricalTime" xml-path="TheoricalTime/text()" />
                <xml-element java-attribute="ci" xml-path="CIPR/text()" />
                <xml-element java-attribute="ch" xml-path="CHPR/text()" />
                <xml-element java-attribute="cr" xml-path="CRPR/text()" />
                <xml-element java-attribute="type" xml-path="Type/text()" />
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

In the class which I'm marshalling to, properties "date" and "theoricalTime" are of type java.util.Date.

The values from the xml I'm marshalling from are strings with this kind of format : "dd/MM/yyyy HH:mm:ss" ("05/01/2012 16:36:24"). I also have some fields with only a time value "HH:mm:ss" ("14:17:33").

Here is the stacktrace I'm getting when unmarshalling the file :

Exception in thread "main" Local Exception Stack: 
Exception [EclipseLink-3002] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.ConversionException
Exception Description: The object [22/01/2009 20:56:29], of class [class java.lang.String], from mapping [org.eclipse.persistence.oxm.mappings.XMLDirectMapping[date-->Date/text()]] with descriptor [XMLDescriptor(Observation --> [DatabaseTable(Observation)])], could not be converted to [class java.util.Calendar].
    at org.eclipse.persistence.exceptions.ConversionException.incorrectDateTimeFormat(ConversionException.java:127)
    at org.eclipse.persistence.exceptions.ConversionException.incorrectDateTimeFormat(ConversionException.java:133)
    at org.eclipse.persistence.internal.oxm.XMLConversionManager.convertStringToXMLGregorianCalendar(XMLConversionManager.java:703)
    at org.eclipse.persistence.internal.oxm.XMLConversionManager.convertStringToDate(XMLConversionManager.java:1111)
    at org.eclipse.persistence.internal.oxm.XMLConversionManager.convertObjectToUtilDate(XMLConversionManager.java:804)
    at org.eclipse.persistence.internal.oxm.XMLConversionManager.convertObject(XMLConversionManager.java:165)
    at org.eclipse.persistence.internal.databaseaccess.DatasourcePlatform.convertObject(DatasourcePlatform.java:160)
    at org.eclipse.persistence.oxm.mappings.XMLDirectMapping.getAttributeValue(XMLDirectMapping.java:293)
    at org.eclipse.persistence.internal.oxm.XMLDirectMappingNodeValue.endElement(XMLDirectMappingNodeValue.java:182)
    at org.eclipse.persistence.oxm.record.UnmarshalRecord.endElement(UnmarshalRecord.java:823)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(AbstractSAXParser.java:601)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(XMLDocumentFragmentScannerImpl.java:1774)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2930)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:648)
    at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:140)
    at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:510)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:807)
    at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:737)
    at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:107)
    at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1205)
    at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:522)
    at org.eclipse.persistence.internal.oxm.record.XMLReader.parse(XMLReader.java:157)
    at org.eclipse.persistence.internal.oxm.record.SAXUnmarshaller.unmarshal(SAXUnmarshaller.java:753)
    at org.eclipse.persistence.internal.oxm.record.SAXUnmarshaller.unmarshal(SAXUnmarshaller.java:333)
    at org.eclipse.persistence.internal.oxm.record.SAXUnmarshaller.unmarshal(SAXUnmarshaller.java:320)
    at org.eclipse.persistence.internal.oxm.record.SAXUnmarshaller.unmarshal(SAXUnmarshaller.java:280)
    at org.eclipse.persistence.oxm.XMLUnmarshaller.unmarshal(XMLUnmarshaller.java:306)
    at org.eclipse.persistence.jaxb.JAXBUnmarshaller.unmarshal(JAXBUnmarshaller.java:115)
    at Main.Test(Main.java:97)
    at Main.main(Main.java:35)

My question is : Is it possible to specify types conversions inside the mapping file using MOXy's external metadata ? How can I handle datetime and time with the formats specified above and map them to Date fields ?

(I secretly hope Blaise Doughan is reading this.)

Thanks in advance for your help !

bdoughan
  • 147,609
  • 23
  • 300
  • 400
Drewman
  • 947
  • 11
  • 23

1 Answers1

6

The following demonstrates how you can use an XmlAdapter with MOXy's external mapping document to achieve the results that you are looking for:

DateAdapter

Since your date/time data is in the following format dd/MM/yyyy HH:mm:ss you will need to use an XmlAdapter like the following:

package forum8745305;

import java.text.SimpleDateFormat;
import java.util.Date;

import javax.xml.bind.annotation.adapters.XmlAdapter;

public class DateAdapter extends XmlAdapter<String, Date> {

    private SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");

    @Override
    public String marshal(Date v) throws Exception {
        return dateFormat.format(v);
    }

    @Override
    public Date unmarshal(String v) throws Exception {
        return dateFormat.parse(v);
    }

}

oxm.xml

This is normally specified on your domain model using the @XmlJavaTypeAdapter annotation, but since you are using MOXy's external metadata document you can specify it as follows. I have specified it at the package level so that it will apply to all fields/properties of type java.util.Date belonging to domain classes in that package:

<?xml version="1.0"?>
<xml-bindings 
        xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
        version="2.1"
        package-name="forum8745305">
    <xml-java-type-adapters>
        <xml-java-type-adapter value="forum8745305.DateAdapter" type="java.util.Date"/>
    </xml-java-type-adapters>
    <java-types>
        <java-type name="Observation">
            <xml-type prop-order="date theoricalTime ci ch cr type" />
            <xml-root-element/>
            <java-attributes>
                <xml-element java-attribute="date" xml-path="Date/text()"/>
                <xml-element java-attribute="theoricalTime" xml-path="TheoricalTime/text()" />
                <xml-element java-attribute="numeroTrain" xml-path="NumeroTrain/text()" />
                <xml-element java-attribute="ci" xml-path="CIPR/text()" />
                <xml-element java-attribute="ch" xml-path="CHPR/text()" />
                <xml-element java-attribute="cr" xml-path="CRPR/text()" />
                <xml-element java-attribute="type" xml-path="Type/text()" />
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

Observation

Based on your question, below is what your domain class might look like:

package forum8745305;

import java.util.Date;

public class Observation {

    private Date date;
    private Date theoricalTime;
    private String numeroTrain;
    private String ci;
    private String ch;
    private String cr;
    private String type;

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public Date getTheoricalTime() {
        return theoricalTime;
    }

    public void setTheoricalTime(Date theoricalTime) {
        this.theoricalTime = theoricalTime;
    }

    public String getNumeroTrain() {
        return numeroTrain;
    }

    public void setNumeroTrain(String numeroTrain) {
        this.numeroTrain = numeroTrain;
    }

    public String getCi() {
        return ci;
    }

    public void setCi(String ci) {
        this.ci = ci;
    }

    public String getCh() {
        return ch;
    }

    public void setCh(String ch) {
        this.ch = ch;
    }

    public String getCr() {
        return cr;
    }

    public void setCr(String cr) {
        this.cr = cr;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

}

Demo

The following code can be used to run the example:

package forum8745305;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

import org.eclipse.persistence.jaxb.JAXBContextFactory;

public class Demo {

    public static void main(String[] args) throws Exception {
        Map<String, Object> properties = new HashMap<String, Object>(1);
        properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "forum8745305/oxm.xml");
        JAXBContext jc = JAXBContext.newInstance(new Class[] {Observation.class}, properties);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/forum8745305/input.xml");
        Observation observation = (Observation) unmarshaller.unmarshal(xml);

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

}

Input/Output

<?xml version="1.0" encoding="UTF-8"?>
<observation>
    <Date>05/01/2012 16:36:24</Date>
    <TheoricalTime>01/02/2012 12:34:45</TheoricalTime>
</observation>

For More Information

UPDATE

You can also specify XmlAdapters at the property level. This means you could have a different XmlAdapter for each of your Date properties if you wanted to.

<?xml version="1.0"?>
<xml-bindings 
        xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
        version="2.1"
        package-name="forum8745305">
    <java-types>
        <java-type name="Observation">
            <xml-type prop-order="date theoricalTime ci ch cr type" />
            <xml-root-element/>
            <java-attributes>
                <xml-element java-attribute="date" xml-path="Date/text()">
                    <xml-java-type-adapter value="forum8745305.DateAdapter"/>
                </xml-element>
                <xml-element java-attribute="theoricalTime" xml-path="TheoricalTime/text()">
                    <xml-java-type-adapter value="forum8745305.DateAdapter"/>
                </xml-element>
                <xml-element java-attribute="numeroTrain" xml-path="NumeroTrain/text()" />
                <xml-element java-attribute="ci" xml-path="CIPR/text()" />
                <xml-element java-attribute="ch" xml-path="CHPR/text()" />
                <xml-element java-attribute="cr" xml-path="CRPR/text()" />
                <xml-element java-attribute="type" xml-path="Type/text()" />
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • 1
    It worked perfectly for all datetime fields (fields containing "01/02/2012 18:02:34") but I'm still confused on how to map fields containing time or date only ("14:01:34" and "dd/MM/yyyy") and which are supposed to be mapped to Date fields ? Should I instead create 3 SimpleDateFormat for each format ("dd/MM/yyyy HH:mm:ss", "dd/MM/yyyy" and "HH:mm:ss") and test the string's content to know which SimpleDateFormat should be used ? Or is there a way to specify a specific method for each property ? – Drewman Jan 05 '12 at 16:45
  • @user1121108 - I have updated my answer with what the XML metadata would look like if you specified the `XmlAdapter` at the property level. Based on your comment you would need separate `XmlAdapters` for each of the desired formats. – bdoughan Jan 05 '12 at 17:23
  • @user1121108: If you have strings in different format you should have one `XmlAdapter` but try several patterns for parsing (starting from longest). Unfortunately, you won't be able to do the correct conversion from `Date` back to `String`: there is no notion of unset field in `Date`. Consider mapping to `Partial` from [Joda Time](http://joda-time.sourceforge.net/). – dma_k Jan 05 '12 at 18:07
  • Thanks for the answers, that was very helpful, is there a place where I can find some documentation on the different possibilities of MOXy's external metadata mapping ? – Drewman Jan 06 '12 at 08:39
  • @user1121108 - The MOXy User Guide (http://wiki.eclipse.org/EclipseLink/UserGuide/MOXy) is your best resource. Each section describes how to do the relevant mappings with annotations and XML. – bdoughan Jan 06 '12 at 18:14