L'obiettivo di questa esercitazione è quello di realizzare un nuovo servizio che elenchi gli ordini attivi, e renderlo utilizzabile dal client tramite il solito proxy che agisce da gateway per l'accesso ai servizi.
Lo facciamo utilizzando una nuova modalità per la realizzazione di Web Services, utile quando il codice Java sia già disponibile al momento della progettazione del Web Service. In questo caso, quindi, non genereremo gli skeleton del servizio a partire dal WSDL che lo descrive ma partendo direttamente dal codice Java e lasciando al framework il compito di generare il WSDL corrispondente.
Per scrivere i Web Services facciamo uso della specifica
Java API for XML Web Services (JAX-WS).
Creiamo un nuovo Dynamic Web Progect chiamato Magazzino
e scriviamo l'interfaccia del servizio lai.ws.Ordini
con l'annotazione (l'unica obbligatoria) @WebService
:
package lai.ws; import javax.jws.WebService; @WebService public interface Ordini { public ListaOrdini mostraOrdini(Utente utente); }
Ricordarsi quindi di configurare il Build Path del progetto, aggiungendo le librerie di
CXF, come già fatto nell'esercitazione precedente sui web services.
(Project > Properties > Java Build Path
> Libraries > Add library...
).
Creiamo quindi la classe lai.ws.OrdiniImpl
che implementa l'interfaccia Ordini:
package lai.ws; import javax.jws.WebService; @WebService(endpointInterface="lai.ws.Ordini") public class OrdiniImpl implements Ordini { public ListaOrdini mostraOrdini(Utente utente) { String[] lista = new String[2]; lista[0] = "0001"; lista[1] = "0002"; ListaOrdini listaOrdini = new ListaOrdini(); listaOrdini.setIdOrdine(lista); return listaOrdini; } }
Tutte le classi di interfaccia saranno mappate in XML. Per far questo devono rispettare i vincoli imposti da JAXB:
Devono avere un costruttore che non prende parametri
Ogni variabile deve avere l'apposito setter e getter per la lettura e scrittura del valore.
package lai.ws; public class Utente { private String codfisc; public void setCodfisc(String codfisc) { this.codfisc = codfisc; } public String getCodfisc() { return codfisc; } }
package lai.ws; public class ListaOrdini { private String[] idOrdine; public void setIdOrdine(String[] idOrdine) { this.idOrdine = idOrdine; } public String[] getIdOrdine() { return idOrdine; } }
Le operazioni da eseguire per il deploy di un Web Service
creato a partire dal codice java sono le stesse viste per
il deploy dei servizi creati a partire dal WSDL, quindi
modifichiamo il web.xml
per utilizzare la servlet di
CXF come visto nella precedente esercitazione e creiamo il file di
configurazione Spring cxf_beans.xml
per esporre
il nuovo servizio alla url GestioneOrdini
:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:cxf="http://cxf.apache.org/core" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd"> <import resource="classpath:META-INF/cxf/cxf.xml" /> <import resource="classpath:META-INF/cxf/cxf-servlet.xml" /> <!-- definizione dell'endpoint --> <jaxws:endpoint id="ordini" implementor="lai.ws.OrdiniImpl" address="/GestioneOrdini" /> <cxf:bus> <cxf:features> <cxf:logging /> </cxf:features> </cxf:bus> </beans>
Nota | |
---|---|
La configurazione aggiunge la feature di CXF logging. In questo modo tutti i messaggi scambiati con il servizio vengono loggati nel log di Tomcat. |
Adesso eseguiamo il deploy, esportando il WAR
e copiandolo nella cartella webapps
di tomcat come Magazzino.war.
Verifichiamo la correttezza del deploy richiamando il wsdl dell'applicazione:
http://localhost:8080/Magazzino/GestioneOrdini?wsdl
Il WSDL esposto descrive in dettaglio il Web Service appena implementato. Abbiamo tutte le informazioni necessarie per l'invocazione del servizio. Scriviamo manualmente una richiesta (eg. request.soap.v1.xml) che rispetti la sintassi prevista e spediamola tramite cURL
curl -v -H "Content-Type:text/xml" -H "SOAPAction:" -X POST -v -T request.soap.v1.xml "http://localhost:8080/Magazzino/GestioneOrdini"
Verifichiamo, sia nel log di cURL che di Tomcat, il corretto funzionamento del servizio.
Dall'analisi dei messaggi e del WSDL vi renderete conto del fatto che molti dettagli per il binding tra classi java ed elementi XML sono stati decisi dal framework con valori di default (il valore della SOAPAction, il namespace degli elementi e il nome di alcuni di loro) che rendono l'interfaccia inadeguata ad una distribuzione pubblica.
Le annotazioni di JAX-WS ci permettono però di intervenire su questi parametri ottenendo il WSDL ed i messaggi che desideriamo.
Ci poniamo quindi l'obiettivo di realizzare le seguenti modifiche:
Cambiare il namespace degli elementi in
http://www.negozio.org/
Cambiare nel messaggio di richiesta il localname di
arg0
inutente
Cambiare nel messaggio di risposta il localname di
response
inlistaOrdini
Valorizzare il parametro SOAPAction
Cambiare l'ElementFormDefault dell'XML Schema da unqualified a qualified, per avere anche gli elementi figli associati al namaespace
Per i primi 4 punti si può intervenire tramite opportune annotazioni sull'interfaccia del Servizio:
package lai.ws; import javax.jws.WebParam; import javax.jws.WebResult; import javax.jws.WebService; @WebService(targetNamespace="http://www.negozio.org") public interface Ordini { @WebResult(name="listaOrdini") @WebMethod(action = "mostraOrdini") public ListaOrdini mostraOrdini(@WebParam(name="utente") Utente utente); }
mentre per l'ultimo è necessario aggiungere al progetto, nella directory del package lai.ws, il file package-info.java
con il seguente contenuto:
@javax.xml.bind.annotation.XmlSchema(namespace = "http://www.negozio.org", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED) package lai.ws;
Nota | |
---|---|
il file |
Verifichiamo le modifiche apportate deployando la nuova versione del servizio ed interrogandolo con cURL
(ricordate di valorizzare la SOAPAction) utilizzando il messaggio di richiesta request.soap.v2.xml, conforme alla nuova specifica del servizio.
Nota | |
---|---|
La specifica prevede che il chiamante valorizzi la SOAPAction come indicato nel WSDL. Alcune implementazioni della specifica SOAP sono rigide su questo aspetto, mentre altre rilasciano il vincolo accettando impropriamente anche la SOAPAction vuota. |
Rianalizzando il WSDL ed il formato dei messaggi scambiati dalla nuova versione del servizio, potrete verificare che tutte le modifiche progettate sono state recepite.
Notiamo che i messaggi contengono un elemento radice (<mostraOrdini>
nella richiesta e <mostraOrdiniResponse>
nella risposta). La cosa è dovuta al comportamento di default di JAX-WS che, in accordo alla convenzione Wrapped Document/Literal
, prevede che il corpo dei messaggio sia racchiuso da un elemento radice corrispondente al nome dell'operazione invocata.
E' possibile cambiare il tipo di codifica agendo sull'annotazione @SOAPBinding
. Ad esempio per utilizzare la specifica Document/Literal
non wrapped:
package lai.ws; import javax.jws.WebParam; import javax.jws.WebResult; import javax.jws.WebService; import javax.jws.soap.SOAPBinding; @WebService(targetNamespace="http://www.negozio.org") @SOAPBinding(style=SOAPBinding.Style.DOCUMENT, use=SOAPBinding.Use.LITERAL, parameterStyle=SOAPBinding.ParameterStyle.BARE) public interface Ordini { @WebResult(name="listaOrdini") @WebMethod(action = "mostraOrdini") public ListaOrdini mostraOrdini(@WebParam(name="utente") Utente utente); }
Deployamo la nuova versione e testiamola un'ultima volta sempre con cURL (eg. request.soap.v3.xml)
Abbiamo implementato e installato il nuovo servizio. Non rimane che integrarlo nella nostra infrastruttura:
Implementiamo nel client la chiamata al servizio Magazzino, sempre indirizzata al proxy.
Espandiamo la logica di routing del proxy in modo da indirizzare correttamente la richiesta al nuovo servizio.
Per questi ultimi due punti, far riferimento alle esercitazioni precedenti.