Topics

Is it possible to register global XmlAdapters or at least provide additional "built-in" types in JAXB?


kalgon@...
 

With JAXB, you can marshal some built-in types like String or intlike this:
marshaller.marshal(new JAXBElement<>(new QName("result"), String.class, "some string"), target);
unmarshaller.unmarshal(source, String.class);
which produces the following XML: <result>some string</result>

Is it possible to register other built-in types like LocalDate(or other custom ones)? I would like to do the following but it does not work:
marshaller.marshal(new JAXBElement<>(new QName("result"), LocalDate.class, LocalDate.of(2000, 1, 1)), target);
unmarshaller.unmarshal(source, LocalDate.class);
This code currently throws: javax.xml.bind.JAXBException: java.time.LocalDate is not known to this context

Thanks,
Xavier ( hoping somebody actually reads those messages :-) )


bernhard.wege@...
 

Hi Xavier,

you need to introduce an adapter and custom bindings for your type. Here is a great example that will map to LocalDate:
http://bedtimestoriesforprogrammers.blogspot.de/2015/03/generating-jaxb-classes-with-date-or.html

Kind regards
Bernhard


kalgon@...
 

Here is an example of what I am trying to do (I would like to make the 2 commented lines work):

@XmlJavaTypeAdapter(type = LocalDate.class, value = LocalDateXmlAdapter.class)
package mypackage;

public class Test {

    public static void main(String... args) throws JAXBException {
        JAXBContext context = JAXBContext.newInstance(ObjectFactory.class);

        Marshaller marshaller = context.createMarshaller();
        marshaller.marshal(new JAXBElement<>(new QName("Anything"), Integer.class, 42), System.out); // OK
        marshaller.marshal(new JAXBElement<>(new QName("Whatever"), Date.class, new Date(100, 0, 1)), System.out); // OK
//        marshaller.marshal(new JAXBElement<>(new QName("YouNameIt"), LocalDate.class, LocalDate.of(2000, 1, 1)), System.out); // Does not work
        marshaller.marshal(new JAXBElement<>(new QName("LocalDate"), LocalDate.class, LocalDate.of(2000, 1, 1)), System.out); // OK but must always be <LocalDate> (see ObjectFactory)

        Unmarshaller unmarshaller = context.createUnmarshaller();
        unmarshaller.unmarshal(new StreamSource(new StringReader("<Anything>42</Anything>")), Integer.class).getValue(); // OK
        unmarshaller.unmarshal(new StreamSource(new StringReader("<Whatever>hello world</Whatever>")), String.class).getValue(); // OK
//        unmarshaller.unmarshal(new StreamSource(new StringReader("<YouNameIt>2000-01-01</YouNameIt>")), LocalDate.class).getValue(); // Does not work
        ((JAXBElement<?>) unmarshaller.unmarshal(new StringReader("<LocalDate>2000-01-01</LocalDate>"))).getValue(); // OK but must always be <LocalDate> (see ObjectFactory)
    }
} @XmlRegistry public class ObjectFactory { @XmlElementDecl(name = "LocalDate") @XmlJavaTypeAdapter(LocalDateXmlAdapter.class) public JAXBElement<LocalDate> createLocalDate(LocalDate value) { return null; // is it ever called? } } public class LocalDateXmlAdapter extends XmlAdapter<String, LocalDate> { public LocalDate unmarshal(String value) { return LocalDate.parse(value); } public String marshal(LocalDate value) { return value.toString(); } }


kalgon@...
 

Hi Bernard,

Using an XmlAdapter is only possible when marshalling a LocalDate which is contained in a bean (there you can either annotate the property/field or the package with the correct XmlAdapter).

In my case, I want to marshal a LocalDate without any scope/context provided by some outer bean (~ global scope).
This is possible for some basic types (java.util.Date, int, String...) but there does not seem to be a way to register additional types:

marshaller.marshal(new JAXBElement<>(new QName("value"), java.util.Date.class, new java.util.Date(100, 0, 1)), target); // works
marshaller.marshal(new JAXBElement<>(new QName("value"), LocalDate.class, LocalDate.of(2000, 1, 1)), target); // does not work

I also need to be able to unmarshal the result like this:

unmarshaller.unmarshal(new StreamSource(new StringReader("<value>42</value>")), Integer.class); // works
unmarshaller.unmarshal(new StreamSource(new StringReader("<value>2001-01-01</value>")), LocalDate.class); // does not work

I tried doing the following in the ObjectFactory:

@XmlRegistry
public class ObjectFactory {

    @XmlElementDecl(name = "localDate")
    @XmlJavaTypeAdapter(LocalDateXmlAdapter.class)
    public JAXBElement<LocalDate> createLocalDate(LocalDate value) { ... }
}
which allows me to marshal a standalone local date like this (but it needs to be <localDate> and not anything else):

marshaller.marshal(new JAXBElement<>(new QName("localDate"), LocalDate.class, LocalDate.of(2000, 1, 1)), target); // gives <localDate>2001-01-01</localDate>

But then, it's not possible to unmarshal it:

unmarshaller.unmarshal(new StreamSource(new StringReader("<localDate>2001-01-01<localDate>")), LocalDate.class); // error: LocalDate not known in this context

For more information, I posted this stackoverflow question: https://stackoverflow.com/questions/48264321/marshalling-directly-non-built-in-value-types-with-jaxb

The reason I want to do that is because I am making some framework which stores changes (in XML format) made to an object graph and I would like to treat entities/beans (which are@XmlType) and value objects (which have a corresponding XmlAdapter) the same way... and I have a lot of custom value objects (SocialSecurityNumber, VATNumber...) which I need to be able to store independently.

Regards,
Xavier


bernhard.wege@...
 

Hello Xavier,

I may have misunderstood what you want to do, but as far as I understand all you have to do is provide a scope in your XmlElementDecl. There is an example in the javadocs (https://docs.oracle.com/javaee/5/api/javax/xml/bind/annotation/XmlElementDecl.html):
       @XmlElementDecl(scope=Pea.class,name="foo")


Kind regards,
Bernhard