2
votes

We use Jaxb (jaxb-api 2.2.5) to generate a Java class from an XSD. The 'someField' element has a nillable='true' attribute and an (implicit) minoccurs='1'. There is also an optional 'order' attribute.

When we set the order attribute on someField, but no value, JAXB will generate the XML element in the request without nill='true' and this is not accepted by the XSD and results in a SOAP fault.

The XSD for the field:

<xs:element name="someField" nillable="true">
    <xs:complexType>
        <xs:simpleContent>
            <xs:extension base="iata:AlphaNumericStringLength1to19">
                <xs:attribute name="order" type="xs:integer" use="optional"/>
            </xs:extension>
        </xs:simpleContent>
    </xs:complexType>
</xs:element>

Jaxb translates this to the following field on our Java class:

@XmlElement(required = true, nillable = true)
protected SomeParentType.SomeField someField;

The SomeField class looks like this:

public static class SomeField{

    @XmlValue
    protected String value;
    @XmlAttribute
    protected BigInteger order;

    // getters  + setters
}

When we set the order ATTRIBUTE to 2 (for example), and set nothing for the value, JAXB will generate this:

<pay1:someField order="2"/> 

This is not valid according to the XSD and it results in a SOAP fault when we send it.

This does work:

<pay1:someField xsi:nil="true" order="2"/>

Do you know how we can get JAXB be to generate the latter? And is JAXB actually wrong in generating the nil-less version?

1

1 Answers

3
votes

And is JAXB actually wrong in generating the nil-less version?

Let me get back to you on this.


Do you know how we can get JAXB be to generate the latter?

Below is what you can do

Java Model

SomeParentType

To get the behaviour you are looking for with existing JAXB libraries the domain model needs to be of the following form:

import java.math.BigInteger;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;

@XmlRootElement
public class SomeParentType {

    @XmlElementRef(name="someField")
    protected JAXBElement<SomeParentType.SomeField> someField;

    public static class SomeField{

        @XmlValue
        protected String value;
        @XmlAttribute
        protected BigInteger order;

        // getters  + setters
    }

}

Registry

To go along with the @XmlElementRef we need to have an @XmlElementDecl on a class annotated with @XmlRegistry.

import javax.xml.namespace.QName;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;

@XmlRegistry
public class Registry {

    @XmlElementDecl(name="someField")
    public JAXBElement<SomeParentType.SomeField> createSomeField(SomeParentType.SomeField someField) {
        return new JAXBElement(new QName("someField"), SomeParentType.SomeField.class, someField);
    }

}

Demo Code

Below is some demo code to exercise your use case:

import javax.xml.bind.*;
import java.math.BigInteger;

public class Demo {

    public static void main(String[] args) throws Exception {
        // Create the JAXBContext to bring in the Registry
        JAXBContext jc = JAXBContext.newInstance(SomeParentType.class, Registry.class);

        // Create the instance of SomeField
        SomeParentType.SomeField sf = new SomeParentType.SomeField();
        sf.order = new BigInteger("1");

        // Wrap the SomeField in a JAXBElement & specify the nil aspect
        Registry registry = new Registry();
        JAXBElement<SomeParentType.SomeField> jaxbElement = registry.createSomeField(sf);
        jaxbElement.setNil(true);

        SomeParentType spt = new SomeParentType();
        spt.someField = jaxbElement;

        // Marshal the domain model to XML
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(spt, System.out);
    }

}

Output

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<someParentType>
    <someField xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" order="1" xsi:nil="true"/>
</someParentType>