2015-05-25 8 views
12

Używam klasy jaxb do generowania klas java z pliku xsd. xsd zawiera definicję elementu, którego treść jest listą stałych zdefiniowanych w tym samym xsd jako wyliczenie.jaxb: dziwny wyjątek odlewania klasy na liście wyliczeniowej

Gdy klasy są generowane przy użyciu implementacji referencyjnej JAXB z poziomu Oracle jdk1.7 (v2.2.4-2), możliwe jest iterowanie na liście wyliczeń i przypisywanie im zmiennych tego samego typu.

Jednak, gdy klasy są generowane przy użyciu Oracle jdk1.8 (build 1.8.0_45-b15 - najpóźniej od dnia opublikowania data) wdrożenie odniesienia JAXB (v2.2.8-b130911.1802) nie jest już możliwe, aby przypisać elementy listy do zmiennej typu wyliczeniowego.

Każda próba przypisania lub iteracyjne Użycie rozszerzonego dla pętli kończy się 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) 

Lista samo jest w obu przypadkach parametryzowane z odpowiedniego typu wyliczeniowego.

Tutaj jest kod odtworzenia problemu opisanego powyżej:

XSD pliku

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

testowy plik XML

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

czytnika test

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 Wyjście

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

jdk1.8 Wyjście

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

Z wyjścia powyżej oczywiste jest, że elementy typu java.lang.String są przemycane do listy List<GConstNameType> lub ustawiona jest lista String s zamiast listy GConstNameType s. W każdym razie String nazwy z pliku xml nie są mapowane do stałych enum Java.

Środowisko wykonawcze java jest w obu przypadkach takie samo, jest to jre z jdk1.8.

Polecenia wykorzystywane do generowania:

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 

i

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 
  • Jak to possibles/Co się tutaj dzieje?
  • Czy definicja schematu pokazana powyżej jest niepoprawna do zdefiniowania wyliczenia?
  • Jak obejść ten problem bez konieczności korzystania z XmlAdapter (sposób, który działałby w każdej wersji jdk)?

EDIT

Jedyna różnica pomiędzy dwoma kod generowanych pakietów

The only code difference between both generated packages

Usuwanie adnotacji

@XmlSchemaType(name = "anySimpleType") 

powoduje, że wygenerowany kod jdk1.8 jest w pełni funkcjonalny.

  • Dlaczego nowsze JAXB realizacja mapowania enum do anySimpleType?
+0

Czy próbowali za pomocą * * zamiast * *? Wygląda na to, że Java 8 jest mniej łagodna w odniesieniu do tego, co łańcuch XML może lub nie może być. – medveshonok117

+0

@ medveshonok117 próbowano z 'xs: token' jako sugerowanym, ale' xcj' nadal generuje '@XmlSchemaType (name =" anySimpleType ")' co prowadzi do 'ClassCastException'. – A4L

+0

Problem w projekcie JAXB GH: https://github.com/gf-metro/jaxb/issues/21 –

Odpowiedz

2

można zmienić swój XSD do:

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

ten działa na java 8.

nowy parser ma kilka nowych restrications.

UPDATE: dla Twojego komentarza można używać to:

<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

Dziękuję za odpowiedź. Podczas gdy działa to wokół problemu, uważam, że nie jest zbyt inteligentne definiowanie wpisanego typu za każdym razem, gdy jest ono potrzebne, a nie definiowanie raz i odwoływanie się do niego za pomocą atrybutu _type = "..." _. Czy możesz rozwinąć więcej na temat tych ograniczeń? Czy istnieje jakaś specyfikacja, która mówi, że listy wyliczeniowe muszą teraz być zdefiniowane w ten sposób (czy obie definicje są równoważne)? W każdym razie nazwałabym to, że generowanie listy 'String'ów i wstrzykiwanie do listy' GConstNameType' jest raczej błędem niż ograniczeniem. Ograniczenia działają odwrotnie ;-) – A4L

+0

@ A4L masz na myśli definicję listy? – igreen

+0

tak, prosty typ 'GCststType', który w twojej wersji jest anonimowy od inlined. – A4L