Posts tagged ‘jaxb’

Jakiś czas temu szukając informacji o szybkiej konfiguracji springa oraz połączeniu go z JAXB natrafiłem na blog Michała Mecha (strona Michała nie jest już dostępna – tekst w wayback machine)który to opisywał wspomniany przeze mnie problem.

Udało mi się w miarę sprawnie przenieść przykład, który zaprezentował na „własne podwórko” i zamiast wstrzykiwać do beana scieżkę do pliku który miałbyć zamieniony poprzez JAXB na obiekt, mogłem wstrzykiwać gotowy obiekt, a bean jak zwykle po prostu nie był swiadom skąd się ten obiekt wziął – z xml’a, czy może ktoś bo sobie „poskładał” łącząc odpowiednio pojo z mappingu xml’owego. Pojawił się tylko mały zgrzyt w momencie, gdy okazało się, że takich podmionionych bean’ów mam 40…

Zacząłem szukać jakiegoś sposobu aby uprościć konfigurację i zamknąć wszystko w jednej linijce i faktycznie udało się wykorzystując poszczególne elementy:

  • dodając do konfiga xml’owego przestrzeń nazw oxm
  • wykorzystując property editora
  • dodając element nadrzędny dla docelowego bean’a

Zmodyfikuje nieco oryginalny przykład w celu prezentacji rozwiązania:

Najpierw property editor w celu zamiany String’a na Source xml’owe:

package pl.bedkowski.test;

import java.beans.PropertyEditorSupport;
import java.io.File;

import javax.xml.transform.stream.StreamSource;

public class String2StreamSource extends PropertyEditorSupport {

	@Override
	public void setAsText(String filePath) {
		setValue(new StreamSource(new File(filePath)));
	}

}

No i kontekst apliacji:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:oxm="http://www.springframework.org/schema/oxm"

	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
	http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-1.5.xsd">

	<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
	  <property name="customEditors">
	    <map>
	      <entry key="javax.xml.transform.Source">
	        <bean class="pl.bedkowski.test.String2StreamSource"/>
	      </entry>
	    </map>
	  </property>
	</bean>

	<oxm:jaxb2-marshaller id="jaxb2Marshaller">
		<oxm:class-to-be-bound name="pl.michalmech.config.Config"/>
	</oxm:jaxb2-marshaller>

	<bean id="configParent"  class="pl.michalmech.config.Config" factory-bean="jaxb2Marshaller" factory-method="unmarshal" abstract="true" />

	<bean id="config1" parent="configParent">
		<constructor-arg value="config1.xml" />
	</bean>

	<bean id="config2" parent="configParent">
		<constructor-arg value="config2.xml" />
	</bean>

	<bean id="config3" parent="configParent">
		<constructor-arg value="config3.xml" />
	</bean>

	<bean id="config4" parent="configParent">
		<constructor-arg value="config4.xml" />
	</bean>

</beans>

Na początku może się wydawać, że to więcej jednak jeżeli odejmiemy te pare(-naście) linijek podczas każdej inicjalizacji bean’a dziedziczącego po „configParent”, podobnie do bean’ów z id’kami config1-config4.

Jakiś czas temu pisałem o dodawaniu adapterów do atrybutów poprzez namespace jaxb, a konkretnie korzystając z elementu jaxb:javaType. Zmiana ta umożliwia eleganckie korzystanie z Map’y po stronie javy (nawet silnie typizowanej z moim hakiem) a także zapis do xml’a string’ów pałkowo-średnikowych. Jednak jak wiadomo taki string to dość toporna struktura danych i jego utrzymanie dość szybko napotkało swoje naturalne ograniczenia:

  • po pierwsze rozmiar – po dodaniu paru elementów string pałkowo-średnikowy przestaje być czytelny
  • po drugie kontrola wersji – zmiana choćby jednego znaku w stringu pałkowo-średnikowym widoczna jest podczas sprawdzania i checkinowania zmian jako zmiana w jednej długaśnej linii, więc jakakolwiek kontrola tego co zostało zmienione staje się bardzo trudna, no i historia w kontorli wersji na mało się przydaje, no chyba, że ktoś ma wbudowany parser. Ja akurat takiego nie mam, więc każda zmiana to droga przez mękę.

Jak widać problem nabrał sporej rangi, no i pojawiło się pytanie czy nie dałoby się takiego rozwiązania zastąpić czymś bardziej czytelnym, czymś co oddawałoby strukture bez potrzeby łapania dziwnej zwiechy przy kolejnym zetknieciu z tym rozwiązaniem (zwiecha częściowo poświęcona na zadume i ciepłe słowa dla autora a częściowo  na sprawdzenie czy czasem się nie cierpi na jakąś chorobę oczu).

Pierwsze co przychodzi do głowy to zaaplikowanie kolejnego adaptera tym razem zamiast do atrybutu to do samego elementu. Pierwsze podejście to próba wdrożenia rozwiązania przedstawionego przez  Koshuke Kawaguchi już zresztą parę lat temu , wyglada na bułkę z masełkiem… Niestety jakoś nie udało mi się go skłonić do współpracy. Później wymyśliłem, żeby zamiast przekładania Listy zamienić ją na zwykłą tablicę, bo może dlatego adapter nie chwyta – niestety, tym razem już brak wyjątków, ale lista jest pusta. Podejście trzecie – wstawienie interfejsu kończy się wyjątkiem, bo JAXB nie obsługuje interfejsów. Aż tu jak już zrezygnowany ot tak zacząłem sobie szukać czy ktoś cokolwiek wymyślił w tej kwestii, natrafiłem na zbiór (działąjących!) tutoriali dla jee5 a w nich cały dział poświęcony JAXB. Niestety interesujący mnie przykład zwiera tylko klasy z adnotacjami, bez schemy. Postanowiłem troche poszukać, bo może udałoby się znaleźć jakis inny, który aplikuje adaptera do elementu, wtedy po połączeniu obu możnaby wygenerować całość ze schemy. No i faktycznie, znalazłem to czego potrzebuję.

Ostateczny wynik to:

  • dodanie elementu jaxb:Type jako podelementu (…) bez nadrzędnego jaxb:property
  • dodanie klasy zawijającej Map’ę czy też inna kolekcję (tak, żeby JAXB mogło operować na konkretnym typie)
  • dorzucenie adaptera
  • odpalenie JXC

A schema wyglada tak:


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:jxb="http://java.sun.com/xml/ns/jaxb" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
jxb:extensionBindingPrefixes="xjc" jxb:version="2.0"
targetNamespace="http://bedkowski.pl/elementAdapter" xmlns="http://bedkowski.pl/elementAdapter">

<xs:annotation>
<xs:appinfo>
<jxb:schemaBindings>
<jxb:package name="bla" />
</jxb:schemaBindings>
</xs:appinfo>
</xs:annotation>

<xs:element name="kitchenWorldBasket" type="KitchenWorldBasketType" />

<xs:element name="purchaseList" type="PurchaseListType">

</xs:element>

<xs:complexType name="KitchenWorldBasketType">
<xs:sequence>
<xs:element name="basket" minOccurs="0">
<xs:simpleType>
<xs:annotation>
<xs:appinfo>
<xjc:javaType name="java.util.Map" adapter="bla.AdapterPurchaseListToHashMap" />
</xs:appinfo>
</xs:annotation>

<xs:restriction base="xs:positiveInteger">
<xs:maxExclusive value="100" />
</xs:restriction>
</xs:simpleType>

</xs:element>
</xs:sequence>
</xs:complexType>

<xs:complexType name="PurchaseListType">
<xs:sequence>
<xs:element name="entry" type="partEntry" nillable="true"
minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>

<xs:complexType name="partEntry">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="key" type="xs:int" use="required" />
</xs:extension>
</xs:simpleContent>
</xs:complexType>

</xs:schema>

Wyobraźmy sobie taką sytuację, że mamy aplikację korzystającą z JAXB i do generowania klas wykorzystujemy task ant’owy XJC, na podstawie DTD dostarczanego z zewnątrz. Niestety jako, że nie jesteśmy autorami, ani właścicielami tego DTD modyfikacje mogą się pojawić niespodziewanie z tego powodu zamiana go na scheme nie wchodzi w grę.

Jako wprawni programiści szybko zauważamy, że pewne elementy mają powtarzające się własności, jednak z uwagi na to, że to DTD wykorzystanie polimorfizmu jest żadne i powstają takie brzydactwa:

String name = null;
if( get1 ) {
Tag1 t1 = container.getTag1();
name = tag.getName();
}
else {
Tag2 t2 = container.getTag2();
name = tag.getName();
}

Oczywiście zmienna get1 jest zdeklarowana gdzieś wcześniej. Ten kod aż krzyczy o wywołania polimorficzne, ale z uwagi na to, że obiektów poza nazwą nic nie łączy, nie można skorzystać z tego cudownego wynalazku.

Z pomocą przychodzą nam wzorce projektowe. Na początek weźmiemy się za napisanie zawijacza do naszej struktury, który to pozwoli wykorzystać poliformizm – niektórzy powiedzą, że to Proxy inni, że dekorator, ja uznałem, że opiszę go bardziej ogólnie jako „zwijacz”, gdyż na początku będzie przypominał zwykłe proxy, później już chyba nieco bardziej dekoratora, no ale do dzieła.

Zacznijmy od prostego interfejsu:

public interface MyTag {
String getName();
}

Dodajmy klasę abstrakcyjną, która tenże interfejs implementuje:

public class MyAbstractTag implements MyTag {}

Po co nam klasa abstrakcyjna, o tym już za chwile 🙂

Następnie piszemy konkretne „zawijacze” dla poszczególnych obiektów:

public class MyTag1 extends MyAbstractTag {
private Tag1 tag;
public MyTag1(Tag1 tag) {
this.tag = tag;
}
public String getName(){
return tag.getName();
}
}

I Tag2 prawie identycznie:

public class MyTag2 extends MyAbstractTag {
private Tag2 tag;
public MyTag2(Tag2 tag) {
this.tag = tag;
}
public String getName(){
return tag.getName();
}
}

W c++ byłoby jescze prościej, bo możnaby było zmusić kompilator do wygenerowania dla nas obu klas z szablonu, ale niestety w Javie, szablony nie sięgają do czasu wykonania, więc pozostaje nam czas kompilacji, ale to wcale nie znaczy, że należy z tego rezygnować:)

public class MyAbstractTag<T> implements MyTag {
protected T tag;
public MyAbstractTag(T tag, Class<T> clazz){
this.tag = tag;
}
}

I implementacja dla MyTag1

public class MyTag1 extends MyAbstractTag<Tag1> {
public MyTag1(Tag1 tag) {
super(tag, Tag1.class);
}
public String getName(){
return tag.getName();
}
}

Niby nic, bo metodę i tak musimy zadeklarować w implementacji, ale zawsze można nacieszyć oko 🙂 Dodajmy małą metodę do budowania zawijaczy:

public MyTag getTag(TagType type, Wrapper wrapper){
MyTag ret = null;
switch(type){
case Tag1: ret = new MyTag1(wrapper.getTag1()); break;
case Tag2: ret = new MyTag2(wrapper.getTag2()); break;
}
return ret;
}// wrócmy do przykładu z góry:
MyTag t = getTag(TagType.Tag1, wrapper);
// już nas zupełnie nie interesuje co jest pod spodem:
System.out.println( t.getName() );

I takim to sposobem doszliśmy do „prostej faktorii”, zwanej czasem też (ponoć mylnie) wzorcem projektowym factory. Z którym ma tyle wspólnego, że produkuje obiekty :)Jednak nic nie stoi na przeszkodzie iść dalej tym tropem, załóżmy, że nasze obiekty Tag1/Tag2 zwracają dane w dość dziwnym formacie jak na xml, ale np jeden z atrybutów ma listę oddzieloną przecinkami. Powodów może być wiele, przyjmijmy ten pozytywny – po prostu tak musiało być inaczej masa plików xml, które już mamy i działają wymagałaby modyfikacji, co do tych mniej pozytywnych to już pozostawiam wyobraźni czytelnika.

Wracając do tematu wzorców, jaki by tutaj pasował – metoda szablonowa. Dlaczego? Bo jedyne co potrzebujemy z obiektów Tag1/Tag2 to wartość atrybutu, cała reszta przetwarzania odbywa się w jednym miejscu (klasa abstrakcyjna). Dodajemy publiczną metodę do interfejsu:

// dodajemy metode do interfejsu
public interface MyTag {
// wcześniejsze deklaracje
List<String> getList();
}

Teraz klasa abstrakcyjna implementuje publiczny interfejs i dodaje swoją metodę abstrakcyjną, żeby pobrać wartość atrybutu z implementacji:

public class MyAbstractTag<T> implements MyTag {
// wszystko jak poprzednio
public List<String> getList(){
String list = doGetList();
if( list == null ) {
return new ArrayList();
}
return Arrays.asList(list.split(","));
}
protected abstract String doGetList();
}

Prawda, że pięknie 🙂 Jedyne co musi dostarczyć implementacja, to metodę do pobierania swojego atrybutu:

public class MyTag1 extends MyAbstractTag<Tag1> {
// reszta bez zmian
protected String doGetList(){
return tag.getList();
}
}

I wykorzystaliśmy złotą zasadę Hollywood – „nie dzwoń do nas to my zadzwonimy do Ciebie” implementacja dostarcza nam danych a klasa abstrakcyjna może z tego skorzystać albo i nie, do tego odwracamy zależności.Możnaby się jeszcze rozwodzić długo nad zaletami takiego rozwiązania, ale trzeba go po prostu spróbować!

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 🙂