Podstawy AOP: konfiguracja, zastosowanie, przykład (Spring) Acegi – Uwierzytelnianie i Autoryzacja w Springu
Aug 25

Axis2

Wprowadzenie

Poniższy artykuł zredagowany został z myślą o przedstawieniu podstawowych zagadnień związanych z tworzeniem usług typu WebService. Przy czym przez tworzenie WebService‘u należy tutaj rozumieć zarówno przygotowanie serwerowej, jak i klienckiej warstwy rozwiązania. W osiągnięciu tego celu pomoże nam Axis2 – darmowe silnik WebService‘ów. Wybór tego akurat produktu o tyle nie jest przypadkowy, że zaledwie kilka dni temu światło dzienne ujrzała wersja 1.3 tego popularnego produktu na “miłej” i lubianej licencji Apache‘a.

W celu jak najlepszego zaprezentowania omawianego zagadnienia posłużymy się, przygotowaną specjalnie na tą potrzebę, prostą aplikacją. W kolejnych krokach postaramy się przejść przez wszystkie konieczne etapy do stworzenia kompletnej, w pełni działającej aplikacji. Należy jednak tutaj zauważyć, że technologia WebService‘ów często wykorzystywana jest w intencji udostępnienia pewnej istniejącej już funkcjonalności instytucjom zewnętrznym, a co za tym idzie odpowiedzialność za implementację strony serwerowej spada na zupełnie inne osoby niż odpowiedzialność za implementację strony klienckiej. Co więcej, środowiska wykonywalne każdej z nich mogą drastycznie się różnić. Na tym jednak polega moc rozwiązań opartych na WebService‘ach. Postaramy się więc prezentować powyższe 2 etapy jako możliwie od siebie niezależne, z tym jedynie małym zastrzeżeniem, że w momencie przystąpienia do implementacji klienta, produkcja serwera będzie już ukończona.

W artykule staraliśmy się przedstawić ogólny zarys WebService‘ow – ma on charakter poglądowy i zawiera jedynie szczątkowe informacje na temat samego Axis2 i jego możliwości w zakresie tworzenia ogólnych serwisów. Nie zawiera informacji na temat rozbudowanych możliwości w zakresie rozbudowy oraz konfiguracji szczególnych dla Axis2. W dokumencie brak również podstaw teoretycznych związanych z samymi WebService‘ami. Mamy jednak nadzieję, że osoby zainteresowane tematyką sięgną po ogólniedostępne materiały dodatkowe. Część z nich staraliśmy się wymienić pod koniec artykułu.

Serdecznie zapraszam więc do zapoznania się z pozostałą częścią artykułu mając jednocześnie nadzieję, że będzie ona stanowiła dla Państwa interesującą lekturę. :)

Kontekst rozwiązania

Problem, z jakim będziemy się borykali, osadzimy (w jakże popularnych dla wielu z nas ;)) realiach pracowniczych. Naszym celem będzie udostępnienie poprzez WebService‘y funkcjonalności dostępnej lokalnie w postaci klas POJO (plain-old java objects).

Funkcjonalność, o której mowa powyżej to usługa odpowiedzialna za uzupełnienie informacji na temat wybranych pracowników włącznie z danymi na temat ich bezpośrednich jak i pośrednich podwładnych. Ze względu na niedostępność dla nas prawdziwej usługi tego typu oraz ustawę o ochronie danych osobowych ;) przygotowałem prostą implementację, która będzie jedynie w bardzo “naiwny” sposób imitowała prawdziwe zachowanie tejże usługi.

Na całość będzie składać się zaledwie kilka prostych klas, których fragmenty dla ustalenia uwagi pozwoliłem sobie zaprezentować poniżej.

package pl.jcommerce.example.pracownik;

import pl.jcommerce.example.Identyfikator;

public class Pracownik {
  private Identyfikator identyfikator;
  private String imie;
  private String nazwisko;
  private int rokUrodzenia;
  private Pracownik[] podwladni;

  //settery i gettery

}

Klasa Pracownik będzie jedyną klasą domenową naszej usługi. Można zauważyć, że jest to bardzo prosta implementacja, a mimo to zawierająca już kilka interesujących elementów na które będzie warto zwrócić uwagę w kontekście tworzenia i wykorzystywania pliku definicji WebService‘u (WSDL). Między innymi możliwe stanie się zaobserwowanie sposobu w jaki Axis poradzi sobie z generacją pliku WSDL w aspekcie zachowania związanego z prezentowaniem referencji do innego złożonego elementu, a także do tablicy obiektów.

public interface PracownikUzupelnianieInformacji {
  public Pracownik[] uzupelnijInformacjeOPracownikach(Pracownik[] pracownicy,int glebokoscUzupelniania) throws ObjectNotFoundException, IdentyfikatorNotSetException;
}

Powyższy interfejs będzie stanowił interfejs naszej usługi, którą będziemy chcieli udostępnić na zewnątrz. Aby udało się przetestować działanie implementowanego rozwiązania stworzyłem również prostą klasę stub‘a, która w odpowiedzi na zgłoszone żądania w sposób mniej lub bardziej losowy uzupełnia dane na temat pracowników przekazanych w zapytaniu, a także na temat ich podwładnych. Ze względów wydajnościowych maksymalna ilość podwładnych jak i głębokość uzupełniania zostały ograniczone. Dodatkowo różne wywołania metody przy dostarczeniu tych samych parametrów operacji mogą zwrócić różne rezultaty. Z naszego jednak punktu widzenia wszystkie wymienione tutaj ułomności nie będą miały żadnego znaczenia.

Prerekwizyty

By rozpocząć musimy mieć zainstalowane i poprawnie skonfigurowane zintegrowane środowisko developerskie. Przykłady zawarte w artykule zostaną przygotowane z wykorzystaniem Eclipse‘a.

W dalszej części będziemy także korzystać z kontenera serwletów. W przykładzie wykorzystamy Apache Tomcat’a v6.0.x lecz nie powinno także być problemów z uruchomieniem rozwiązania na innych implementacjach. Warto także nadmienić, że silnik Axis2 udostępniany jest także w wersji nie wymagającej stosowania dodatkowych aplikacji, czy też bibliotek (standalone application).

Przed przystąpieniem do kolejnych kroków warto pobrać kompletne spakowane źródła przygotowywanego rozwiązania (2 projekty utworzone z wykorzystaniem Eclipse‘a). Po rozpakowaniu archiwum należy pamiętać, aby zastąpić puste pliki .jar kopiami znajdującymi w dystrybucji Axis2.

Instalacja Axis2

W naszym przypadku najbardziej odpowiednim wyjściem będzie pobranie standardowej dystrybucji Axis‘a. Zdecydujemy się na pobranie tej wersji ze względu na dostępność w niej dodatkowych narzędzi linii poleceń, które wykorzystamy do przygotowania klienta.

Jak już wcześniej wspomniałem Axis2 dysponuje aplikacją uruchamialną w kontenerze servlet’ów. W celu przeprowadzenia jej instalacji należy plik Axis2.war znajdujący się w pobranej przez nas dystrybucji wdrożyć w kontenerze serwletów. W przypadku Apache Tomcat‘a wystarczy plik ten skopiować do katalogu webapps, a następnie uruchomić serwer. Już w tym momencie powinniśmy uzyskać w pełni działający engine Axis‘a. Weryfikacji tego faktu można dokonać w prosty sposób poprzez wpisanie w przeglądarce internetowej odpowiedniego adresu. Przy standardowej konfiguracji Apache Tomacat‘a powinien to być następujący URL: http://localhost:8080/axis2.

Naszym oczom ukaże się strona główna Axis‘a, gdzie powinniśmy mieć możliwość zweryfikowania poprawności instalacji modułu poprzez wybranie opcji Validate. Interfejs Axis‘a udostępniany poprzez przeglądarkę daje nam także możliwość weryfikacji uruchomionych serwisów oraz administracji samym silnikiem.

Jeżeli wszystko, jak do tej pory poszło, zgodnie z planem możemy zabrać się za przygotowanie serwisu, który następnie zostanie uruchomiony w środowisku Axis‘a. W przypadku wystąpienia problemów na tym lub późniejszym etapie odsyłam do strony głównej Axis2, gdzie można znaleźć bardzo wiele wartościowych informacji.

Przygotowanie warstwy serwera

Aby rozpocząć pracę nad serwisem będziemy potrzebowali kodu źródłowego usługi, którą powyżej ogólnie już zaprezentowaliśmy.

Jedną z zalet Axis2 jest jego duża elastyczność oraz rozszerzalność o nowe moduły funkcjonalne. Także w kwestii przygotowania serwera WebService‘u dysponuje on wieloma znacznie różniącymi się między sobą rozwiązaniami. My zdecydowaliśmy się na jedną z nich, a mianowicie na wykorzystanie obiektów POJO – to podejście (jak każde) ma swoje liczne zalety i wady. Dla nas czynnikiem decydującym był aspekt czasowy przygotowania WebService‘u. Zainteresowanym informacjami na temat szczegółowych różnic w samej implementacji jak i funkcjonowaniu rozwiązań przygotowanych przy stosowaniu dostępnych podejść odsyłamy do oficjalnej dokumentacji.

Obrane przez nas podejście będzie wymagało przygotowania pliku o rozszerzeniu aar. W rzeczywistości plik ten będzie zwykłym archiwum ZIP posiadającym podobną strukturę plików i katalogów jak dobrze znane pliki JAR. Z tego też względu możliwe stanie się wykorzystanie istniejącego już zadania Apache Ant‘a. W pliku AAR powinny się znajdować następujące elementy:

  • META-INF/services.xml – definicja serwisu wymagana przez Axis‘a
  • lib – katalog z bibliotekami (*.jar) wykorzystywanymi przez serwis
  • skompilowane pliki źródeł

W naszym przypadku katalog lib nie wystąpi ze względu na fakt, że przygotowana usługa nie korzysta z innej funkcjonalności niż tej dostarczanej przez klasy JSE. Jednakże w większości przypadków, z wyłączeniem tak trywialnych przykładów jak ten, korzystanie z bibliotek zewnętrznych będzie niemal koniecznością.

Skupmy naszą uwagę na nowym elemencie, jaki pojawia się w archiwum AAR, a mianowicie na pliku services.xml.

<service name="PracownikUzupelnianieInformacji" scope="application">
  <description>Serwis uzupelnianiajacy informacje o pracowniku</description>
  <messageReceivers>
    <messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out" class="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />
  </messageReceivers>
  <parameter name="ServiceClass">pl.jcommerce.example.pracownik.service.PracownikUzupelnianieInformacjiImpl</parameter>
</service>

Jak widać definicja serwisu w naszym przypadku zawiera jedynie kilka elementów, które wydają się być łatwe do zrozumienia. Szczególną uwagę należy zwrócić na tag <parameter> o atrybucie name równym ServiceClass. Definiuje on klasę, której instancja posłuży jako końcówka WebService‘u po stronie serwera. W wielu przypadkach implementacji serwisów w oparciu o podejście POJO będzie to jeden z niewielu elementów, jakich zmiany będzie się od nas wymagało. Tag messageReceivers definiuje klasy odpowiedzialne za obsługę przesyłania informacji pomiędzy klientem, serwerem usługi WebService‘u. Zaprezentowany tutaj przykład jest bardzo prosty, lecz w ogólności plik definicji serwisu udostępnia rozwinięte możliwości modyfikacji zachowania serwisu.

Bardzo interesującą kwestią jest brak jakiegokolwiek pliku WSDL, ale jego brak jak się później okaże jest jedynie pozorny.

W wypadku konieczności wstępnej konfiguracji obiektu usługi można wykorzystać framework Spring z którym Axis2 potrafi bardzo dobrze się zintegrować. Artykuł ten nie pokrywa jednak w zupełności tej tematyki.

W tym momencie posiadając już kompletną listę plików wchodzących w skład archiwum AAR możemy przystąpić do stworzenia samego archiwum. Można tego dokonać oczywiście ręcznie – ja natomiast posłużyłem się do tego bardzo prostym plikiem build.xml programu Apache Ant:

<project name="PracownikUzupelnianieInformacji" default="dist" basedir=".">
  <property name="location.dir.src" location="src" />
  <property name="location.dir.bin" location="bin" />
  <property name="location.dir.build" location="build" />
  <property name="location.dir.dist" location="dist" />
  <property name="location.dir.resources" location="resources" />
  <property name="location.dir.resources.aar" location="${location.dir.resources}/aar" />
  <property name="file.aar" value="PracownikUzupelnianieInformacji.aar"/>
  <target name="clean">
    <mkdir dir="${location.dir.bin}" />
    <mkdir dir="${location.dir.build}" />
    <delete dir="${location.dir.build}" excludes="${location.dir.build}"/>
    <mkdir dir="${location.dir.dist}" />
    <delete dir="${location.dir.dist}" excludes="${location.dir.dist}"/>
  </target>
  <target name="compile" depends="clean">
    <javac srcdir="${location.dir.src}" destdir="${location.dir.bin}" />
  </target>
  <target name="build" depends="compile">
    <copy todir="${location.dir.build}">
      <fileset dir="${location.dir.resources.aar}" />
      <fileset dir="${location.dir.bin}" />
    </copy>
  </target>
  <target name="dist" depends="build">
    <jar destfile="${location.dir.dist}/${file.aar}" basedir="${location.dir.build}"/>
  </target>
</project>

Wywołanie domyślnego zadania zdefiniowanego w tym pliku budowania powinno skutkować utworzeniem w katalogu dist gotowego pliku z rozszerzeniem aar gotowego do wdrożenia w środowisku Axis‘a.

Wdrażanie serwisu w środowisku uruchomieniowym Axis2

Do wdrożenia przygotowanego w poprzednim podrozdziale serwisu wykorzystamy jedną z udostępnianych przez Axis‘a w tym celu metod. Należy się upewnić, że uprzednio zainstalowany silnik WebService‘ów wciąż działa. Następnie plik PracownikUzupelnianieInformacji.aar umieszczamy w katalogu Apache Tomcat‘a webapps/axis2/WEB-INF/services. Po chwili na konsoli Tomcat‘a powinna pojawić sie informacja identyczna do tej:

[INFO] Deploying Web service: PracownikUzupelnianieInformacji.aar

Oznacza to, że nastąpiło wdrożenie naszego WebService‘u. Hurrra!

Technika jaka została wykorzystana przy wdrożeniu to Hot Deployment. Jest to technika, która bez restartowania aplikacji (w tym wypadku Axis2.war) pozwala na dodawanie/usuwanie serwisów.

Ponownie weryfikacji tego faktu dokonać można przy pomocy interfejsu udostępnianego poprzez Axis‘a przy pomocy przeglądarki. W sekcji Services powinien pojawić się nasz serwis wraz z metodami, jakie się na niego składają. Klikając na nazwie serwisu zostaniemy przekierowani na adres http://localhost:8080/axis2/services/PracownikUzupelnianieInformacji?wsdl, gdzie “odnajdzie” się wcześniej zagubiony WSDL. Ze względu na pokaźną objętość pliku nie zostanie on tutaj zamieszczony, lecz warto we własnym zakresie zapoznać się z nim w celu weryfikacji wygenerowanych odwzorowań pomiędzy typami Java‘owymi, a typami XML Schema.

Trudno przeoczyć, że ma miejsce tutaj pewnego rodzaju “magia”. Axis2 posiadając jedynie skompilowane klasy dokonuje w sposób całkowicie automatyczny generacji pliku definicji WebService‘u.

Przygotowanie warstwy klienta

Posiadając już gotowy, działający WebService możemy przystąpić do realizacji jego klienta. Warto być może raz jeszcze podkreślić fakt braku związku pomiędzy rodzajem implementacji serwera, a klienta. Równie dobrze zaprezentowane poniżej procedury mogłyby posłużyć do wygenerowania klienta dla innego serwisu. Prawdopodobnie jedyną różnicą byłoby zastosowanie alternatywnego adresu URL do WSDL‘a.

W procesie przygotowania klienta wykorzystamy technikę pasywnej generacji kodu. Oznacza to, że dokonamy jednokrotnej generacji kodu dla WebService‘u, który na późniejszym etapie będziemy modyfikować ręcznie, jeżeli oczywiście zajdzie taka potrzeba. Do tego celu użyjemy narzędzi linii poleceń dostarczonych razem ze standardową dystrybucją Axis2. Przed przystąpieniem do generacji należy jednak pamiętać o ustawieniu zmiennej środowiskowej AXIS2_HOME w taki sposób, by wskazywała ona lokalizację w której znajduje się rozpakowana dystrybucja Axis2. Warto także pomyśleć o zmodyfikowaniu zmiennej środowiskowej PATH, aby także wskazywała do katalogu w którym znajduje się narzędzie WSDL2Java. Gdy już tego dokonamy sama generacja kodu nie powinna przysporzyć nam już jakichkolwiek problemów.

W katalogu w którym planujemy umieszczenie wygenerowanego kodu źródłowego wykonujemy następujące polecenie

WSDL2Java
  -uri http://localhost:8080/axis2/services/PracownikUzupelnianieInformacji?wsdl
  -d adb
  -s
  -S .

Jedynym parametrem wywołania o którym wartało wspomnieć jest przełącznik -d. Jest on odpowiedzialny za wybór rodzaju wiązania danych (databinding), jakie będzie wykorzystywane w celu wygenerowania kodu. I tak Axis2 dysponuje kilkoma alternatywnymi rozwiązaniami, które wspiera: XMLBeans, JiBX, JaxMe, referencyjną implementację JAXB oraz własną Axis Data Binding. My skorzystaliśmy z ostatniej z wymienionych, która jednocześnie jest wyborem domyślnym.

Jeżeli wszystko pójdzie zgodnie z oczekiwaniami w katalogu bieżącym powinniśmy odnaleźć wygenerowany kod. Trochę przerażającą kwestią może być jego objętość – wygenerowane klasy mają 230 kilobajtów. Przy 6 kilobajtach kodu serwera może być to wartość lekko “zniechęcająca”. Pocieszeniem w tym wypadku może być świadomość, że nie musieliśmy pisać tego kodu ręcznie. To niestety nie koniec niespodzianek – kod klienta do kompilacji oraz uruchomienia wymaga dosyć sporej ilości bibliotek (prawie 2,5 megabajta). Przy zastosowaniu bardziej skomplikowanych mechanizmów wiązania danych objętość zarówno kodu jaki i dodatkowych bibliotek może jeszcze wzrosnąć. Nie jest to bez znaczenia, jeżeli uruchamiany kod ma działać w warunkach ściśle ograniczonych zasobów – w tym wypadku z dużym prawdopodobieństwem możemy stwierdzić, że klient generowany automatycznie przez Axis‘a nie będzie adekwatnym rozwiązaniem.

Mając już wygenerowanego klienta napiszemy krótki test, który będzie demonstrował sposób wywoływania WebService‘u, jak i przy okazji zweryfikuje, że przygotowane przez nas rozwiązanie w rzeczywistości działa – zarówno serwer, jak i klient. Poniżej listing kodu źródłowego testu przygotowanego pod JUnit 4:

public class PracownikUzupelnianieInformacjiStubTest {

  @Test
  public void testUzupelnijInformacjeOPracownikach() throws RemoteException, ObjectNotFoundExceptionException0, IdentyfikatorNotSetExceptionException1 {
    final PracownikUzupelnianieInformacjiStub pracownikUzupelnianieInformacjiStub = new PracownikUzupelnianieInformacjiStub();
    final UzupelnijInformacjeOPracownikach uzupelnijInformacjeOPracownikach = new UzupelnijInformacjeOPracownikach();        Pracownik[] pracownicy = new Pracownik[2];
    for (int i = 0; i < 2; i++) {
      pracownicy[i] = new Pracownik();
      final Identyfikator identyfikator = new Identyfikator();
      identyfikator.setIdentyfikator(i + 1);
      pracownicy[i].setIdentyfikator(identyfikator);
    }

    uzupelnijInformacjeOPracownikach.setPracownicy(pracownicy);
    uzupelnijInformacjeOPracownikach.setGlebokoscUzupelniania(3);

    UzupelnijInformacjeOPracownikachResponse uzupelnijInformacjeOPracownikachResponse = pracownikUzupelnianieInformacjiStub.uzupelnijInformacjeOPracownikach(uzupelnijInformacjeOPracownikach);

    for (final Pracownik pracownik : uzupelnijInformacjeOPracownikachResponse.get_return()) {
      weryfikujPracownika(pracownik);
    }
  }

  private void weryfikujPracownika(Pracownik pracownik) {
    Assert.assertTrue(pracownik.getIdentyfikator().getIdentyfikator() >= 0);
    Assert.assertEquals("Pracownik[X].imie", pracownik.getImie().replaceAll("d+", "X"));
    Assert.assertEquals("Pracownik[X].nazwisko", pracownik.getNazwisko().replaceAll("d+", "X"));
    Assert.assertTrue(pracownik.getRokUrodzenia() >= 1935);

    final Pracownik[] podwladni = pracownik.getPodwladni();

    if (podwladni == null) {
      return;
    }

    for (final Pracownik podwladny : podwladni) {
      if (podwladny != null) {
        weryfikujPracownika(podwladny);
      }
    }
  }
}

Jak łatwo można zauważyć całość logiki odpowiedzialnej za komunikowanie się z WebService‘em została przed nami ukryta. Naszą odpowiedzialność pozostaje jedynie zapewnienie odpowiedniej obsługi wyjątku typu RemoteException.

Konkluzja

W mojej opinii Axis2 nie jest jeszcze rozwiązaniem idealnym (posiada wciąż wiele swoich problemów), lecz w porównaniu do konkurencji – w tym także do produktów komercyjnych – stanowi godną rozważenie alternatywę. Produkt ten pozostawia po sobie pozytywne wrażenie – szczególnie długa lista rozszerzeń może determinować jego zastosowanie.

Dodatkowe informacje dla szczególnie zainteresowanych ;)

http://www.w3.org/2002/ws/

http://ws.apache.org/axis2/

http://wiki.apache.org/ws/FrontPage/Axis2/
Podziel się z innymi:
  • Wykop
  • Digg
  • del.icio.us
  • StumbleUpon
  • Slashdot

3 Responses to “Tworzenie WebService’ów z wykorzystaniem Axis2”

  1. Michał Mally Says:

    Dodałem brakujący link do spakowanych źródeł oraz poprawiłem pozostałe niedziałające odnośniki.

  2. Adamus Says:

    Prerekwizyty? Ludzie, proszę, korzystajcie ze słownika angielsko-polskiego, bo coraz więcej takich krzaków powstaje, jak choćby “destynacje” na stronach do rezerwacji biletów lotniczych. Jak tak dalej pójdzie, to zamiast języka polskiego będziemy mieli “angielskawy”.

  3. Krzychu Says:

    czy macie jakies doswiadczenie z tworzeniem webservicow konsumowanych w postaci json’a ??

Leave a Reply

Security Code: