18 marca 2008

JAX-WS i Maven 2 w podejściu Contract First Development

Serwisy sieciowe, WebService’y, po przejściu fali marketingu i wyczerpaniu się na slajdach prezentacji odnajdują wreszcie swe nisze i zaczynają zyskiwać znaczenie praktyczne. Jedno jest pewne - o WebService’ach już nie tylko się mówi, coraz częściej przychodzi je zaimplementować. I pojawia się pytanie - jak to robić, żeby było – nie powiem, że łatwo jak na prezentacjach – ale chociaż znośnie. Próbowałem już wielu możliwości i śmiało mogę stwierdzić, że nie ma to jak JAX-WS (zwłaszcza 2.1) w parze z Maven 2. WebService’y z użyciem JAX-WS można implementować na dwa sposoby: zaczynając od klas Javy z odpowiednimi adnotacjami i na ich podstawie generując WSDL, albo na odwrót, zaczynając od WSDL’a i generując klasy. WSDL jest kontraktem, definicją interfejsu serwisu a podejście, w którym zaczynamy od wygenerowania klas na podstawie otrzymanego lub uprzednio napisanego WSDL’a nazywa się stąd, z angielskiego, Contract First Development. Tym właśnie stylem tworzenia serwisów sieciowych zajmę się dzisiaj.

Zakładamy więc, że mamy definicję serwisu w postaci pliku WSDL i naszym zadaniem jest implementacja takiego serwisu. Nasz WSDL (plik math.wsdl), definiujący operację dodawania wygląda następująco:

<definitions targetNamespace="http://jaxws.centric.pl/math"
xmlns:tns="http://jaxws.centric.pl/math"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:math="http://jaxws.centric.pl/math/domain">

<types>
<xsd:schema>
<xsd:import namespace="http://jaxws.centric.pl/math/domain"
schemaLocation="math.xsd"/>
</xsd:schema>
</types>

<message name="addRequestMsg">
<part name="addRequest" element="math:addRequest"/>
</message>

<message name="addResponseMsg">
<part name="addResponse" element="math:addResponse"/>
</message>

<portType name="mathService">
<operation name="add">
<input message="tns:addRequestMsg"/>
<output message="tns:addResponseMsg"/>
</operation>
</portType>

<binding name="soapBinding" type="tns:mathService">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"
style="document"/>
<operation name="add">
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
</binding>

<service name="mathServiceProxy">
<port name="mathService" binding="tns:soapBinding">
<soap:address location="http://localhost:8080/mathService"/>
</port>
</service>
</definitions>

Fakt, plik jest dosyć długi, ale tak to już być musi, a doskonałą przecież wymówką jest, że język WSDL nie był tworzony z myślą o tym by być czytelnym dla ludzi. Co więcej, kto zerknął na powyższego WSDL’a zauważył, że brakuje w nim definicji typów. Owszem, zostały one zdefiniowane w osobnym pliku XML Schema a w WSDL’u tylko ten plik wskazujemy. Oto więc brakująca definicja typów (plik math.xsd):

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://jaxws.centric.pl/math/domain"
xmlns:tns="http://jaxws.centric.pl/math/domain"
elementFormDefault="qualified">

<xsd:element name="addRequest" type="tns:addRequestType"/>

<xsd:complexType name="addRequestType">
<xsd:sequence>
<xsd:element name="a" type="xsd:double" />
<xsd:element name="b" type="xsd:double" />
</xsd:sequence>
</xsd:complexType>

<xsd:element name="addResponse" type="tns:addResponseType"/>

<xsd:complexType name="addResponseType">
<xsd:sequence>
<xsd:element name="c" type="xsd:double" />
</xsd:sequence>
</xsd:complexType>
</xsd:schema>

No dobrze, mamy już kontrakt, zatem przystępujemy do generowania klas. W tym celu utworzymy projekt Maven 2. Nie chcę zbyt dużo czasu poświęcić na podstawy Maven’a więc powiem krótko - tworzymy projekt główny, tj. typu pom o nazwie math i konfigurujemy go w następujący sposób (plik pom.xml):

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>pl.centric.jaxws</groupId>
<artifactId>math</artifactId>
<packaging>pom</packaging>
<version>1.1</version>
<name>math</name>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>RELEASE</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>RELEASE</version>
<configuration>
<encoding>US-ASCII</encoding>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>RELEASE</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
<modules>
<module>math-schema</module>
<module>math-ws</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<artifactId>math-schema</artifactId>
<groupId>pl.centric.jaxws</groupId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<pluginRepositories>
<pluginRepository>
<id>maven2-repository.dev.java.net</id>
<url>http://download.java.net/maven/2/</url>
</pluginRepository>
</pluginRepositories>
<repositories>
<repository>
<id>maven-repository.dev.java.net</id>
<url>http://download.java.net/maven/1/</url>
<layout>legacy</layout>
</repository>
<repository>
<id>maven2-repository.dev.java.net</id>
<url>http://download.java.net/maven/2/</url>
</repository>
</repositories>
</project>

Teraz tworzymy projekt potomny typu jar o nazwie math-schema. Jedynym zadaniem tego podprojektu będzie generowanie na podstawie WSDL’a i XML Schema’y odpowiednich klas, kompilowanie ich i pakowanie do postaci pliku .jar. Plik pom.xml tego projektu poniżej:

<project>
<parent>
<artifactId>math</artifactId>
<groupId>pl.centric.jaxws</groupId>
<version>1.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>pl.centric.jaxws</groupId>
<artifactId>math-schema</artifactId>
<name>math-schema</name>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxws-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>wsimport</goal>
</goals>
</execution>
</executions>
<configuration>
<xdebug>true</xdebug>
<verbose>true</verbose>
<target>2.0</target>
</configuration>
<dependencies>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-tools</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.1.3</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

Kluczowe jest tutaj wykorzystanie wtyczki (ang. plugin) jaxws-maven-plugin - https://jax-ws-commons.dev.java.net/jaxws-maven-plugin/, to ona generuje klasy i w jej dokumentacji powinniśmy szukać informacji, gdy chcemy zmodyfikować standardowe ustawienia. W katalogu src projektu math-schema tworzymy podkatalog wsdl i kopiujemy tam pliki WSDL i XML Schema, czyli w naszym przypadku math.wsdl i math.xsd. Jest to domyślna lokalizacja, w której wtyczka jaxws-maven-plugin poszukuje definicji serwisów, dla których ma wygenerować klasy.

Utwórzmy teraz podprojekt typu war o nazwie math-ws. Będzie to projekt, w którym zaimplementujemy nasz serwis. Oto plik pom.xml dla tego projektu:

<project>
<parent>
<artifactId>math</artifactId>
<groupId>pl.centric.jaxws</groupId>
<version>1.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>pl.centric.jaxws</groupId>
<artifactId>math-ws</artifactId>
<name>math-ws</name>
<packaging>war</packaging>
<dependencies>
<dependency>
<artifactId>math-schema</artifactId>
<groupId>pl.centric.jaxws</groupId>
</dependency>
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-rt</artifactId>
<version>2.1.3</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

Ponieważ mamy do czynienia bądź, co bądź z aplikacją internetową (ang. web application) nie obędzie się bez pliku web.xml, oto on:

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" />

I wreszcie implementacja serwisu, tj. interfejsu wygenerowanego przez narzędzie jaxws-maven-plugin. Oto ona:

package pl.centric.jaxws.math.service;

import javax.jws.WebService;

import pl.centric.jaxws.math.MathService;
import pl.centric.jaxws.math.domain.AddRequestType;
import pl.centric.jaxws.math.domain.AddResponseType;

@WebService(endpointInterface="pl.centric.jaxws.math.MathService")
public class MathServiceBean implements MathService {

public AddResponseType add(AddRequestType addRequestType) {
AddResponseType addResponseType = new AddResponseType();

addResponseType.setC(addRequestType.getA() + addRequestType.getB());

return addResponseType;
}
}

Budujemy projekt i przystępujemy do testów. Zaczynamy od serwera GlassFish V2. Do testów używam narzędzia soapUI - http://www.soapui.org/. Prosty test pokazuje, że działa.




Ale na tym nie koniec, sprawdzamy jeszcze czy zadziała na innych serwerach. W końcu się przekonałem i wziąłem do ręki Apache Geronimo - http://geronimo.apache.org/ - w wersji 2.1. Działa bez zarzutu a serwer robi dobre wrażenie, zwłaszcza szybkość działania konsoli. Co prawda dostajemy ostrzeżenie, że nie znaleziono pliku geronimo-web.xml i może to skutkować błędami, ale nic w tym dziwnego jako że rzeczywiście takiego pliku nie ma. Błędów też nie ma. Na koniec sprawdzam jeszcze na serwerze BEA WebLogic Server 10.2. Działa i tu, co nie koniecznie musiało mieć miejsce. O ile moje doświadczenia z GlassFish’em są takie, że łyknie wszystko, co tylko można to z WLS’em jest już zupełnie na odwrót – wszystko musi być idealnie. Jak mamy WLS’a to mamy też konsolę do testowania WebService’ów, zamiast soapUI używam więc tejże konsoli. Wygląda to tak jak poniżej.


4 komentarze:

Łukasz Lenart pisze...

Możesz dołączyć kod do wpisu?

Mariusz Lipiński pisze...

Zrobione,

źródła dostępne są tutaj:


http://www.centric.pl/examples/math/math.zip


a gotowy war tutaj:

http://www.centric.pl/examples/math/math-ws-1.1.war

Łukasz Lenart pisze...

Dzięki, po testuje...

Anonimowy pisze...

spoko post a mam pytanie bo z tego co przedstawiłeś jest to przykład typu top-down zgadza się ? a co jeśli chciałbym przy użyciu maven 2 i jax-ws utworzyć service metodą bottom-up ?