2015-05-25 12 views
12

Sto utilizzando jaxb per generare classi java da un file xsd. Lo xsd contiene una definizione di un elemento di cui il contenuto è un elenco di costanti definito nello stesso xsd come enumerazione.jaxb: strana eccezione del cast di classe nell'elenco enum

Quando le classi vengono generate utilizzando l'implementazione di riferimento JAXB da Oracle jdk1.7 (v2.2.4-2) è possibile scorrere l'elenco di enumerazioni e assegnarle le variabili dello stesso tipo.

Tuttavia, quando le classi sono generati utilizzando l'oracolo di jdk1.8 (build 1.8.0_45-b15 - ultima come di distacco data) implementazione di riferimento JAXB (v2.2.8-b130911.1802) non è più possibile assegnare gli elementi della lista alla variabile del tipo enum.

Qualsiasi tentativo di assegnare o iterare utilizzando migliorato per il ciclo termina con una 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) 

La lista stessa è in entrambi i casi parametrizzati con il tipo enum corretto.

Qui è un codice che riproduce il problema descritto in precedenza:

file XSD

<?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> 

file XML di prova

<?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> 

lettore di prova

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); 
      } 
     } 
    } 
} 

jdk1.7 uscita

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

jdk1.8 uscita

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

Dall'output sopra, è chiaro che gli elementi del tipo java.lang.String sono contrabbandate nella lista List<GConstNameType> o che sia impostato un elenco di String s invece di un elenco GConstNameType. Comunque i nomi delle enumerazioni String dal file xml non sono mappati alle costanti java enum.

Il runtime Java è in entrambi i casi lo stesso, è il dal jdk1.8.

I comandi utilizzati per la generazione:

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 

e

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 
  • Come è possibili/Che cosa sta accadendo qui?
  • La definizione dello schema mostrata sopra non è corretta per la definizione di un'enumerazione?
  • Come aggirare questo problema senza dover utilizzare un XmlAdapter (un modo che avrebbe funzionato su ogni versione di jdk)?

EDIT

L'unica differenza codice tra entrambi i pacchetti generati

The only code difference between both generated packages

Rimozione l'annotazione

@XmlSchemaType(name = "anySimpleType") 

esegue il rendering del codice generato jdk1.8 perfettamente funzionante.

  • Perché la più recente implementazione JAXB esegue il mapping dell'enumerazione a anySimpleType?
+0

Hai provato a usare * * invece di * *? Sembra che Java8 sia meno indulgente su ciò che una stringa XML potrebbe o non potrebbe essere. – medveshonok117

+0

@ medveshonok117 ha provato con 'xs: token' come suggerito ma' xcj' sta ancora generando '@XmlSchemaType (name =" anySimpleType ")' che porta a 'ClassCastException'. – A4L

+0

Problema nel progetto JAXB GH: https://github.com/gf-metro/jaxb/issues/21 –

risposta

2

è possibile modificare la xsd a:

<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> 

questo sta lavorando su Java 8.

il nuovo parser ha alcune nuove restrications.

UPDATE: per il tuo commento è possibile utilizzare questo:

<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> 

+0

Grazie per la risposta. Mentre questo risolve il problema, trovo che non sia molto intelligente dover definire il tipo in linea ogni volta che è necessario piuttosto che definirlo una volta e fare riferimento a esso usando l'attributo _type = "..." _. Puoi approfondire ulteriormente queste restrizioni? C'è qualche specifica che dice che le liste di enumerazione devono ora essere definite in questo modo (imho entrambe le definizioni sono equivalenti)? In ogni caso, chiamerei il fatto che la generazione di un elenco di 'String's e l'iniezione in un elenco di' GConstNameType' è un errore di rathaer piuttosto che una restrizione. Le restrizioni funzionano in senso inverso ;-) – A4L

+0

@ A4L intendi la definizione della lista? – igreen

+0

sì, il semplice tipo 'GConstType' che nella tua versione è anonimo poiché inline. – A4L

Problemi correlati