Skip to content
This repository has been archived by the owner on Feb 5, 2019. It is now read-only.

JAXB 2.1 bug when generating JAXB classes on certain nillable elements #1189

Open
LanceAndersen opened this issue Apr 25, 2018 · 0 comments
Open

Comments

@LanceAndersen
Copy link

ULL PRODUCT VERSION :
java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
Java HotSpot(TM) Server VM (build 19.1-b02, mixed mode)

JAXB version 2.1:
[INFO] JAXB API is loaded from the [jar:file:/usr/lib/jvm/java-6-sun-1.6.0.24/jre/lib/rt.jar!].
[INFO] Detected JAXB API version [2.1].

ADDITIONAL OS VERSION INFORMATION :
Linux extdivmoha-laptop 2.6.32-34-generic-pae #77-Ubuntu SMP Tue Sep 13 21:16:18 UTC 2011 i686 GNU/Linux

EXTRA RELEVANT SYSTEM CONFIGURATION :
(none)

A DESCRIPTION OF THE PROBLEM :
When the XJC compiler generates java binding classes from XML Schema elements that are declared optional (minOccurs="0") and nillable (nillable="true"), the resulting element should cause a JAXBElement field to be generated to allow JAXB binding class clients to distinguish between the element being absent and the element being explicitly declared with a nil value.

Unfortunately, in certain circumstance, this is not the case.

Example 1 (correct XJC binding):

XML schema element declaration is made by referencing a schema type:
<xsd:element name="NillableElement2" type="xsd:string" nillable="true" minOccurs="0"/>

Resulting JAXB binding class field:
@XmlElementRef(name = "NillableElement2", namespace = "urn:jaxb:sample", type = JAXBElement.class)
protected JAXBElement nillableElement2;

Example 2 (incorrect XJC binding):

XML schema element declaration is made by referencing a nillable element (in a sequence):
<xsd:element ref="NillableElement1" minOccurs="0"/>
(with the)
<xsd:element name="NillableElement1" type="xsd:string" nillable="true"/>

Resulting JAXB binding class field:
@xmlelement(name = "NillableElement1", nillable = true)
protected String nillableElement1;

Conclusion (ref Example 2, above):

When an xml schema element declaration specifies minOccurs="0" and references a nillable element. Notice how the "nillableElement1" field is declared as a String, which does not provide any way of determining whether the XML instance element was not present, or it was declared as a nil value (xsi:nil attribute).

Furthermore, when an JAXB Class instance, containing this field declaration is marshalled:
@xmlelement(name = "NillableElement1", nillable = true)
protected String nillableElement1;

And the field (nillableElement1) contains null, the resulting XML will ALWAYS generate a NILL reference:

Even though the two XML schema examples above are semantically identical, they cause different JAXB Binding to be generated, one of which (Example 2) has two major problems:

  1. It cannot be determined (or defined) whether an optional nillable field is null or (explicit) nil.
  2. When marshalled, a null field will cause a "nil" element to be generated.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Use JAXB to generate binding classes of a schema (attached: sample.xsd)
Look at the resulting OptionalNillable.java class (attached).

See the difference between the nillableElement1 and nillableElement2 fields of the OptionalNillable.java class.

Full maven project with unit tests can be provided, upon request.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Given the XML schema declarations...

<xsd:element name="OptionalNillable">
    <xsd:complexType>
        <xsd:sequence>
            <xsd:element ref="NillableElement1" minOccurs="0"/>
            <xsd:element name="NillableElement2" type="xsd:string" nillable="true" minOccurs="0"/>
        </xsd:sequence>
    </xsd:complexType>
</xsd:element>

The resulting JAXB binding fields for NillableElement1 and NillableElement2 should both be of type JAXBElement, like:

@XmlElementRef(name = "NillableElement1", namespace = "urn:jaxb:sample", type = JAXBElement.class)
protected JAXBElement<String> nillableElement1;

@XmlElementRef(name = "NillableElement2", namespace = "urn:jaxb:sample", type = JAXBElement.class)
protected JAXBElement<String> nillableElement2;

ACTUAL -
NillableElement1 is declared as a String:

@XmlElement(name = "NillableElement1", nillable = true)
protected String nillableElement1;
@XmlElementRef(name = "NillableElement2", namespace = "urn:jaxb:sample", type = JAXBElement.class)
protected JAXBElement<String> nillableElement2;

ERROR MESSAGES/STACK TRACES THAT OCCUR :
(none)

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
File: sample.xsd
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:jaxb:sample" targetNamespace="urn:jaxb:sample" elementFormDefault="qualified">

<xsd:element name="OptionalNillable">
    <xsd:complexType>
        <xsd:sequence>
            <xsd:element ref="NillableElement1" minOccurs="0"/>
            <xsd:element name="NillableElement2" type="xsd:string" nillable="true" minOccurs="0"/>
        </xsd:sequence>
    </xsd:complexType>
</xsd:element>

<xsd:element name="NillableElement1" type="xsd:string" nillable="true"/>

</xsd:schema>

File: OptionalNillable.java
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.4
// See http://java.sun.com/xml/jaxb&lt;/a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2011.10.28 at 10:22:00 AM CEST
//
package jaxb.sample;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import org.jvnet.jaxb2_commons.lang.Equals;
import org.jvnet.jaxb2_commons.lang.EqualsStrategy;
import org.jvnet.jaxb2_commons.lang.HashCode;
import org.jvnet.jaxb2_commons.lang.HashCodeStrategy;
import org.jvnet.jaxb2_commons.lang.JAXBEqualsStrategy;
import org.jvnet.jaxb2_commons.lang.JAXBHashCodeStrategy;
import org.jvnet.jaxb2_commons.lang.JAXBToStringStrategy;
import org.jvnet.jaxb2_commons.lang.ToString;
import org.jvnet.jaxb2_commons.lang.ToStringStrategy;
import org.jvnet.jaxb2_commons.locator.ObjectLocator;
import org.jvnet.jaxb2_commons.locator.util.LocatorUtils;

/**

  • Java class for anonymous complex type.

  • The following schema fragment specifies the expected content contained within this class.

  • <complexType>
  • <complexContent>
  • <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
  • <sequence>
  • <element ref="{urn:jaxb:sample}NillableElement1" minOccurs="0"/>
  • <element name="NillableElement2" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
  • </sequence>
  • </restriction>
  • </complexContent>
  • </complexType>

*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"nillableElement1",
"nillableElement2"
})
@XmlRootElement(name = "OptionalNillable")
public class OptionalNillable
implements Equals, HashCode, ToString
{

@XmlElement(name = "NillableElement1", nillable = true)
protected String nillableElement1;
@XmlElementRef(name = "NillableElement2", namespace = "urn:jaxb:sample", type = JAXBElement.class)
protected JAXBElement<String> nillableElement2;

/**
 * Gets the value of the nillableElement1 property.
 *
 * @return
 * possible object is
 * {@link String }
 *
 */
public String getNillableElement1() {
    return nillableElement1;
}

/**
 * Sets the value of the nillableElement1 property.
 *
 * @param value
 * allowed object is
 * {@link String }
 *
 */
public void setNillableElement1(String value) {
    this.nillableElement1 = value;
}

/**
 * Gets the value of the nillableElement2 property.
 *
 * @return
 * possible object is
 * {@link JAXBElement }{@code <}{@link String }{@code >}
 *
 */
public JAXBElement<String> getNillableElement2() {
    return nillableElement2;
}

/**
 * Sets the value of the nillableElement2 property.
 *
 * @param value
 * allowed object is
 * {@link JAXBElement }{@code <}{@link String }{@code >}
 *
 */
public void setNillableElement2(JAXBElement<String> value) {
    this.nillableElement2 = value;
}

public String toString() {
    final ToStringStrategy strategy = JAXBToStringStrategy.INSTANCE;
    final StringBuilder buffer = new StringBuilder();
    append(null, buffer, strategy);
    return buffer.toString();
}

public StringBuilder append(ObjectLocator locator, StringBuilder buffer, ToStringStrategy strategy) {
    strategy.appendStart(locator, this, buffer);
    appendFields(locator, buffer, strategy);
    strategy.appendEnd(locator, this, buffer);
    return buffer;
}

public StringBuilder appendFields(ObjectLocator locator, StringBuilder buffer, ToStringStrategy strategy) {
    {
        String theNillableElement1;
        theNillableElement1 = this.getNillableElement1();
        strategy.appendField(locator, this, "nillableElement1", buffer, theNillableElement1);
    }
    {
        JAXBElement<String> theNillableElement2;
        theNillableElement2 = this.getNillableElement2();
        strategy.appendField(locator, this, "nillableElement2", buffer, theNillableElement2);
    }
    return buffer;
}

public boolean equals(ObjectLocator thisLocator, ObjectLocator thatLocator, Object object, EqualsStrategy strategy) {
    if (!(object instanceof OptionalNillable)) {
        return false;
    }
    if (this == object) {
        return true;
    }
    final OptionalNillable that = ((OptionalNillable) object);
    {
        String lhsNillableElement1;
        lhsNillableElement1 = this.getNillableElement1();
        String rhsNillableElement1;
        rhsNillableElement1 = that.getNillableElement1();
        if (!strategy.equals(LocatorUtils.property(thisLocator, "nillableElement1", lhsNillableElement1), LocatorUtils.property(thatLocator, "nillableElement1", rhsNillableElement1), lhsNillableElement1, rhsNillableElement1)) {
            return false;
        }
    }
    {
        JAXBElement<String> lhsNillableElement2;
        lhsNillableElement2 = this.getNillableElement2();
        JAXBElement<String> rhsNillableElement2;
        rhsNillableElement2 = that.getNillableElement2();
        if (!strategy.equals(LocatorUtils.property(thisLocator, "nillableElement2", lhsNillableElement2), LocatorUtils.property(thatLocator, "nillableElement2", rhsNillableElement2), lhsNillableElement2, rhsNillableElement2)) {
            return false;
        }
    }
    return true;
}

public boolean equals(Object object) {
    final EqualsStrategy strategy = JAXBEqualsStrategy.INSTANCE;
    return equals(null, null, object, strategy);
}

public int hashCode(ObjectLocator locator, HashCodeStrategy strategy) {
    int currentHashCode = 1;
    {
        String theNillableElement1;
        theNillableElement1 = this.getNillableElement1();
        currentHashCode = strategy.hashCode(LocatorUtils.property(locator, "nillableElement1", theNillableElement1), currentHashCode, theNillableElement1);
    }
    {
        JAXBElement<String> theNillableElement2;
        theNillableElement2 = this.getNillableElement2();
        currentHashCode = strategy.hashCode(LocatorUtils.property(locator, "nillableElement2", theNillableElement2), currentHashCode, theNillableElement2);
    }
    return currentHashCode;
}

public int hashCode() {
    final HashCodeStrategy strategy = JAXBHashCodeStrategy.INSTANCE;
    return this.hashCode(null, strategy);
}

}

---------- END SOURCE ----------

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant