Introduzione
I Web Services rappresentano una tecnologia consolidata
e si trovano in circolazione diverse implementazioni
per eseguire deploy delle proprie applicazioni come
web service utilizzando i tre mattoni fondamentali,
ovvero SOAP, WSDL e UDDI. Questo articolo è il
seguito ideale di [1] e descriverà l'ultima versione
dell'implementazione di SOAP per Java fornita dall'Apache
Group denominata Axis, Apache eXtendable Interaction
System. L'articolo citato mostrava invece Apache SOAP
2.x, la generazione precedente del toolkit.
L'articolo
verterà prevalentemente sugli aspetti pratici
della realizzazione di un web service con Axis, in particolare
si eseguirà il deploy di un EJB con SOAP. Quindi
si presenterà un client Java, mostrando come
ingegnerizzare il client, e si vedranno varie tecnologie
a contorno di Axis sviluppate proprio per semplificare
le fasi di invocazione di un web service.
Axis
Axis [2] non è un semplice engine SOAP ma un
framework per realizzare sistemi di integrazione basati
su SOAP. Il progetto non è una revisione della
precedente versione del toolkit, Apache SOAP 2.x, ma
una completa riscrittura che nasce dalla necessità
di disporre di una migliore architettura basata su criteri
di
- flessibilità:
è possibile estendere l'engine per eseguire
elaborazioni custom degli header dei messaggi;
- componibilità:
i gestori di richieste, chiamati Handler in terminologia
Axis, possono essere riutilizzati;
- indipendenza
dal meccanismo di trasporto dei messaggi: in questo
modo è possibile utilizzare vari protocolli
oltre ad HTTP come SMTP, FTP e messaging.
Rispetto
alla versione precedente di SOAP in Axis si è
cercato inoltre di migliorare le performance utilizzando
SAX per l'elaborazione dei messaggi.
Nel seguito si farà riferimeno alla beta 2 di
Axis che implementa le specifiche 1.1 di SOAP e offre
un supporto parziale a SOAP 1.2.
Installazione
L'installazione di Axis è abbastanza semplice:
il prerequisito fondamentale è un servlet container
e negli esempi presentati si farà riferimento
a Tomcat.
Innanzi tutto si deve copiare il folder webapps/axis
dalla distribuzione nel folder webapps di Tomcat. I
web service saranno quindi accessibili all'URL
http://hostname:port/axis/...
E'
possibile cambiare il nome di questo folder ma cambierà
corrispondentemente anche l'URL.
All'interno di webapps/axis si trova il folder WEB-INF
in cui copiare le classi che si vogliono esporre come
web service: i .class verranno copiati nel folder classes
mentre i .jar nel folder lib. Prima di usare Axis è
comunque necessario copiare un parser XML, Xerces o
Crimson, in WEB-INF/lib.
Ora Tomcat è configurato per esporre web service
con Axis.
Un
semplice web service
In questa sezione si affronterà il problema del
deploy di un web service con Axis mostrando tre casi
di difficoltà crescente
- deploy
di una classe Java con copia di file;
- deploy
della stessa classe mediante deployment descriptor;
- deploy
di un EJB;
Per
i primi due esempi si userà la seguente classe
Java che implementa un semplice sommatore
public
class Calculator {
public int add(int x, int y) {
return x + y;
}
}
Per
eseguire un deploy istantaneo della classe è
sufficiente copiare il sorgente nel folder della web
application di Axis con estensione jws, Java Web Service.
A questo punto il servizio è immediatamente disponibile
all'URL http://localhost:8080/axis/Calculator.jws
E' il runtime di Axis che compila il file JWS in classe
Java e inoltra quindi ad essa le chiamate SOAP.
Questa
modalità è semplice e immediata ma non
sempre utilizzabile; spesso il servizio di cui fare
deploy non sarà implementato con una singola
classe Java ed è inoltre necessario un modo per
configurare i parametri di deploy come ad esempio il
mapping dei tipi. Per questa ragione in Axis sono previsti
i deployment descriptor, documenti XML che contengono
le informazioni di deploy; segue come esempio il deployment
descriptor per il servizio calculator
<deployment
xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
<service
name="calculator" provider="java:RPC">
<parameter name="className" value="com.mokabyte.javaxml.calculator.Calculator"/>
<parameter name="allowedMethods" value="add"/>
</service>
</deployment>
L'elemento
service definisce appunto il web service a cui è
stato dato il nome calculator. L'attributo provider
permette di specificare il modo in cui il servizio viene
esposto; in questo caso si usa il provider per una classe
Java quindi si usa java:RPC come valore dell'attributo.
L'elemento parameter permette di specificare i parametri
di configurazione del servizio, in questo caso il nome
della classe e i metodi esposti.
Il deployment descriptor è contenuto in un file
che ha in genere estensione wsdd. Quindi il deploy vero
e proprio si ottiene mediante un tool a linea di comando:
dopo aver incluso nel classpath axis.jar, commons-logging.jar,
log4j-core.jar, jaxrpc.jar, tt-bytecode.jar e il parser
XML si esegue
java
org.apache.axis.client.AdminClient -lhttp://localhost:8080/axis/services/AdminService
deploy.wsdd
Questo
processo può essere facilmente automatizzato
usando ANT [3]. Fare deploy di un servizio su Axis consiste
'semplicemente' nel richiamare una classe Java con certi
argomenti specificando le opportune librerie nel classpath.
Ecco un target ANT che esegue proprio questo compito
<target
name="deployWS">
<java classname="org.apache.axis.client.AdminClient"
fork="yes">
<arg line="-lhttp://localhost:8080/axis/services/AdminService
deploy.wsdd"/>
<classpath>
<pathelement location="${lib.path}/axis.jar"/>
<pathelement location="${lib.path}/commons-logging.jar"/>
<pathelement location="${lib.path}/log4j-core.jar"/>
<pathelement location="${lib.path}/jaxrpc.jar"/>
<pathelement location="${lib.path}/tt-bytecode.jar"/>
<pathelement location="${lib.path}/xerces.jar"/>
</classpath>
</java>
</target>
In
questo modo il deploy diventa immediato e indipendente
dall'ambiente di sviluppo utilizzato.
Dopo
aver realizzato un web service ecco un client che lo
utilizza
package
com.mokabyte.javaxml.client.calculator;
import
org.apache.axis.client.Call;
import org.apache.axis.client.Service;
import javax.xml.rpc.namespace.QName;
import java.net.URL;
public
class CalculatorClient {
public static void main (String args[])
{
try {
String url;
url="http://localhost:8080/axis/services/Calculator";
Service service
= new Service();
Call call = (Call)service.createCall();
call.setTargetEndpointAddress(new
URL(url));
call.setOperationName(new
QName("Calculator", "add"));
Object[] params
= new Object[2];
params[0] = new
Integer(2);
params[1] = new
Integer(3);
Object result =
call.invoke(params);
int sum = ((Integer)result).intValue();
System.out.println("result
is " + sum);
}
catch(Exception e) {
e.printStackTrace();
}
}
}
Il codice presentato è abbastanza semplice
-
viene creato un oggetto Service come punto di partenza
per connettersi ad un web service SOAP; è possibile
specificare un documento WSDL per ottenere informazioni
sul servizio;
- si
crea quindi un oggetto Call che incapsula la chiamata
vera e propria e si impostano le proprietà
necessarie, in questo caso URL del servizio e nome
del metodo da richiamare sul servizio. Si osservi
che queste informazioni potrebbero essere ricavate
dal WSDL;
- a
questo punto si impostano i parametri applicativi
per l'invocazione del servizio inserendoli in un array
di oggetti: in questo caso si tratta di una copia
di interi;
- infine
si invoca il servizio e si ottiene il risultato dell'operazione.
Il
codice necessario per un client Axis è abbastanza
semplice ma abbastanza verboso: è ad esempio
abbastanza scomodo dover specificare i parametri del
servizio come array di oggetti ed inoltre è necessario
usare esplicitamente la API Axis. Nel seguito verrà
mostrata una interessante soluzione fornita da Axis
a questo problema.
EJB
e web service
Un web service espone in genere logiche applicative
implementate come EJB; in questa sezione si vedrà
appunto come accedere via SOAP ad un EJB.
Si supponga di voler realizzare un servizio che esponga
i dati di una lista di attività. Il servizio
verrà implementato come un session EJB stateless
che espone un singolo metodo getList(): questo ritorna
la lista delle attività come documento XML. Ecco
la remote interface:
package
com.mokabyte.javaxml.todolist;
import
javax.ejb.*;
import java.rmi.RemoteException;
public
interface TodoList extends EJBObject {
public String getList() throws RemoteException;
}
Questa
è invece l'implementazione del metodo getList()
contenuta nella classe TodoListEJB.java
...
public String getList() throws RemoteException {
return "<TodoList>" +
"<Task>Leggere la posta</Task>"
+
"<Task>Riunione</Task>"
+
"<Task>Corsa nel parco</Task></TodoList>";
}
...
Una
implementazione reale avrebbe considerato identificativo
e ruolo dell'utente per ricercare le attività
in un database oppure interrogando un engine di workflow
ma ai fini dell'esempio non sarebbe cambiato nulla.
Ecco il deployment descriptor per l'ejb
<deployment
xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
<service
name="todolist" provider="java:EJB">
<parameter name="className" value="com.mokabyte.javaxml.todolist.TodoList"/>
<parameter name="beanJndiName" value="moka/TodoListHome"/>
<parameter name="homeInterfaceName" value="com.mokabyte.javaxml.todolist.TodoListHome"/>
<parameter name="remoteInterfaceName" value="com.mokabyte.javaxml.todolist.TodoList"/>
<parameter name="jndiContextClass" value="org.jnp.interfaces.NamingContextFactory"/>
<parameter name="jndiURL" value="localhost:1099"/>
<parameter name="allowedMethods" value="getList"/>
</service>
</deployment>
Come
si può vedere è stato indicato un provider
di tipo java:EJB e vi sono più parametri per
specificare il nome JNDI, le interfacce home e remote
e infine le property JNDI per connettersi al server
J2EE, in questo caso JBoss. A questo punto è
possibile fare deploy del servizio e accedervi da un
qualsiasi client SOAP.
In precedenza è stato mostrato come accedere
a un servizio usando JAX-RPC; nella sezione seguente
si mostrerà come realizzare un client più
evoluto basato su WSDL.
Axis
e WSDL
WSDL (Web Services Description Language) è un
linguaggio XML che descrive un web service, ovvero dove
si trova, quali metodi espone e quali tipi di dato utilizza.
Maggiori informazioni su WSDL si possono trovare in
[4].
Axis offre un supporto completo a WSDL: innanzitutto
è possibile ottenere il WSDL di un servizio in
modo immediato con un browser e appendendo un "?WSDL"
all'URL del servizio. Ad esempio accedendo all'URL http://localhost:8080/axis/services/todolist?WSDL
si ottiene il seguente documento
<?xml
version="1.0" encoding="UTF-8"?>
<wsdl:definitions
targetNamespace="http://localhost:8080/axis/services/todolist"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:impl="http://localhost:8080/axis/services/todolist-impl"
xmlns:intf="http://localhost:8080/axis/services/todolist"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<wsdl:message name="getListResponse">
<wsdl:part name="return" type="xsd:string"/>
</wsdl:message>
<wsdl:message name="getListRequest">
</wsdl:message>
<wsdl:portType name="TodoList">
<wsdl:operation name="getList">
<wsdl:input message="intf:getListRequest"/>
<wsdl:output message="intf:getListResponse"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="todolistSoapBinding"
type="intf:TodoList">
<wsdlsoap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="getList">
<wsdlsoap:operation soapAction=""/>
<wsdl:input>
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="getList" use="encoded"/>
</wsdl:input>
<wsdl:output>
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://localhost:8080/axis/services/todolist"
use="encoded"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="TodoListService">
<wsdl:port binding="intf:todolistSoapBinding"
name="todolist">
<wsdlsoap:address location="http://localhost:8080/axis/services/todolist"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Questa funzionalità è molto utile non
solo perchè è semplice e immediata ma
soprattutto perchè è indipendente da Axis
e da Java: un qualsiasi client SOAP in grado di utilizzare
WSDL si può collegare all'URL e usare il documento
così generato per interrogare il servizio.
Axis mette a disposizione anche un apposito tool a linea
di comando, Java2WSDL, per ottenere il WSDL a partire
da una classe Java. Lo stesso documento si può
quindi ottenere con
java
org.apache.axis.wsdl.Java2WSDL -o todolist.wsdl -l"http://localhost:8080/axis/services/todolist"
-n "urn:todolist" -p"com.mokabyte.javaxml.todolist"
"urn:todolist" com.mokabyte.javaxml.todolist.TodoList
dove
i parametri hanno il seguente significato
- -o
nome del documento WSDL da ottenere in output
- -l
url del servizio
- -n
namespace per il documento WSDL
- -p
specifica la corrispondenza fra nome di package e
namespace
- ·
nome della classe che contiene l'interfaccia del servizio.
A
partire dal documento WSDL è ora possibile utilizzare
un altro tool, WSD2Java, che permette di generare
- le
classi proxy da usare lato client;
- lo
skeleton che fa da bridge fra il runtime di Axis e
l'implementazione vera e propria del servizio.
Partendo
dal documento WSDL precedente è possibile generare
le classi da usare lato client nel modo seguente (nel
classpath devono essere inclusi axis.jar, commons-logging.jar,
jaxrpc.jar, wsdl4j.jar e il parser XML):
java
org.apache.axis.wsdl.WSDL2Java -o . -p com.mokabyte.javaxml.todolist.ws
todolist.wsdl
dove
- -o
indica dove generare i sorgenti
- -p
è il package che conterrà le classi
generate
Per
il documento precedente vengono generate le seguenti
classi all'interno del package com.mokabyte.javaxml.todolist.ws:
TodoList.java
Questa è l'interfaccia, che estende java.rmi.Remote,
ottenuta dal portType e viene chiamata SDI, Service
Definition Interface, in terminologia Axis.
TodoListService.java
Questa interfaccia viene derivata dal servizio definito
nel documento WSDL.
TodoListServiceLocator.java
La classe locator implementa l'interfaccia di servizio
e permette di ottenere un'istanza dello stub.
TodoListSoapBindingStub.java
Questa classe implementa l'interfaccia SDI, TodoList
in questo caso, e contiene il codice che converte
le invocazioni di metodi utilizzando la API di Axis.
In questo modo vengono nascosti allo sviluppatore
tutti i dettagli della chiamata SOAP.
Ecco
un client che usa le classi generate
package
com.mokabyte.javaxml.client.todolist;
import
com.mokabyte.javaxml.todolist.ws.*;
public
class TodoListClient {
public static void main (String args[])
throws Exception {
// Creazione del servizio
TodoListService service = new
TodoListServiceLocator();
// Si ottiene uno stub
TodoList todolist = service.getTodolist();
// Invocazione vera e propria
String result = todolist.getList();
System.out.println(result);
}
}
Oltre alle classi da usare lato client è possibile
generare anche gli skeleton lato server mediante le
opzioni --server-side --skeletonDeploy true del generatore.
Utilizzando tali switch verranno generati i file:
TodoListSoapBindingImpl.java
Implementazione del servizio. Questa è un template
che lo sviluppatore deve completare per accedere
TodoListSoapBindingSkeleton.java
Skeleton lato server che si pone fra l'engine Axis
e l'implementazione del servizio.
deploy.wsdd
e undeploy.wsdd
Deployment descriptor per il deploy e l'undeploy del
servizio.
Nell'esempio
presentato non si è fatto uso di questa feature
ma si è fatto deploy direttamente dell'EJB.
WSDL2Java
è un tool molto importante poichè permette
di semplificare notevolmente lo sviluppo di un client
Java per un web service: non solo si riduce la quantità
di codice da scrivere ma questo non fa alcun riferimento
alla API Axis o JAX-RPC.
Monitoring
Per verificare il corretto funzionamento di un web service
Axis mette a disposizione una comoda utility, TCPMon,
che permette di monitorare il traffico di rete generato
dal servizio. Lo si lancia con
java
org.apache.axis.utils.tcpmon
Appare
quindi la seguente interfaccia per l'inserimento dei
parametri
Figura
1 - Form di inserimento parametri
utilizzando
il tool come Listener si devono impostare
- Listen
Port #: numero di porta su cui TCPMon si pone in ascolto
per le richieste
- Target
Hostname: host su cui inoltrare le richieste
- Target
Port #: il numero di porta utilizzato dall'host target
A
questo punto il pulsante Add viene aggiunto un tab con
le informazioni sulla connessione inserita:
Figura
2 - Informazioni sulla connessione monitorata
E' possibile inserire altre serie di parametri per monitorare
altre connessioni.
I pannelli Request e Response mostrano i messaggi scambiati
ad ogni connessione. Ad esempio invocando il servizio
calculator si hanno i seguenti
messaggio
di richiesta
POST
/axis/services/calculator HTTP/1.0
Content-Length: 520
Host: localhost
Content-Type: text/xml;
charset=utf-8
SOAPAction: ""
<?xml
version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:add xmlns:ns1="Calculator">
<arg0 xsi:type="xsd:int">2</arg0>
<arg1 xsi:type="xsd:int">3</arg1>
</ns1:add>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
messaggio
di risposta
HTTP/1.0
200 OK
Content-Type: text/xml;
charset=utf-8
Content-Length: 450
Date: Sun, 25 Aug 2002 08:57:25 GMT
Server: Tomcat Web Server/3.3 Final ( JSP 1.1; Servlet
2.2 )
<?xml
version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Body>
<ns1:addResponse SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:ns1="Calculator">
<addReturn xsi:type="xsd:int">5</addReturn>
</ns1:addResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
In
questo modo si ha un completo controllo del traffico
di rete generato da un web service.
Client
Visual Basic
Poiché il vero valore aggiunto di un web service
è l'interoperabilità fra linguaggi e piattaforme
diverse si mostrerà ora un client Visual Basic
per il servizio todolist:
Option
Explicit
Private
Sub Form_Load()
Dim client As SoapClient30
Set client = New MSSOAPLib30.SoapClient30
client.MSSoapInit "http://localhost:8080/axis/services/todolist?WSDL"
Dim list As String
list = client.getList()
MsgBox list
End Sub
Il
codice precedente utilizza la libreria SOAP 3.0 di Microsoft
utilizzando il documento WSDL esposto dal runtime Axis.
Conclusioni
Axis è un toolkit con molte caratteristiche interessanti,
in particolare il supporto a WSDL che semplifica notevolmente
l'integrazione con altri sistemi, come dimostrato dal
client sviluppato in VB. Il generatore di codice semplifica
poi notevolmente lo sviluppo di client Java. Axis risulta
quindi essere un importante strumento per lo sviluppatore
Java.
Bibliografia
[1] Andrea Giovannini - "SOAP e Java", http://www.mokabyte.it/2001/01/soap.htm
[2] Apache Group - "Axis", http://xml.apache.org/axis
[3] Apache Group - "ANT", http://jakarta.apache.org/ant
[4] W3C - "Web Services Description Language"
- http://www.w3.org/TR/wsdl
[4] I sorgenti discussi nell'articolo: src.zip
Risorse
Scarica
qui i sorgenti presentati nell'articolo
Andrea
Giovannini si occupa di architetture per applicazioni
enterprise Web-based con tecnologia J2EE, con particolare
interesse per XML.
|