Esercitazione JAX-WS


1. Approccio Bottom-Up

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;
	}
}    

2. Deployment

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] 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

3. Invocare il Web Service

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 in utente

  • Cambiare nel messaggio di risposta il localname di response in listaOrdini

  • 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] Nota

il file package-info.java va creato come semplice file, non come classe, altrimenti Eclipse segnala una violazione nel nome.

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] 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)

4. Integrazione nell'infrastruttura preesistente

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.

Footer BGFooter BG
Tito Flagella - © 2007-2015