Entra

View Full Version : -Official guide - Web Services con Axis 1.4 (Diffidate dalle imitazioni)


F@lkland§
09-01-2010, 16:11
Ciao a tutti, siamo tre studenti del Dipartimento di Informatica dell’Università di Bari. Recentemente per via di un progetto a cui abbiamo lavorato, ci siamo ritrovati a combattere con una serie di problematiche riguardanti il mondo dei web service. In realtà, parlare del “mondo dei web service” è un po’ esagerato, poiché i problemi si sono presentati lavorando alla creazione dei web service in java mediante l’utilizzo di axis.
Chi come noi ha cominciato a lavorare con axis senza avere la minima idea di come questo strumento permetta la generazione dei web service, sicuramente avrà mosso i primi passi cercando sul web informazioni utili a riguardo. Basta provare a fare una semplice ricerca per notare la quantità di guide che offrono spiegazioni su come “creare web service con axis in pochi minuti” oppure “come creare un web service in java semplicemente cambiando l’estensione da java a jws”.
Tutto questo fa pensare a qualcosa di estremamente semplice ed intuitivo che non potrebbe dare alcun tipo di problema nemmeno ad un neofita di axis. Questa sospetta osservazione può avere un fondo di verità se il nostro web service si limiti a restituirci la stringa “Ciao Peppino” se passiamo il parametro “Peppino” oppure che ci restituisca 6 se noi gli passiamo due numeri da moltiplicare, cioè 2 e 3.
Ma è davvero difficile trovare qualcuno a cui possa interessare qualcosa di questo tipo. Per questo i nostri problemi sono nati quando abbiamo cominciato a chiederci come fosse stato possibile generare dei web service, a partire dal nostro progetto in java strutturato su più package contenenti diverse classi. Purtroppo le tante guide da noi cercate, lette e rilette proprio non hanno preso in considerazione questo tipo di problematiche. Per questo motivo, dopo aver “combattuto” e “sconfitto” axis, abbiamo deciso di pubblicare questa guida che potrà tornare utile a molti di voi.

PROBLEMATICHE DI BASE

Prima di descrivere un esempio pratico, è bene fare chiarezza in maniera preventiva sui possibili e frequenti problemi che possono sorgere con axis.

Librerie: nella cartella \WEB-INF\lib\ di axis potrebbero mancare alcune delle librerie fondamentali, ad esempio: activation.jar, mail.jar, tools.jar, xml-apis.jar, xmlsec.jar. Pertanto, assicurarsi della presenza di queste librerie.
Classpath: quando si vanno ad eseguire operazioni di compilazione o di esecuzione di alcuni comandi può succedere di ricevere un errore del tipo java.lang.ClassNotFoundException. In altre parole, le librerie necessarie all’esecuzione dei comandi non sono presenti o come accade nella maggior parte dei casi, non sono state trovate, questo perché il classpath non è stato settato correttamente oppure è stato completamente ignorato. La strategia da adottare è quella di creare un file config.bat in cui siano presenti tutte le variabili d’ambiente necessarie. Il file dovrà avere una forma di questo tipo, dove ovviamente bisognerà settare i percorsi in base alla propria macchina:


set CATALINA_HOME=C:\Programmi\Apache Software Foundation\Tomcat 6.0;
set AXIS_HOME=C:\Programmi\Apache Software Foundation\Tomcat 6.0\webapps\axis;
set AXIS_LIB=C:\Programmi\Apache Software Foundation\
Tomcat 6.0\webapps\axis\WEB-INF\lib
set CLASSPATH=%CLASSPATH%;%AXIS_LIB%\activation.jar;%AXIS_LIB%\axis.jar;
%AXIS_LIB%\axis-ant.jar;%AXIS_LIB%\commons-discovery-0.2.jar;%AXIS_LIB%\
commons-logging-1.0.4.jar;%AXIS_LIB%\jaxrpc.jar;%AXIS_LIB%\
log4j-1.2.8.jar;%AXIS_LIB%\mail.jar;%AXIS_LIB%\saaj.jar;%AXIS_LIB%\
wsdl4j-1.5.1.jar;%AXIS_LIB%\xmlsec.jar;%AXIS_LIB%\xml-apis.jar
set JAVA_HOME=C:\Programmi\Java\jdk1.6.0_17;


GENERARE WEB SERVICE A PARTIRE DAL PROPRIO PROGETTO JAVA
Fatte queste premesse, passiamo alla descrizione di un esempio pratico. Il caso da noi analizzato dovrebbe rispecchiare per grandi linee un comune progetto java, in ogni caso questo esempio è utile a capire con quale logica affrontare un problema di questo tipo su un progetto di qualsiasi dimensione. Perciò è bene focalizzare l’attenzione non su cosa la nostra applicazione svolga, ma solo sugli accorgimenti da dover prendere per generare web service correttamente.
Supponiamo che la nostra applicazione java abbia il compito di gestire dei conti bancari; il nostro obiettivo è quello di dare la possibilità al sito web della banca X di mostrare il saldo di un determinato cliente mediante l’invocazione di un web service. Il web service in questione dovrà essere generato sfruttando i metodi per la visualizzazione del saldo già presenti nell’applicazione. Per arrivare a capire come sia possibile fare questo, bisogna partire dall’analizzare la struttura del sorgente dell’applicazione stessa:

Package principale:

banca: corrisponde al package principale, contenente tutto il codice del nostro progetto. Al suo interno saranno presenti tre sotto-package.

Sotto-package:

entity: è il package contenente tutte le classi che si occupano della logica di business, ad esempio Conto, Cliente, Transazione, ecc.
data_layer: è il package contenente tutte le classi per l’interfacciamento con il database.
web_services: è il package contenente le due classi che dovranno essere implementate al fine di esporre i metodi da trasformare in web service.

Classi del package web_services:

WsInterface.java: è la classe che corrisponde all’interfaccia software contenente la definizione dei metodi, per cui farà da collante tra i web service che saranno generati e la loro reale implementazione;
WebServices.java: è la classe che dovrà contenere gli stessi metodi dichiarati nella classe WsInterface, ma a differenza di quest’ultima dovrà contenere la reale implementazione dei metodi. Per cui richiamerà le classi dei package entity e data_layer.


1° PASSO: DICHIARAZIONE DEI METODI DA TRASFORMARE IN WEB SERVICE
Dalla descrizione appena formalizzata, si può notare come le uniche differenze da un qualsiasi progetto java siano da ricondurre al solo package web_services.
Nel nostro caso la classe WsInterface sarà la seguente:
package web_services;

public interface WsInterface {
// metodo per l’autenticazione di un cliente della banca dal web
public String autenticazione_web(String username, String password);
// metodo per la visualizzazione del saldo di un determinato cliente
public Double mostra_saldo(String username, String password, int numeroConto);
}


Come già accennato, la classe WebServices conterrà l’implementazione dei metodi, per questo motivo saranno presenti tutte le chiamate alle classi appartenenti al package entity, responsabile della logica di business:

package web_services;

import entity.Cliente;
import entity.Conto;

public class WebServices {

public String autenticazione_web(String username,String password)
{ // istanzia un nuovo oggetto cliente
Cliente cliente = new Cliente();
cliente.setUsername(username);
cliente.setPassword(password);
// verifica che i dati inseriti siano validi
if(cliente.readClienteWeb())
return cliente.getUsername();
else
return "1";
}

public Double mostra_saldo(String username, String password, int numConto)
{
if(autenticazione_web(username, password).equals(username))
{
// istanzia un nuovo oggetto conto
Conto conto = new Conto();
conto.setNumero(numConto);
conto.readConto();
// esegue la lettura del saldo del conto istanziato
return conto.getSaldo();
}
else
return -1.0;
}
}

2° PASSO: CREAZIONE DEL WSDL

Per una questione di comodità è bene copiare l’intera cartella \banca\ e il file config.bat sul desktop. Va precisato, poiché potrebbe non essere banale, che la cartella deve contenere le classi compilate in formato .class e non i sorgenti. Inoltre, è fondamentale utilizzare per tutte le operazioni sempre la stessa istanza della shell affinché non si perdano i percorsi settati nel classpath.
Detto questo, una volta posizionati con la shell sul desktop, eseguire il comando config.bat precedentemente creato in modo tale da assicurarci che tutte le variabili d’ambiente siano settate; dopodiché entrare nella cartella \banca\ ed eseguire il comando:

% java org.apache.axis.wsdl.Java2WSDL -o ws_banca.wsdl -l"http://localhost:8080/axis/services/banca" -n urn:banca -p"banca" urn:banca web_services.WsInterface

Se l’operazione è andata a buon fine, si potrà notare la presenza del file ws_banca.wsdl, il quale è stato creato a partire dalla classe WsInterface contenente la dichiarazione dei metodi.

3° PASSO: GENERARE LE CLASSI JAVA A PARTIRE DAL WSDL CREATO

Il passo successivo prevede l’operazione inversa, per cui a partire dal file wsdl generato, saranno create le classi java necessarie ad instaurare il binding tra web service e la loro implementazione. Basterà eseguire il seguente comando:

% java org.apache.axis.wsdl.WSDL2Java -o . -d Session -s -p ws ws_banca.wsdl

Il risultato sarà un nuovo package all’interno della cartella principale che si chiamerà ws. Questo package conterrà cinque classi sorgenti più il file di deploy e undeploy; delle cinque classi sorgenti solo una sarà di nostro interesse poiché andrà modificata. In altre parole, bisognerà inserire all’interno della classe BancaSoapBindingImpl le chiamate ai metodi della classe WebServices vista in precedenza, in modo tale da permetterne la reale implementazione.
La classe generata in origine sarà la seguente:

package ws;

public class BancaSoapBindingImpl implements ws.WsInterface{
public java.lang.String autenticazione_web(java.lang.String in0, java.lang.String in1) throws java.rmi.RemoteException {
return null;
}

public java.lang.Double mostra_saldo(java.lang.String in0, java.lang.String in1, int in2) throws java.rmi.RemoteException {
return null;
}
}

4° PASSO: CREARE IL BINDING TRA WEB SERVICE E LOGICA DI BUSINESS

Come si può notare, i metodi della classe BancaSoapBindingImpl corrispondono proprio ai metodi dichiarati nella classe WsInterface; per cui ora non bisognerà fare altro che sostituire al posto dei null, gli oggetti che realmente dovranno essere restituiti. Le modifiche apportate saranno le seguenti (in grassetto):

package ws;

import web_services.WebServices;

public class BancaSoapBindingImpl implements ws.WsInterface{
WebServices servizio_banca=new WebServices();

public java.lang.String autenticazione_web(java.lang.String in0, java.lang.String in1) throws java.rmi.RemoteException {
return servizio_banca.autenticazione_web(in0,in1);
}

public java.lang.Double mostra_saldo(java.lang.String in0, java.lang.String in1, int in2) throws java.rmi.RemoteException {
return servizio_banca.mostra_saldo(in0,in1,in2);
}
}

In questa maniera i nostri web service una volta invocati, richiameranno la classe WebServices presente nel package web_services, la quale contiene l’implementazione dei singoli metodi e le relative chiamate alle altre classi dei package entity e data_layer.
Una volta che la classe BancaSoapBindingImpl è stata modificata come descritto in precedenza, si può passare alla compilazione dei sorgenti del package ws mediante il comando:

% javac ws\*.java



5° PASSO: DEPLOYMENT DEI WEB SERVICE SUL SERVER (APACHE TOMCAT)

Se la compilazione ha avuto esito positivo si può passare alla creazione del file jar contenente tutte le classi compilate. E’ bene notare che il file jar va creato in modo tale da riprodurre in maniera precisa la struttura ad albero della cartella su cui si sta lavorando, per cui è fondamentale fare attenzione ai percorsi dei package inclusi all’interno del jar.
Nel nostro caso andrà eseguito il seguente comando:

% jar cvf ws_banca.jar entity/*.class data_layer/*.class web_services/*.class ws/*.class

Se è andato tutto a buon fine, sarà stato generato il file ws_banca.jar, al cui interno saranno presenti i quattro package entity, data_layer, web_services e ws con tutte le classi compilate. Questo file jar dovrà essere copiato all’interno della cartella \WEB-INF\lib\ di axis.
E’ importante osservare che se il nostro progetto esegue delle connessioni ad un database oppure utilizza delle librerie esterne, è bene che questi file jar vengano anch’essi copiati all’interno della cartella \WEB-INF\lib\ di axis. Ad esempio, se il nostro web service dovrà collegarsi ad un database di tipo mysql per la lettura di un dato, è fondamentale che venga copiata anche la libreria jdbc per la connessione a mysql.
Dopo essersi assicurati che Apache Tomcat sia avviato, si può passare al deployment vero e proprio dei web service. Per cui entrare all’interno della cartella \ws\ ed eseguire il comando:

% java org.apache.axis.client.AdminClient deploy.wsdd

Se otteniamo una risposta di questo tipo…

<admin>Done processing</admin>

…vuol dire che i nostri web service sono stati generati, per cui ora non resta che accedere all’indirizzo http://localhost:8080/axis/servlet/AxisServlet e verificare la loro presenza nell’elenco.
Ovviamente la prova del nove può esserci data solo testando i web service mediante un client, il quale accedendo al wsdl (nel nostro caso http://localhost:8080/axis/services/banca?wsdl) restituisca le giuste informazioni in base al web service invocato.
La scelta sul client da implementare è abbastanza ampia, si può passare dal flash al php o ancora java. Ma almeno per questo, il web è in grado di fornirvi un’adeguata documentazione che vi possa far creare il client di un web service senza dover passare notti insonni di fronte al computer!

Gli amici di Ago

Darkness10
09-01-2010, 16:14
Spero serva a molti questa nostra guida! :)
Ciao da tutti e 3 gli amici di Ago!

Player1
23-02-2010, 16:53
Ciao ragazzi, grazie per la guida, sono riuscito a far girare su axis il mio web service ma ora ho un problema con il client, avete qualche suggerimento please?
Ecco il problema:

La mia webService si chiama CL se provo dal browser a richiamare il metodo getUserProfile(int user id) funziona, ad esempio se nel browser metto:
http://localhost:8080/axis/services/CL?method=getUserProfile&u=2
Lui mi restituisce:
Il file XML specificato apparentemente non ha un foglio di stile associato. L'albero del documento è mostrato di seguito.


<soapenv:Envelope>

<soapenv:Body>

<getUserProfileResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<getUserProfileReturn href="#id0"/>
</getUserProfileResponse>

<multiRef id="id0" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns1:User">
<ID xsi:type="xsd:int">2</ID>
<authorization xsi:type="xsd:int">3</authorization>
<name xsi:type="xsd:string">Elisa</name>
<surname xsi:type="xsd:string">Esposito</surname>
</multiRef>
</soapenv:Body>
</soapenv:Envelope>

Ok fin qui tutto bene.
Ora sto provando con netbeans a creare un client.
Dal wsdl netbeans mi crea un sacco di classi java di cui per ora ignoro il significato, adesso se creo questa semplice classe:

import java.net.*;
import java.rmi.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.xml.namespace.*;
import javax.xml.rpc.*;
import org.apache.axis.client.Call;
import stubs.User;

public class AAItoCL {
public static void main(String[] args) {
User u = getUserProfile (2);
System.out.println(u.getName());
}

private static User getUserProfile (int userId){
Object[] param = new Object[1];
param[0]=Integer.valueOf(userId);
User res = new User();
try {
Call call = (Call) new org.apache.axis.client.Service().createCall();
call.setTargetEndpointAddress(new URL("http://localhost:8080/axis/services/"));
res = (User) call.invoke(new QName("CL", "getUserProfile"), param);
}
catch (MalformedURLException ex) {
System.out.println("Error: invalid URL");
}
catch (ServiceException ex) {
System.out.println("Error: call fault");
}
catch (RemoteException ex) {
System.out.println("Error: WS invocation fault");
}
finally {
return res;
}
}
}

Mi lancia la seguente eccezione:
- Exception:
org.xml.sax.SAXException: Deserializing parameter 'getUserProfileReturn': could not find deserializer for type {urn:User}User
at org.apache.axis.message.RPCHandler.onStartChild(RPCHandler.java:277)
at org.apache.axis.encoding.DeserializationContext.startElement(DeserializationContext.java:1035)
at org.apache.axis.message.SAX2EventRecorder.replay(SAX2EventRecorder.java:165)
at org.apache.axis.message.MessageElement.publishToHandler(MessageElement.java:1141)
at org.apache.axis.message.RPCElement.deserialize(RPCElement.java:345)
at org.apache.axis.message.RPCElement.getParams(RPCElement.java:384)
at org.apache.axis.client.Call.invoke(Call.java:2467)
at org.apache.axis.client.Call.invoke(Call.java:2366)
at org.apache.axis.client.Call.invoke(Call.java:1812)
at org.apache.axis.client.Call.invoke(Call.java:1727)
at AAItoCL.AAItoCL.getUserProfile(AAItoCL.java:120)
at AAItoCL.AAItoCL.main(AAItoCL.java:36)

Credo si tratti di un problema nella serializzazione, come posso risolverlo?
Se provo ad utilizzare un metodo che prende ad esempio un parametro intero e restituisce un booleano tale problema non si verifica.
Netbeans ha creato, leggendo il wsdl, anche altre classi come "User_SOAPBuilder" e "User_SOAPSerializer" che forse potrebbero essermi utili ma non so come possono essere usate.
Please HELP!