19

I'm migrating a project from JAXB 1.0 to JAXB 2.1 and I'm having problems with the datatype mapping.

I'm using the Ant xjc binding compiler, and I've successfully configured the global bindings such that (for example) xs:date maps to java.util.Calendar.

However I'm getting generated methods which return Boolean, whereas I want boolean.

Here is the complex type:

<xs:element name="usage-auth-rate-charge">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="service-id" type="xs:string"/>
            <xs:element name="pricepoint_custom_fields_required" type="xs:boolean" minOccurs="0"/>
        </xs:sequence>
    </xs:complexType>
</xs:element>

And the generated class looks like this:

public class UsageAuthRateCharge {
........
public Boolean isPricepointCustomFieldsRequired() {
    return pricepointCustomFieldsRequired;
}

The problem is that although boxing will work, if the supplied XML doesn't contain a value for pricepoint_custom_fields_required, the class's Boolean field is null, instead of false. So I get NullPointerExceptions when doing something like this:

methodWhichTakesPrimitiveBooleanArg(myUsageAuthRateChargeInstance.isPricepointCustomFieldsRequired());

because it tries to unbox the Boolean passed in - except it's null.


I can't change the schema, and I can't adjust all the client code to do the null checks.

I've set the optionalProperty attribute in my binding.xml as follows:

<globalBindings optionalProperty="primitive">

In the spec, it says: "If the attribute’s value is "primitive", it binds as it did in JAXB 1.0"

Yet this is clearly not happening.

How can I solve this problem?

UPDATE:

This is now fixed in jaxb 2.2.9: https://java.net/jira/browse/JAXB/fixforversion/16850

mdarwin
  • 1,684
  • 7
  • 28
  • 72
  • 1
    Here is a link to a related JAXB RI bug: http://java.net/jira/browse/JAXB-510. – bdoughan Oct 23 '12 at 18:09
  • I cannot try it right now, but can you also set jaxb:globalBindings generateIsSetMethod="true" and see if it works? This would be indeed as in JAXB 1.0, as it was the mechanism used to handle your scenario (minOccurs=0 on a primitive). – Petru Gardea Oct 23 '12 at 18:37
  • Petru, as you say that generates a method like this: `public boolean isSetPricepointCustomFieldsRequired() { return (this.pricepointCustomFieldsRequired!= null); }` However I don't want this method, as I would have to change all my application code in order to use it. – mdarwin Oct 24 '12 at 07:43
  • @BlaiseDoughan, thanks, I saw that. I notice the ticket is marked as 'cannot reproduce'. In the comments somebody has suggested changing the xsd as a solution to the problem, which as we have seen, is avoiding the problem. – mdarwin Oct 24 '12 at 07:47
  • I've re-opened the ticket. See http://java.net/jira/browse/JAXB-926 I'm a bit disappointed that my migration to JAXB 2 has to stop because it doesn't conform to the spec. I've spent several days on this, and it's basically all been time wasted. – mdarwin Oct 24 '12 at 08:09
  • @mdarwin - I just emailed the JAXB (JSR-222) spec lead about this issue. I will post back when I have more information. – bdoughan Oct 24 '12 at 10:00
  • Thanks @BlaiseDoughan. I opened a new ticket (http://java.net/jira/browse/JAXB-927) since the other one was getting no attention, probably because it was listed as fixed in 2.1.8. – mdarwin Oct 27 '12 at 17:16
  • 1
    @mdarwin - I just heard back from the JAXB (JSR-222) spec lead. He has confirmed this as a bug and they are currently looking into the issue. – bdoughan Oct 30 '12 at 15:29
  • @BlaiseDoughan how do I follow this up with the JAXB guys? It's been a year now, and still no update :-( – mdarwin Oct 11 '13 at 11:34

4 Answers4

30

Problem

The reason you are getting Boolean instead of boolean is that you have minOccurs="0" in your element definition. A null value will be stored for an absent element.

<xs:element name="pricepoint_custom_fields_required" type="xs:boolean" minOccurs="0"/>

What the Solution Should Be

The solution should be an external binding file that indicates that a primitive type should be used for optional values (see section 7.5.1 of the JAXB 2.2 specification).

<?xml version="1.0" encoding="UTF-8"?>
<jaxb:bindings
    xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
    version="2.1">
    <jaxb:globalBindings optionalProperty="primitive"/>
</jaxb:bindings>

The external binding file is specified using the -b option with XJC.

xjc -b binding.xml my-schema.xsd

Why That Solution Does Not Work

The following bugs have been opened to track the issue with optionalProperty="primitive".

Workaround

If you don't like the way a class is generated in JAXB, then you can create a class yourself and have JAXB pull it in as part of the schema to class generation.

binding.xml

<jxb:bindings 
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
    version="2.1">

    <jxb:bindings schemaLocation="my-schema.xsd">
        <jxb:bindings node="//xs:element[@name='usage-auth-rate-charge']/complexType">
            <jxb:class ref="com.example.foo.UsageAuthRateCharge"/>
        </jxb:bindings>
    </jxb:bindings>
</jxb:bindings>

UsageAuthRateCharge

Create this class with the methods as you want them.

XJC Call

Use the -b flag on the XJC call to specify the binding.xml file

xjc -b binding.xml my-schema.xsd

UPDATE

What I was really hoping was that one of the readers might be a jaxb dev team member who could do the change easily in jaxb. User archenroot offered a possible solution in his comments on jaxb issue 927 (a duplicate of 926) which leads me to think that for the right person, it would be a simple job to fix jaxb

I'm the EclipseLink JAXB (MOXy) lead. I have reached out to my colleagues who do the JAXB reference implementation (they also work for Oracle). Below is the reply I got from them:

I’ve tested in trunk - and the problem exists. I’ll check code how easy is to fix it.

Hopefully you will be able to get a real fix for this issue.

Peter
  • 965
  • 7
  • 13
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • 1
    Hi Blaise, thanks for your detailed answer. The problem with this solution is that the problem affects not one or two, but hundreds of methods across many classes. So it's not really practical to write every class / interface by hand. – mdarwin Dec 16 '13 at 14:14
  • What I was really hoping was that one of the readers might be a jaxb dev team member who could do the change easily in jaxb. User archenroot offered a possible solution in his comments on jaxb issue 927 (a duplicate of 926) which leads me to think that for the right person, it would be a simple job to fix jaxb. – mdarwin Dec 16 '13 at 14:16
  • @mdarwin - I have made contact with one of my colleagues who works on the XJC component of the RI. He is looking into this issue. – bdoughan Dec 16 '13 at 16:53
  • @mdarwin - I am currently working with the XJC developer on what we need to do to fix the issue. We're currently figuring out how this impacts the isSet method. – bdoughan Dec 18 '13 at 15:33
  • thanks very much for picking up this issue and getting the attention of the dev team on this. – mdarwin Dec 19 '13 at 08:59
  • here's my 2 cents on how to handle the isSet method: continue to return a Boolean - but if optionalProperty="primitive", return false if the Boolean is null. isSet requires no modifications. Java unboxing will do the rest. – mdarwin Jan 08 '14 at 16:34
3

Try this...

<xs:element name="usage-auth-rate-charge">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="service-id" type="xs:string"/>
        </xs:sequence>
        <xs:attribute name="chosen" type="xs:boolean" use="required"/>
    </xs:complexType>
</xs:element>

I found your question as I was looking how to do the exact opposite thing you were doing. I had a boolean attribute that would only generate code that had the attribute as a primitive boolean value. To make jaxb generate this attribute as a Boolean object instead of a boolean primitive, I just removed the use="required" portion of my attribute's definition in the xsd.

twindham
  • 930
  • 9
  • 31
  • 2
    Thanks. I have no doubt I can get this to work by tweaking the xsd like that. I could also set minOccurs="1" or default="false". However, I can't change the xsd, since it's in use by the existing (JAXB 1.0-based) code, and has been made public to clients. – mdarwin Oct 26 '12 at 07:29
  • 1
    Probably a bad workaround, but what if you change the XSD, generating the wanted code, and get the old XSD back? However, if you can't remove the minOccurs="0" from the XSD source, i think you are risking nullpointer exception. Why not managing this on java side (consider null as false by only testing true value: BooleanUtils.isTrue(myBool)). – Kloe2378231 Dec 16 '13 at 16:04
2

Just to make it complete

type="xs:boolean" minOccurs="0" maxOccurs="1"                   == Boolean value (object)

type="xs:boolean" minOccurs="0" maxOccurs="1" nillable="true"   == JAXBElement<Boolean> value (object)

type="xs:boolean" minOccurs="1" maxOccurs="1"                   == boolean value (primitive)

type="xs:boolean" minOccurs="1" maxOccurs="1" nillable="true"   == Boolean value (object)
hocikto
  • 871
  • 1
  • 14
  • 29
0

I got bored of waiting for a fix from the dev team so I rolled up my sleeves and did it myself.

I'm including the code below to help people for whom this is also an issue.

Disclaimer: my code probably isn't the best way to solve the problem but it works for me.

The generated code now looks like this:

public boolean isPricepointCustomFieldsRequired() {
    if (pricepointCustomFieldsRequired == null) {
        return false;
    } else {
        return pricepointCustomFieldsRequired;
    }
}

And the modification is as follows:

com.sun.tools.xjc.reader.xmlschema.bindinfo.BIProperty:createElementProperty, line ~358

After the line

types.addTo(prop);

insert the following code:

if (prop.isOptionalPrimitive() && getOptionalPropertyMode() == OptionalPropertyMode.PRIMITIVE &&
    !prop.getTypes().isEmpty() && "boolean".equals(prop.getTypes().get(0).getTypeName().getLocalPart()) )   {
        prop.defaultValue= CDefaultValue.create(CBuiltinLeafInfo.BOOLEAN, new XmlString("false"));
    }
mdarwin
  • 1,684
  • 7
  • 28
  • 72