Posts tagged ‘xjc’

Batalii z JAXB 2.1 ci膮g dalszy 馃檪

Po tym jak uda艂o mi si臋 zmusi膰 XJC do wygenerowania schemy, dodania do wygenerowanego kodu @XmlJavaTypeAdapter i zamiany „w locie” atrybutu na kolekcje (opisane tutaj) pojawi艂 sie ma艂y problem. Po pr贸bie dodania do <xjb:javaType /> silnie typizowanej kolekcji w nast臋puj膮cy spos贸b:

<xjc:javaType name="java.util.Map&lt;String,String&gt;" adapter="com.nsn.tariffmig.parser.output.LliLibReferenceAdapter" />

<!-- tak tak pamietajcie, 偶e to xml, wi臋c > i < trzeba zapisywa膰 jako encje -->


Pojawi艂 si臋 nast臋puj膮cy problem w wygenerowanym kodzie:


import java.util.Map<String,String>;

Czyli nie ca艂kiem to o co chodzi艂o 馃檨 Na szcz臋艣cie 藕r贸d艂a do JAXB s膮 dost臋pne, wi臋c postanowi艂em sprawdzi膰 ile zajmie znalezienie kodu, odpowiedzialnego za wypluwanie „import” co艣tam. Okaza艂o si臋 to bajecznie proste, szybki grep:

grep -nirs 'import' *.java | less

Pojawi艂o si臋 do艣膰 sporo wynik贸w ale przecie偶聽import to s艂owo kluczowe w Javie. Z drugiej jednak strony nietrudno by艂o odr贸偶ni膰 to czego szuka艂em, bo wi臋kszo艣膰 wpis贸w to prawdziwe importy, np:

com/sun/codemodel/JPackage.java:50:import java.util.Iterator;
com/sun/codemodel/JPackage.java:51:import java.util.List;
com/sun/codemodel/JPackage.java:52:import java.util.Map;

Natomiast ja szuka艂em tego:

com/sun/codemodel/JFormatter.java:110: importedClasses = new HashSet<JClass>();
com/sun/codemodel/JFormatter.java:266: * decide what types to import and what not to.
com/sun/codemodel/JFormatter.java:273: if(importedClasses.contains(type)) {

Teraz wystarczy tylko doda膰 „wymazywanie typu :D”, czyli zamieni膰 lini臋 438 z:

p(„import”).p(clazz.fullName()).p(鈥;鈥).nl();

na:

p(„import”).p(clazz.fullName().replaceAll(„<.*?>$”, „”)).p(鈥;鈥).nl();

I ju偶 – kompilujemy, wk艂adamy to do jara (nie zapomnijcie o licencjach) i ju偶 mo偶emy odpala膰 taska XJC z adapterami do silnie typizowanych kolekcji.

Prawda, 偶e proste…

Wyobra藕my sobie, 偶e mamy aplikacj臋, kt贸ra przetwarza dane na podstawie skomplikowanej konfiguracji w plikach xml, kt贸rych struktura jest opisana przez odpowiadaj膮ce im DTD. Tak tak wiem, pierwszy pomys艂, to wywali膰 DTD i zast膮pi膰 je na schema. Brzmi nie藕le, ale jest par臋 problem贸w, kt贸re trzeba rozwa偶y膰:

  • cz臋艣膰 plik贸w DTD pochodzi z zewn膮trz, wi臋c ich zmiana nie wchodzi w gr臋
  • struktura, kt贸r膮 opisuj膮 jest do艣膰 skomplikowana i przepisanie jej r臋cznie mo偶e zaj膮膰 nawet pare dni, a to nie jest to co tygryski lubi膮 najbardziej

Ale nie jest beznadziejnie, gdy偶 sun jaki艣 czas temu dostarczy艂 do艣膰 mi艂e nar臋dzie ukrywaj膮ce manipulacje drzewem XML’owym za obiektami JavaBean – JAXB. Nasz膮 wycieczk臋 rozpoczniemy od mi艂ego nardz臋dzia – XJC, umo偶wliaj膮cego wygenerowanie klas z istniej膮cego DTD/Schema.

No wi臋c do dzie艂a, szybki task ant’owy i mo偶na jecha膰 z koksem. Jednak pozostaje ma艂y problem – niekt贸re z atrybut贸w, maj膮 ograniczon膮 ilo艣膰 element贸w, ale niestety 聽z uwagi na to, 偶e jedyny typ jaki im mo偶na przypisa膰 to string siln膮 typizacj臋 trafia szlag :/

Jest jednak i na to rozwi膮zanie, coprawda nieco zakr臋cone i troch臋 naoko艂o, ale wynik jest zadowalaj膮cy. W sk艂ad JAXB, poza nar臋dziem XJC jest tak偶e SchemaGen, dost臋pne r贸wnie偶 jako SchemaGenTask dla ant’a, kt贸re to umo偶liwia wygenerowanie schem’y na podstawie gotowych klas i adnotacji.

Czyli najpierw z DTD robimy klasy przy pomocy XJC, a p贸藕niej zamieniamy na scheme’a przy u偶yciu SchemaGen (ostrzega艂em, 偶e to troche naoko艂o 馃檪 ).

No i dzi臋ki temu dla cz臋艣膰i klas mamy schem’e, co daje mo偶liwo艣ci rozszerzenia jej poprzez namespace jaxb, jednak aby nie by艂o zbyt prosto informacji jak na lekarstwo :/ Troche trzeba by艂o si臋 naszuka膰, ale znalaz艂em w ko艅cu stron臋 z opisem tego偶 namespace’a, wraz z przyk艂adami i ku mojemu zdumieniu okaza艂o si臋, 偶e rozwi膮zania problemu dodania silnej typizacji do atrybutu s膮 conajmniej 2:

  • mo偶na doda膰 enuma, z poziomu schem’y – trzeba wypisa膰 elementy kt贸re ma zawiera膰 a XJC zrobi reszt臋, wygeneruje klas臋 i zapewni siln膮 kontrol臋 typ贸w. Jedyny wym贸g jest taki, 偶eby nazwy poszeg贸lnych element贸w by艂y identyczne z dozwolonymi elementami atrybutu (przyk艂ad ze strony powy偶ej)
    <xs:simpleType name="ApptType">
            <xs:annotation>
                <xs:appinfo>
                    <!-- Map the elements of this simple type to a Java typesafe enum class. -->
                    <jxb:typesafeEnumClass/>
                </xs:appinfo>
            </xs:annotation>
            <!-- Use XML Schema elements restriction and enumeration to define the supported appointment types. -->
            <xs:restriction base="xs:string">
                <xs:enumeration value="Yearly Checkup"/>
                <xs:enumeration value="Well Mom Exam"/>
                <xs:enumeration value="Teeth Cleaning"/>
                <xs:enumeration value="Vaccination"/>
                <xs:enumeration value="Senior Pet Checkup"/>
            </xs:restriction>
        </xs:simpleType>
    
  • mo偶na doda膰 mapowanie typu na string’a (lub inny prymityw schem’a) realizowane poprzez klas臋 rozszerzaj膮c膮 XmlAdapter, dzi臋ki czemu w kodzie wygenerowanym ze schem’y zostanie wstawiona adnotacja XmlJavaTypeAdapter i podczas marshalling/unmarshallingu odb臋dzie si臋 wywo艂anie naszego adaptera (przyk艂ad te偶 ze strony)
    <xs:element name="printOrder" type="PrintOrderType"/>
        <xs:complexType name="PrintOrderType">
            <xs:sequence>
                <xs:element name="notifications" type="notificationsType" minOccurs="0"/>
            </xs:sequence>
            <xs:attribute name="id" type="xs:long" use="required">
                <xs:annotation>
                    <xs:appinfo>
                        <!-- Use an XmlAdapter-based adapter to create WePrintStuff's PrintOrderKey class. -->
                        <!-- Specify the name orderKey for this property. -->
                        <jxb:property name="orderKey">
                            <jxb:baseType>
                                <!-- Specify weprintstuff.print.PrintOrderKey as the type of the orderKey property. -->
                                <!-- Specify the adapter class that will map the schema type to the Java type. -->
                                <xjc:javaType name="weprintstuff.print.PrintOrderKey"
                                  adapter="weprintstuff.print.IdAdapter"/>
                            </jxb:baseType>
                        </jxb:property>
                    </xs:appinfo>
                </xs:annotation>
            </xs:attribute>
        </xs:complexType>
    

O ile dla nowych rozwi膮za艅, chyba najszybszym podej艣ciem b臋dzie zastosowanie „automatycznej” zamiany typu na enum’a poprzez wypisanie i listy dost臋pnych element贸w i pozwolanie XJC na zrobienie reszty, zamiast samemu definiowa膰 klase, kt贸ra b臋dzie odpowiedzialna za konwersje, o tyle ja szuka艂em akurat tego drugiego rozwi膮zania. Z uwagi na to, 偶e wcze艣niej podododawa艂em enum’y w kodzie, razem z odpowiednimi klasami/metodami s艂u偶膮cymi do ich zamiany. Wystarczy艂o wi臋c przenie艣膰 kod do odpowiedniego Adaptor’a i ju偶…

…i ju偶 ju偶 si臋 z g膮sk膮 wita艂…

Jednak okaza艂o si臋, 偶e to wcale nie koniec, bo po odpaleniu mojego generatora ze schemy dosta艂em b艂膮d, 偶e nie mog臋 u偶ywa膰 xjc:javaType wewn膮trz deklaracji i tutaj by艂em naprawde zaskoczony, bo pr贸ba zast膮pienia przez axb:javaType jak suegorwa艂 wyj膮tek, ko艅czy艂a si臋 jeszcze gorzej. Jak si臋 okaza艂o, b艂膮d by艂 w samym JAXB i przej艣cie na wersj臋 2.1.11 poprawi艂o sytuacj臋, ale okaza艂o si臋, 偶e task ant’owy zamiast atrybutu target posiada teraz destdir, wi臋c trzeba by艂o jeszcze to zapu艣ci膰 i klasy si臋 w ko艅cu wygenerowa艂y.

Mo偶liwo艣ci dodawania adapter贸w s膮 nieograniczone, teraz tylko musze doj艣膰 jak zrobi膰 co艣 takiego dla DTD, kt贸re pochodz膮 z zewn膮trz. Podejrzewam, 偶e da si臋 to zrobi膰 poprzez dodatkowy plik, kt贸ry mo偶na do艂膮czy膰 do mapowa艅 (external binding customization files) ale to ju偶 sobie zostawi臋 na nast臋pny wpis 馃檪