3

I am trying to implement a XmlAdapter for modifying the marshalling/unmarshalling of certain object properties. Particularly, I tried with the NullStringAdapter described here:

Jaxb: xs:attribute null values

The objective of the NullStringAdapter is marshalling null values as empty strings, and viceversa.

The only difference with the example described above and my code, is that I want to apply the adapter to an element, not to an attribute, so what I have is:

@XmlElement
@XmlJavaTypeAdapter(NullStringAdapter.class)
public String getSomeValue() {
    return someValue;  //someValue could be null, in that case the adapter should marshall it as an empty string
}

However, after some debugging, I realized that the Adapter methods are never called during the marshalling from Java to XML!. This occurs when the XmlElement value is null. When this value is different than null, the adapter methods are called as expected.

Thanks for any help!.

Community
  • 1
  • 1
Sergio
  • 8,532
  • 11
  • 52
  • 94
  • The following examples may help: http://blog.bdoughan.com/search/label/XmlAdapter – bdoughan Nov 04 '11 at 16:21
  • Thanks a lot for the links Blaise !!, according to those examples it seems to me that I have implemented the adapter correctly. Do you know if there is a way to force its use even if the adapted object has a null value ?? – Sergio Nov 04 '11 at 17:34
  • Maybe element values are never set? Please, provide the code for `NullStringAdapter` maybe that will give a hint... – dma_k Nov 04 '11 at 19:50
  • @Sergio - The JAXB RI does not appear to call the `XmlAdapter` when the value is null, but if you are interested EclipseLink JAXB (MOXy) does: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html – bdoughan Nov 04 '11 at 20:03
  • @BlaiseDoughan It sure makes a lot more sense. Wonder if the JAXB spec has anything to say about this or if it's up to the mercy of the implementation. – G_H Nov 04 '11 at 21:57
  • Ok, I tried with MOXy and looks cool. The adapter works as expected, processing null values. Furthermore, if the marshal method returns null, the field will not appear in the XML (that makes lots of sense to me, but JAXB RI throws an exception in that situation, I wonder if the specification does not say something about this!). The only problem I have now with MOXy is that empty List objects are not present anymore in the generated XML!. With JAXB RI I could see their element wrappers with no children, and that was what I intended. Do you know how I could fix that in MOXy, @BlaiseDoughan ? – Sergio Nov 04 '11 at 23:17
  • Well, given that the problem with the list objects is a complete different question than the original one, I open a new thread: http://stackoverflow.com/questions/8022266/list-wrappers-in-jaxb-moxy. Thanks a lot for your help both G_H and @BlaiseDoughan. Blaise if you are so kind to copy your comment as an answer to this question I will accept it (basically the answer is that JAXB RI just will not call the adapter with null values while MOXy will do it (?)). – Sergio Nov 05 '11 at 18:28

2 Answers2

7

Note: I'm the EclipseLink JAXB (MOXy) lead, and a member of the JAXB 2 (JSR-222) expert group.

However, after some debugging, I realized that the Adapter methods are never called during the marshalling from Java to XML!. This occurs when the XmlElement value is null. When this value is different than null, the adapter methods are called as expected.

This behaviour varies between implementations of JAXB. The JAXB reference implementation will not call the marshal method on the XmlAdapter when the field/property is null, but MOXy will.

What the JAXB spec says (section 5.5.1 Simple Property)

The get or is method returns the property’s value as specified in the previous subsection. If null is returned, the property is considered to be absent from the XML content that it represents.

The MOXy interpretation of this statement is that the value of the field/property is really the value once it has gone through the XmlAdapter. This is necessary to support the behaviour that Sergio is looking for.

Community
  • 1
  • 1
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • 3
    And it's pretty much what makes the most sense. `null` should be a value like any other and open to interpretation by an adapter, in my opinion. – G_H Nov 08 '11 at 18:24
3

Of course the adapter will never be called if there isn't any element in the input to trigger that action. What happens in that example you're linked is that an attribute with an empty value is presented:

<element att="" />

The key here is that there is an att attribute, but it has an empty String. So a JAXB unmarshaller is gonna present that to the setter. But, since there's an adapter declared on it, it will pass through there and get turned into a null value.

But if you had this

<element />

it's another story. There's no att attribute, so the setter would never need to be called.

There's a difference between an element that occurs but has no content and a complete absence of an element. The former can basically be considered to contain an empty String, but the latter is just "not there".

EDIT: tested with these classes...

Bean.java

package jaxbadapter;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name="Test")
public class Bean {

    @XmlElement
    @XmlJavaTypeAdapter(NullStringAdapter.class)
    private String someValue;

    public Bean() {
    }

    public String getSomeValue() {
        return someValue;
    }

    public void setSomeValue(final String someValue) {
        this.someValue = someValue;
    }

}

NullStringAdapter.java

package jaxbadapter;

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

public class NullStringAdapter extends XmlAdapter<String, String> {

    @Override
    public String unmarshal(final String v) throws Exception {
        if("".equals(v)) {
            return null;
        }
        return v;
    }

    @Override
    public String marshal(final String v) throws Exception {
        if(null == v) {
            return "";
        }
        return v;
    }

}

ObjectFactory.java

package jaxbadapter;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;


@XmlRegistry
public class ObjectFactory {

    public ObjectFactory() {
    }

    public Bean createBean() {
        return new Bean();
    }

    @XmlElementDecl(name = "Test")
    public JAXBElement<Bean> createTest(Bean value) {
        return new JAXBElement<>(new QName("Test"), Bean.class, null, value);
    }

}

Main.java

package jaxbadapter;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.transform.stream.StreamResult;

public class Main {

    public static void main(String[] args) throws Exception {

        final JAXBContext context = JAXBContext.newInstance("jaxbadapter");

        final Marshaller m = context.createMarshaller();
        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);

        final ObjectFactory of = new ObjectFactory();

        final Bean b1 = new Bean();

        final Bean b2 = new Bean();
        b2.setSomeValue(null);

        final Bean b3 = new Bean();
        b3.setSomeValue("");

        m.marshal(of.createTest(b1), System.out);
        System.out.println("");

        m.marshal(of.createTest(b2), System.out);
        System.out.println("");

        m.marshal(of.createTest(b3), System.out);
        System.out.println("");


    }

}

This is the output:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Test/>

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Test/>

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Test>
    <someValue></someValue>
</Test>

Actually surprised me quite a bit. I've then tried changing the getter to return someValue == null ? "" : someValue; to no avail. Then set a breakpoint on the getter and found out it never gets called.

Apparently JAXB uses reflection to try and retrieve the value rather than going through the setter when using XmlAccessType.FIELD. Hardcore. Now, you can bypass this by using XmlAccessType.PROPERTY instead and annotating either the getter or setter...

package jaxbadapter;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlType(name="Test")
public class Bean {

    private String someValue;

    public Bean() {
    }

    @XmlElement
    @XmlJavaTypeAdapter(NullStringAdapter.class)
    public String getSomeValue() {
        return someValue;
    }

    public void setSomeValue(final String someValue) {
        this.someValue = someValue;
    }

}

... but that still didn't help. The adapter's marshal method was only called once, on the last test case where an empty String had been set. Apparently it first calls the getter and when that returns null, it simply skips the adapter stuff altogether.

The only solution I can come up with is just foregoing the use of an adapter altogether here and put the substitution in the getter, making sure to use XmlAccessType.PROPERTY:

package jaxbadapter;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlType(name="Test")
public class Bean {

    private String someValue;

    public Bean() {
    }

    @XmlElement
//  @XmlJavaTypeAdapter(NullStringAdapter.class)
    public String getSomeValue() {
        return someValue == null ? "" : someValue;
    }

    public void setSomeValue(final String someValue) {
        this.someValue = someValue;
    }

}

That worked for me. It's only really an option if you're creating the JAXB-annotated classes yourself and not generating them via XJC from a schema, though.

Maybe someone can clarify a bit why adapters are skipped for nulls and if there's a way to change that behaviour.

G_H
  • 11,739
  • 3
  • 38
  • 82
  • I thought the adapter will be called behind the courtains for the marshaller/unmarshaller. If that is not the case could you please explain to me what should I do instead ? thanks – Sergio Nov 04 '11 at 16:21
  • @Sergio Well, it should work for the getter if it returns a null value. That is, it should work for marhsalling. But it won't work for unmarshalling since nothing "triggers" the call to the adapter. Is it not working during marshalling from Java to XML? – G_H Nov 04 '11 at 16:27
  • If I understood correctly from the explanation of @Blaise in the link shown at the question, the idea of that adapter is precisely to modify the way null values are marshalled/unmarshalled. – Sergio Nov 04 '11 at 16:29
  • @Sergio You're right... See edit for my tests and a possible solution. Depends on your use-case, though. – G_H Nov 04 '11 at 17:59
  • Thanks a lot for the big answer!. I am also really looking for someone who can clarify the behaviour of adapters in this case! – Sergio Nov 04 '11 at 18:15