Some of posts from this blog has been moved to dywicki.pl. You will be automatically redirected to new blog if you would submit comment.
New posts are published on dywicki.pl, this blog contains old content and it is not continued.

Niektóre posty z tego bloga zostały przeniesione do dywicki.pl. Zostaniesz automatycznie przekierowany jeśli bedzięsz chciał dodać komentarz.
Nowe posty sa publikowane na dywicki.pl, ten blog zawiera stare treści i nie jest kontynuowany.

JAXB 2, Wprowadzenie

Filed under Eclipse,Java,JAXB,tlumaczenia,XML by

W ramach WarsJava, konferencji/warsztatów organizowanych przez Warszawski JUG, będę miał szansę przedstawić publiczności Java Architecture for XML Binding w wersji drugiej. Swoje boje z JAXB postanowiłem opisać na blogu, być może dla kogoś temat wyda się interesujący.. :)

Ogólniki

JAXB ma za zadanie ułatwić pracę z XML poprzez automatyczne dostarczanie obiektów zamiast żmudnego, ręcznego obrabiania plików SAX, DOM czy StAX. Jest to dodatkowa warstwa zbudowana na bazie Java Api for XML Processing
. Druga wersja JAXB jest dołączona do J2SE 1.6.

Spójrzmy teraz na schemat budowy JAXB:
JAXB, schemat

  1. Kompilator
    1. Schema – dokument opisujący strukturę dokumentu – może to być XML Schema, DTD jak również Relax NG czy też WSDL.
    2. XML /Java Binding Customization – JAXB daje nam możliwość wpływania na wygenerowany kod poprzez ten właśnie mechanizm, dzięki temu można określić np. metody, które będą tworzyć elementy w przypadku odczytu z dokumentu bądź serializować w przypadku zapisu.
  2. Runtime
    1. Portable JAXB-annotated classes – kod, który wygenerowaliśmy przy użyciu kompilatora bądź klasy, do których dodaliśmy adnotacje.
    2. Object Factory – klasa, która tworzy obiektowe reprezentacje naszych elementów XML.
    3. Binding runtime framework implementation – obsługuje proces odczytu danych (unmarshalling) oraz ich zapisu (marshalling), obsługuje również walidację. Element ten do poprawnego działania wymaga elementów wymienionych powyżej – czyli klas z adnotacjami oraz ich “fabryki”.

Pierwszy projekt

Pamiętam, że kiedyś chciałem stworzyć prostą aplikację w której mógłbym notować jakie książki mam i komu je wypożyczyłem. Niestety w którymś momencie zabrakło chyba sił i poza definicją struktury dokumentu stworzyłem tyko kilka linii kodu. Nie mniej, po drobnych przeróbkach udało się doprowadzić schemat do porządku. W aplikacji będą występować następujące instancje klas:

  1. Books – repozytorium książek, zawiera listę książek
  2. Book – definicja książki – atrybuty to title, isbn oraz lista autorów
  3. Author – reprezentacja autora – atrybuty firstName, lastName. Dodatkowo opcjonalny jest type wskazujący czy mamy do czynienia z tłumaczem czy też z “normalnym” autorem.
  4. AuthorType – typ wyliczeniowy – dostępne wartości to Translator, Author

Aby korzystało się nam z kompilatora (xjc) wygodniej podepniemy go pod Eclipse jako narzędzie zewnętrzne. Tu przyda się kilka informacji na temat samego kompilatora:
xjc [-options …] file/URL/dir … [-b bindinfo] …
Gdzie najważniejsze opcje to:

  1. -d, katalog do którego trafi wygenerowany kod
  2. -b, dodatkowe mappingi umożliwiające zmianę zachowania kompilatora
  3. -p, paczka do której trafi wygenerowany kod
  4. -classpath, miejsca w których xjc ma szukać klas, które zostały użyte w mappingach

Przykładowo

xjc -d generated schema.xsd

Konfiruracja external tools
Spowoduje, że klasy wylądują w katalogu generated. Nazwa paczki zostanie zaczerpnięta z atrybutu targetNamespace dokumentu zawartego w schemacie. Obok znajduje się screen z konfiguracją – z ważniejszych informacji – Working directory ustawione na ${project_loc} pozwala nam się nawigować po katalogach względem aktualnie zaznaczonego w nawigatorze projektu. wartość w polu Arguments -d generated ${resource_loc} -verbose -extension -npa oznacza, że wygenerowane klasy wylądują w katalogu “generated” (należy stworzyć taki source folder). Zmienna ${resource_loc} pozwala nam użyć aktualnie zaznaczonego pliku w nawigatorze. Dzięki takiej konfiguracji niezależnie od projektu i pliku jesteśmy w stanie wygenerować potrzebne nam klasy poprzez dwa kliknięcia – pierwsze w nawigatorze, drugie na ikonie external tools.

Schemat jest dostępny do ściągnięcia, podobnie jak przykładowy plik z danymi. Proszę zwrócić uwagę na to jak jest zdefiniowany główny element dokumentu – poprzez zawarcie typu anonimowego. Jeśli zdefiniujemy oddzielnie element i typ to wówczas konieczne będzie ręczne dodanie adnotacji @XmlRootElement przy klasie Books. O tym dlaczego JAXB się tak zachowuje można wyczytać na jednym z Sun-owskich blogów.

package pl.dywicki.books.app;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;

import pl.dywicki.books.Author;
import pl.dywicki.books.AuthorType;
import pl.dywicki.books.Book;
import pl.dywicki.books.Books;
import pl.dywicki.books.ObjectFactory;

/**
 * Testowa klasa pokazująca użycie JAXB do zapisywania danych.
 *
 * @author Łukasz Dywicki
 */
public class App {

	public static void main(String[] args) throws Exception {

		// repozytorium książek
		Books books = new Books();

		// przykładowa książka
		Book book = new Book();
		book.setTitle("Test title");
		book.setIsbn("111-111-111");

		// testowy autor
		Author author = new Author();
		author.setFirstName("Martin");
		author.setLastName("Fowler");
		book.getAuthor().add(author);

		// testowy tłumacz
		author = new Author();
		author.setFirstName("Łukasz");
		author.setLastName("Dywicki");
		author.setType(AuthorType.TRANSLATOR);
		book.getAuthor().add(author);

		// dodanie książki do repozytorium
		books.getBook().add(book);

		JAXBContext context = JAXBContext.newInstance(ObjectFactory.class);
		Marshaller marshaller = context.createMarshaller();
		// chcemy ładnych wcięć w wyniku! :)
		marshaller.setProperty("jaxb.formatted.output", true);
		marshaller.marshal(books, System.out);
	}

}

Po uruchomieniu tego przykładu w konsoli powinien pokazać się taki oto wynik:

< ?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2_books xmlns:ns2="http://dywicki.pl/books">
    <book>
        <title>Test title</title>
        <isbn>111-111-111</isbn>
        <author>
            <firstname>Martin</firstname>
            <lastname>Fowler</lastname>
        </author>
        <author>
            <firstname>Łukasz</firstname>
            <lastname>Dywicki</lastname>
            <type>Translator</type>
        </author>
    </book>
</ns2_books>

(Zamiast ns2_books powinno być ns2:books, ale narzędzie kolorujące składnie sobie nie radzi).

Dla zainteresowanych załączam źródła projektu.

13 responses so far

13 Responses to “JAXB 2, Wprowadzenie”

  1. Fajne :) Choc mowiac prawde nawet PeHap sie czegos takiego juz dorobil :)

  2. @Paweł czy mógłbyś podać jakiś link bądź nazwę? Z chęcią przyjrzę się implementacji. :)

  3. faramir says:

    Jedna rzecz nie daje mi spokoju: “ns2:” – Nie da się, by był bez tego wygenerowany XML?

  4. Można pominąć przestrzeń nazw dostarczając rozszerzenie klasy NamespacePrefixMapper. Gdy Twoja implementacja zwróci pusty string JAXB powinno pominąć przestrzeń nazw i potraktować ją jako domyślną (xmlns=”mynamespace”).

  5. faramir says:

    Wydaje mi się to proste tylko w teorii. W praktyce nie chce mi zadziałać :(. Nie wiem jaką wersję mam JAXB, ale rzuca mi Exceptionem przy ustawianiu: com.sun.xml.bind.namespacePrefixMapper. W ogóle nie istnieje taka klasa. Już nawet “bind” nie istnieje.

    Myślałem, że Java jest prostsza, a tutaj by cokolwiek zrobić to trzeba jakieś hacki.. :/… Co i tak nie udaje się (tak jak mi teraz).

    Chyba najprostszym sposobem będzie zamiana wszystkich “ns2:” na “”, ale to nie jest zbyt eleganckie.

    Oprócz tego przeczytałem, że jeśli klasa zwróci “” albo null, to ustawi namespace na standardowy (czyli “ns2”).

  6. Fragment kodu, który działa:

    JAXBContext ctx = JAXBContext.newInstance(ObjectFactory.class);
    Marshaller marshaller = ctx.createMarshaller();
    marshaller.setProperty("jaxb.formatted.output", Boolean.TRUE);
    marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new AgaviNamespacePrefixMapper());
    marshaller.marshal(myObjectToSerialize, new FileOutputStream(file));
    

    Gdzie AgaviNamespacePrefixMapper to:

    package org.agavi.xsd.extensions;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import com.sun.xml.bind.marshaller.NamespacePrefixMapper;
    
    /**
     * @TODO ... :)
     *
     * @author Łukasz Dywicki
     */
    public class AgaviNamespacePrefixMapper extends NamespacePrefixMapper {
    
    	/**
    	 * Map with namespace to prefix mapping.
    	 */
    	private static Map[String , String] NAMESPACE_TO_PREFIX
    		= new HashMap[String, String]();
    
    	// mapping initialization
    	static {
    		NAMESPACE_TO_PREFIX.put("http://agavi.org/agavi/1.0/config/module", "module");
    		NAMESPACE_TO_PREFIX.put("http://agavi.org/agavi/1.0/config/common", "common");
    	}
    
    	@Override
    	public String getPreferredPrefix(String namespaceUri, String suggestion,
    		boolean requirePrefix) {
    
    		if (NAMESPACE_TO_PREFIX.containsKey(namespaceUri)) {
    			// to mała proteza - dla jednej z przestrzeni nie ma prefiksu:
    			// http://agavi.org/agavi/1.0/config/module
    			if (NAMESPACE_TO_PREFIX.get(namespaceUri).equals("module")) {
    				return "";
    			}
    			return NAMESPACE_TO_PREFIX.get(namespaceUri);
    		}
    		return suggestion;
    	}
    }
    

    NamespacePrefixMapper domyślnie nie jest dostępny w paczce jaxb-impl, musisz dorzucić jeszcze jaxb-xjc.ar (korzystając z JDK 6 ominiesz ten problem).

  7. faramir says:

    No to mam problem, bo korzystam z JDK 6 (tak ustawiłem we właściwościach projektu) a paczki odpowiedniej nie znajduje. Co prawda nie używam eclipse’a tylko netbeans, ale wydaje mi się, że to nie powinien być w tym problem. Chyba, że źle zainstalowałem jdk1.6… Ale wątpię, bo w Windowsie po prostu się klika i wszystko powinno być gotowe :).
    Swoją drogą nie mogę znaleźć nigdzie jaxb-xjc.jar.
    Czy to, że korzystam z Apache/Tomcat robi dużą różnicę?

  8. Komplet potrzebnych Ci paczek. Po ściągnięciu konieczne jest rozpakowanie przez java -jar JAXB2_20060426.jar.

    To, w jakim środowisku korzystasz nie robi różnicy, jeśli masz dołączone biblioteki. Uprawnij się czy np tomcat nie chodzi na domyślnej, systemowej VM (sprawdź czy masz zainstalowane JDK 5.0, ew gdzie wskazuje JAVA_HOME).

  9. faramir says:

    Z tym coś nie chce działać nadal. Ale to już chyba nie na temat na komentarz do artykułu. Gdy dodałem paczkę do bibliotek i uruchomiłem test taki exception rzucił:

    java.lang.NullPointerException
            at com.sun.xml.bind.v2.model.impl.PropertyInfoImpl.calcXmlName(PropertyInfoImpl.java:287)
            at com.sun.xml.bind.v2.model.impl.PropertyInfoImpl.calcXmlName(PropertyInfoImpl.java:262)
            at com.sun.xml.bind.v2.model.impl.ElementPropertyInfoImpl.getTypes(ElementPropertyInfoImpl.java:96)
            at com.sun.xml.bind.v2.model.impl.RuntimeElementPropertyInfoImpl.getTypes(RuntimeElementPropertyInfoImpl.java:50)
            at com.sun.xml.bind.v2.model.impl.ElementPropertyInfoImpl$1.size(ElementPropertyInfoImpl.java:42)
            at java.util.AbstractList$Itr.hasNext(AbstractList.java:339)
            at com.sun.xml.bind.v2.model.impl.ModelBuilder.getClassInfo(ModelBuilder.java:139)
            at com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder.getClassInfo(RuntimeModelBuilder.java:49)
            at com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder.getClassInfo(RuntimeModelBuilder.java:41)
            at com.sun.xml.bind.v2.model.impl.ModelBuilder.getTypeInfo(ModelBuilder.java:189)
            at com.sun.xml.bind.v2.model.impl.TypeRefImpl.calcRef(TypeRefImpl.java:56)
            at com.sun.xml.bind.v2.model.impl.TypeRefImpl.getTarget(TypeRefImpl.java:33)
            at com.sun.xml.bind.v2.model.impl.RuntimeTypeRefImpl.getTarget(RuntimeTypeRefImpl.java:22)
            at com.sun.xml.bind.v2.model.impl.RuntimeTypeRefImpl.getTarget(RuntimeTypeRefImpl.java:15)
            at com.sun.xml.bind.v2.model.impl.ElementPropertyInfoImpl$1.get(ElementPropertyInfoImpl.java:38)
            at com.sun.xml.bind.v2.model.impl.ElementPropertyInfoImpl$1.get(ElementPropertyInfoImpl.java:41)
            at java.util.AbstractList$Itr.next(AbstractList.java:345)
            at com.sun.xml.bind.v2.model.impl.ModelBuilder.getClassInfo(ModelBuilder.java:139)
            at com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder.getClassInfo(RuntimeModelBuilder.java:49)
            at com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder.getClassInfo(RuntimeModelBuilder.java:41)
            at com.sun.xml.bind.v2.model.impl.ModelBuilder.getTypeInfo(ModelBuilder.java:189)
            at com.sun.xml.bind.v2.model.impl.RegistryInfoImpl.(RegistryInfoImpl.java:63)
            at com.sun.xml.bind.v2.model.impl.ModelBuilder.addRegistry(ModelBuilder.java:232)
            at com.sun.xml.bind.v2.model.impl.ModelBuilder.getTypeInfo(ModelBuilder.java:201)
            at com.sun.xml.bind.v2.runtime.JAXBContextImpl$3.run(JAXBContextImpl.java:352)
            at com.sun.xml.bind.v2.runtime.JAXBContextImpl$3.run(JAXBContextImpl.java:350)
            at java.security.AccessController.doPrivileged(Native Method)
            at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(JAXBContextImpl.java:349)
            at com.sun.xml.bind.v2.runtime.JAXBContextImpl.(JAXBContextImpl.java:215)
            at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:76)
            at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:55)
            at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:124)
            at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
            at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
            at java.lang.reflect.Method.invoke(Method.java:597)
            at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:132)
            at javax.xml.bind.ContextFinder.find(ContextFinder.java:286)
            at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:372)
            at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:337)
            at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:244)
            at aurochs.aml.AmlParser.parseRequest(AmlParser.java:22)
            at aurochs.aml.AmlParser.parseRequest(AmlParser.java:31)
            at aurochs.cheetah.Engine.xmlRequest(Engine.java:66)
            at aurochs.pony.www.AMLTestBean.Test(AMLTestBean.java:37)
            at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
            at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
            at java.lang.reflect.Method.invoke(Method.java:597)
            at org.apache.myfaces.el.MethodBindingImpl.invoke(MethodBindingImpl.java:132)
            at org.apache.myfaces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:61)
            at org.apache.myfaces.trinidad.component.UIXCommand.broadcast(UIXCommand.java:154)
            at javax.faces.component.UIViewRoot._broadcastForPhase(UIViewRoot.java:97)
            at javax.faces.component.UIViewRoot.processApplication(UIViewRoot.java:171)
            at org.apache.myfaces.lifecycle.InvokeApplicationExecutor.execute(InvokeApplicationExecutor.java:32)
            at org.apache.myfaces.lifecycle.LifecycleImpl.executePhase(LifecycleImpl.java:95)
            at org.apache.myfaces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:70)
            at javax.faces.webapp.FacesServlet.service(FacesServlet.java:139)
            at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
            at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
            at org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl._invokeDoFilter(TrinidadFilterImpl.java:253)
            at org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl._doFilterImpl(TrinidadFilterImpl.java:210)
            at org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl.doFilter(TrinidadFilterImpl.java:164)
            at org.apache.myfaces.trinidad.webapp.TrinidadFilter.doFilter(TrinidadFilter.java:92)
            at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
            at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
            at org.netbeans.modules.web.monitor.server.MonitorFilter.doFilter(MonitorFilter.java:390)
            at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
            at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
            at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
            at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
            at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
            at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
            at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
            at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:263)
            at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
            at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:584)
            at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
            at java.lang.Thread.run(Thread.java:619)

    :(..

  10. Zgaduję – upewnij się, że Twój mapper nie zwraca nulla tylko pustą nazwę. Ew zweryfikuj poprawnosć adnotacji.

  11. faramir says:

    Sam już nie wiem. Wydaje się, że nie miał szans by zwracać nulla (chyba, że suggestion takie było). Przeczytałem, że by takie zabiegi robić, w NetBeans trzeba w JAXB Wizard zaznaczyć “Extenssion”.. Ale niestety nie jestem w stanie tego zrobić, bo ten czarodziej działa tylko z Glassfish, a nie z Tomcatem (http://www.nabble.com/JAXB-in-Web-Application-tp15009566p15049223.html)… Ale dzięki za pomoc!

  12. […] Jedną z bolączek JAXB jest problematyczna obsługa dat i czasów. Przypomnijmy sobie schemat użyty w jednej z wcześniejszych not. […]

  13. Alan says:

    Teraz zamiast generowac na wyjsciu plik xml, chcilabym z niego moc czytac. Zrobilem rzecz nastepujaca:

    JAXBContext context = JAXBContext.newInstance(ObjectFactory.class);
    Unmarshaller um = context.createUnmarshaller();
    Books books = (Books) um.unmarshal(new File( “src/books.xml”));
    List booklist = books.getBook();
    for (Iterator iter = (Iterator) booklist.iterator(); iter.hasNext();)
    {
    Book book = (Book) iter.next();
    System.out.println(“Zobacz czy w ogole wchodzi do petli”);
    }

    Chcialbym moc po prostu wyisac ksiazki, ich auturow. Okazuje sie ze do petli for w ogole nie wchodzi gdyz jak pozniej sprawdzilem:

    int lenght = booklist.size();
    System.out.println(lenght);

    Wartosc lenght daje w wyniku 0. Wie ktos moze jaka jest tego przyczyna?

Leave a Reply

You must be logged in to post a comment.