MokaByte 66 - 7mbre 2002 
Apache Axis
Il SOAP per Java
di
Andrea Giovannini
SOAP è una delle tecnologie alla base dei Web Service e Apache SOAP era l'implementazione di riferimento per gli sviluppatori Java. Questo articolo presenta Axis , il risultato di un completo ridisegno di Apache SOAP

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.

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it