jaxb: strange class cast exception on enum list
Asked Answered
A

1

12

I am using jaxb to generate java classes from an xsd file. The xsd contains a definition of an element of which the content is a list of constants defined in the same xsd as enumeration.

When the classes are generated using the JAXB reference implementation from the oracle's jdk1.7 (v2.2.4-2) it is possible to iterate over the list of enums and assign them the variables of the same type.

However, when the classes are generated using oracle's jdk1.8 (build 1.8.0_45-b15 - latest as of posting date) JAXB reference implementation (v2.2.8-b130911.1802) it is no longer possible to assign the elements of the list to variable of the enum type.

Any attempt to assign or iterate using enhanced for loop ends with a ClassCastException

java.lang.ClassCastException: java.lang.String cannot be cast to so.jaxb.enums.generated.GConstNameType
    at so.jaxb.enums.domain.TestReader.readTest(TestReader.java:36)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

The list itself is in both cases parameterized with the correct enum type.

Here is a code reproducing the problem described above:

XSD file

<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns="http://www.w3.org/2001/XMLSchema" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:tns="http://www.foo.com/xmlns/test"
    targetNamespace="http://www.foo.com/xmlns/test"
    attributeFormDefault="unqualified"
    elementFormDefault="qualified">

    <xs:simpleType name="GConstType">
        <xs:list itemType="tns:GConstNameType" />
    </xs:simpleType>
    <xs:simpleType name="GConstNameType">
        <xs:restriction base="xs:string">
            <xs:enumeration value="FOO" />
            <xs:enumeration value="BAR" />
            <xs:enumeration value="BAZ" />
        </xs:restriction>
    </xs:simpleType>

    <xs:complexType name="TestType">
        <xs:all>
            <xs:element name="const-name-list" 
                type="tns:GConstType" minOccurs="0" maxOccurs="1" />
        </xs:all>
    </xs:complexType>

    <xs:element name="test" type="tns:TestType" />

</xs:schema>

Test XML file

<?xml version="1.0" encoding="UTF-8"?>
<t:test xmlns="http://www.w3.org/2001/XMLSchema"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:t="http://www.foo.com/xmlns/test"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <t:const-name-list>FOO BAR</t:const-name-list>

</t:test>

Test reader

public class TestReader {

    @Test
    public void readTest() throws IOException {
        try (InputStream xml = TestReader.class
                .getResourceAsStream("/so/jaxb/enums/resources/test.xml");
            InputStream xsd = TestReader.class
                .getResourceAsStream("/so/jaxb/enums/resources/test.xsd")) {
            TestType testType = fromXML(TestType.class, xml, xsd);
            List<GConstNameType> constNameList = testType.getConstNameList();
            for(Object constName : constNameList) {
                System.out.println(constName.getClass().getName());
            }
            for(GConstNameType constName : constNameList) {
                System.out.println(constName);
            }
        }
    }

    public static <T> T fromXML(Class<T> _class, InputStream xml, InputStream xsd) {
        XMLStreamReader xsr = null;
        try {
            Source xmlSource = new StreamSource(xml);
            Source xsdSource = new StreamSource(xsd);
            JAXBContext jaxbContext = JAXBContext.newInstance(_class);
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            Schema schema = SchemaFactory
                .newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(xsdSource);
            unmarshaller.setSchema(schema);
            xsr = XMLInputFactory.newInstance().createXMLStreamReader(xmlSource);
            JAXBElement<T> jaxbElement = unmarshaller.unmarshal(xsr, _class);
            return jaxbElement.getValue();
        } catch (JAXBException | SAXException | XMLStreamException | FactoryConfigurationError e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if(xsr != null) {
                    xsr.close();   
                }
            } catch(XMLStreamException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

Output jdk1.7

so.jaxb.enums.generated.GConstNameType
so.jaxb.enums.generated.GConstNameType
FOO
BAR

Output jdk1.8

java.lang.String
java.lang.String
<ClassCastException>

From the output above it is clear that elements of the type java.lang.String are smuggled into the list List<GConstNameType> or that a List of Strings is set instead of a GConstNameTypes List. Anyhow the String enums names from the xml file are not mapped to java enum constants.

The java runtime is in both cases the same, it's the jre from the jdk1.8.

The commands used for generation:

C:\Oracle\Java\jdk1.7\bin\xjc.exe D:\dev\java\Tests\src\so\jaxb\enums\resources\test.xsd -b D:\dev\java\Tests\src\so\jaxb\enums\resources -d D:\dev\java\Tests/src -p so.jaxb.enums.generated -extension 

and

C:\Oracle\Java\jdk1.8\bin\xjc.exe D:\dev\java\Tests\src\so\jaxb\enums\resources\test.xsd -b D:\dev\java\Tests\src\so\jaxb\enums\resources -d D:\dev\java\Tests/src -p so.jaxb.enums.generated -extension 
  • How is this possibles / What is happening here?
  • Is the schema definition shown above not correct for defining an enumeration?
  • How to work around this problem without having to use an XmlAdapter (a way that would work on every jdk version)?

EDIT

The only code difference between both generated packages

The only code difference between both generated packages

Removing the annotation

@XmlSchemaType(name = "anySimpleType")

renders the jdk1.8 generated code fully functional.

  • Why is the newer JAXB implementation mapping the enum to anySimpleType?
Askew answered 25/5, 2015 at 13:58 Comment(3)
Have you tried using <xs:restriction base="xs:token"> instead of <xs:restriction base="xs:string"> ? It seems that Java8 is less lenient on what a XML string could or could not be.Setzer
@medveshonok117 tried with xs:token as suggested but xcj is still generating @XmlSchemaType(name = "anySimpleType") which leads to the ClassCastException.Askew
Issue in the JAXB GH project: github.com/gf-metro/jaxb/issues/21Aspergillum
C
2

you can change your xsd to:

 <xs:complexType name="TestType">
    <xs:sequence>
        <xs:element name="const-name-list">
            <xs:simpleType>
                <xs:list itemType="tns:GConstNameType"/>
            </xs:simpleType>
        </xs:element>
    </xs:sequence>
</xs:complexType>

<xs:simpleType name="GConstNameType">
    <xs:restriction base="xs:string">
        <xs:enumeration value="FOO"/>
        <xs:enumeration value="BAR"/>
        <xs:enumeration value="BAZ"/>
    </xs:restriction>
</xs:simpleType>

this is working on java 8.

the new parser has some new restrications.

UPDATE: for your comment you can use this:

<xs:complexType name="TestType">
    <xs:complexContent>
        <xs:extension base="tns:ListType">
            <xs:sequence/>
        </xs:extension>
    </xs:complexContent>
</xs:complexType>

<xs:complexType name="ListType">
    <xs:sequence>
        <xs:element name="const-name-list">
            <xs:simpleType>
                <xs:list itemType="tns:GConstNameType"/>
            </xs:simpleType>
        </xs:element>
    </xs:sequence>
</xs:complexType>

<xs:complexType name="SecondTestType">
    <xs:complexContent>
        <xs:extension base="tns:ListType">
            <xs:sequence/>
        </xs:extension>
    </xs:complexContent>
</xs:complexType>

<xs:simpleType name="GConstNameType">
    <xs:restriction base="xs:string">
        <xs:enumeration value="FOO"/>
        <xs:enumeration value="BAR"/>
        <xs:enumeration value="BAZ"/>
    </xs:restriction>
</xs:simpleType>

Cooney answered 14/6, 2015 at 17:47 Comment(5)
Thank you for your answer. While this works around the issue, I find it not very smart to have to define the type inline each time it is needed rather than defining once and referencing it using the attribute type="...". Can you elaborate more on those restrictions? Is there any specification that tells that enum lists now have to be defined like this (imho both definitions are equivalent)? In any way, I would call the fact that generating a list of Strings and injecting into a list of GConstNameType is rathaer a bug than a restriction. Restrictions work the other way around ;-)Askew
@Askew you mean the definition of the list?Cooney
yes, the simple type GConstType which in your version is anonymous since inlined.Askew
@Askew I put up-to-date xsd.Cooney
Hey, I saw your update but didn't had the time to evaluate it. Your second suggestion works too, but I find the first variant better since it alters the original xsd only in a minimal way, in comparison to the second one. I will accept your answer since it provides a workaround that results in working generated code from both versions of xjc. No bounty awarded since no further explanations are provided :(, but anyways thank you again for your time and help!Askew

© 2022 - 2024 — McMap. All rights reserved.