In questa nuova serie, parleremo della realizzazione di web services in Java utilizzando le ultimissime specifiche e tecnologie rilasciate da Sun. Vedremo come l‘uso di annotazioni, la API JAXWS e le tecniche basate sugli handler permettano di realizzare interessanti soluzioni fra cui la web service security1.0
Il nuovo corso di Sun
Di recente Sun ha dato vita a un nuovo corso nella politica per i futuri sviluppi della tecnologia Java, cosa che le ha consentito di capitalizzare nuovamente l’attenzione dei programmatori di mezzo mondo grazie a nuovi prodotti completamente open e finalmente competitivi dal punto di vista tecnico.
Possiamo citare le ultime evoluzioni della piattaforma NetBeans, che rappresenta un valido strumento di sviluppo: il supporto della casa prevede finalmente un catalogo di plugin razionale, funzionale e umanamente gestibile (cosa che è forse il tallone di Achille del mondo Eclipse). Anche nel settore degli application server si può certamente verificare questo nuovo corso: Glassfish può essere considerato il primo application server rilasciato da Sun che stia riscuotendo un successo di pubblico e di critica.
In parte il successo di questi prodotti e della politica di Sun risiede nel proporre strumenti sempre allineati con le ultime tecnologie alla moda, cosa che di recente si era un po’ persa: il successo di standard e tecnologie dal basso (p.e. Hibernate, Spring) aveva in parte fatto perdere prestigio e competitività alle proposte di Sun (rimasta per un po’ in affanno).
La risposta, che per certi versi è stata efficace, ma è ancora da verificare per intero, ha visto il rilascio di nuove specifiche e nuovi standard volti a una maggiore semplificazione (p.e. EJB 3.0 e la modalità POJO-annotation di sviluppare applicazioni Java EE).
In quest’ottica di rinnovamento, anche la parte relativa allo sviluppo di web services è stato oggetto di un intenso lavoro di re-ingegnerizzazione: l’obiettivo di questa serie di articoli è proprio quella di presentare queste nuove caratteristiche sia tecnologiche che architetturali. Partendo dalla definizione di un semplice web service tramite le annotazioni di Java5, verrà mostrato come utilizzare il meccanismo degli handler per introdurre funzionalità avanzate ai web service in modo dichiarativo quali log e sicurezza.
Nuove tecnologie WSIT
Fin dalla loro apparsa sullo scenario tecnologico, i web services sono stati presentati come la soluzione principe per la realizzazione di architetture volte alla interoperabilità, interconnessione, integrazione. Le premesse furono fin dall’inizio estremamente interessanti: i vari elementi costituenti risolvevano problemi specifici delle precedenti tecnologie di programmazione distribuita. Si pensi alla possibilità di usare standard aperti tramite binomio XML e HTTP (di fatto il modello SOAP), unitamente alla presenza di un linguaggio descrittivo standard (WSDL) e alla introduzione di un sistema di pubblicazione/ricerca (UDDI).
L’elevato interesse intorno a questa tecnologia, se da un lato le ha fornito una spinta in avanti, dall’altro ne ha rappresentato anche il principale impedimento: i vari produttori e soggetti coinvolti a vario titolo nello sviluppo di implementazioni e componenti per WS hanno infatti intrapreso una corsa a gran velocità impedendo di rimanere al passo agli organismi preposti nella defizione degli standard e specifiche. Questo ha portato in poco tempo alla nascita di implementazioni e specifiche proprietarie che poco avevano a che fare con gli obiettivi originari dei web services (un limpido esempio di una chiara contraddizione, ovvero la diffusione di standard proprietari).
Nel 2003, consci che avere un web service C# incapace di parlare con uno scritto con tecnologia Java era un non-senso, i vari produttori si sono seduti a un tavolo con l’obiettivo di porre rimedio a questo problema. Nacque WS-I Organization ([WSI]), a cui hanno in breve tempo aderito tutti i più importanti protagonisti del settore, con l’obiettivo di definire un nuovo standard volto a garantire la interoperabilità reale dei web services. Il consorzio, invece di stravolgere tutto il set di tecnologie, ha deciso di eseguire un’opera di selezione, volta a raccogliere il subset minimo di specifiche e standard comuni a tutte le implementazioni. Il vari sottoinsiemi sono stati raccolti in profili a seconda degli obiettivi che si vogliono ottenere. Il tutto parte dal profile “basic” (che contiene le indicazioni base per la realizzazione di web services che siano interoperabili) a cui si stanno pian piano aggiungendo altri profili: in questo momento inizia a essere utilizzabile il profilo “di sicurezza”, mentre si inizia a parlare dei profili prossimi venturi, orientati alla transazione, allegati etc.
In ambito WS-I, in seguito ad un processo di rinnovamento e ammodernamento, Sun ha rilasciato di recente una nuova implementazione chiamata WSIT: qui troviamo il nuovo runtime Metro, le nuove api JAXWS, il nuovo approccio basato su XWSS 2.0/3.0. Da quanto riportato sul sito della casa americana, WSIT dovrebbe consentire una maggior flessibilità permettendo di scegliere sia la strada programmativa che dichiarativa.
A detta di Sun, WSIT non rappresenta una versione proprietaria delle specifiche dettate da WS-I ma anzi l’uso di questo pacchetto dovrebbe consentire lo sviluppo di web service in linea con tutte le specifiche WS-*.
Figura 1 – Il profilo WS-Basic prevede che si utilizzino solo alcune tecnologie di comune diffusione in tutte le principali implementazioni dei vari runtime.
Nel prosieguo di questo articolo (e nei prossimi), vedremo cosa significa utilizzare queste tecnologie e quanto effettivamente si è semplificato lo sviluppo di un web service grazie all’uso di annotazioni apposite. A tal proposito è bene ricordare che, sebbene le specifiche saranno parte integrante della Java6, tutte le prove qui riportate sono state scritte e testate con Java5 ed eseguite nell’application server Glassfish 2.0 (che contiene Metro), su Tomcat e su JBoss (opportunamente adattato all’uso di JAXWS).
Realizzare web services con Java5
L’introduzione in Java5 delle annotazioni ha portato un benefico risultato anche per quanto concerne lo sviluppo di web services, cosa che si è fortemente semplificata.
La prima cosa da fare è partire dalla definizione di un semplice POJO al quale vengono aggiunte le annotazioni necessarie per consentire la successiva trasformazione in web service.
Nell’esempio che segue vedremo proprio questo passaggio, partendo da un bean che contiene alcuni semplici metodi che verranno poi pubblicati come web services methods. Il codice dovrebbe essere piuttosto chiaro: si noti la presenza della annotazione @WebService e @WebMethod necessarie per creare il web service e il remote method.
import javax.jws.WebService;@WebService
public class WSImpl {
@WebMethod
public void simple() {
System.out.println("simple");
}
@WebMethod
public String simpleEcho(String in) {
System.out.println("simpleEcho. in=" + in);
return in;
}
@WebMethod
public String simpleFault() throws WSException {
System.out.println("simpleFault");
throw new WSException("simpleFault");
}
@WebMethod
public Info echoInfo(Info in) throws WSException {
System.out.println("simpleEcho. in=" + in);
return in;
}
}
Si noti la presenza degli import javax.jws e la dichiarazione dell’eccezione WSException che verrà utilizzata per rilanciare l’errore sotto forma di SOAP Fault message. Come si può notare la parte relativa alla definizione del web service è molto semplice.
Anche se in genere il wizard dell’IDE di turno realizzava la maggior parte del lavoro, in passato il lavoro da compiere richiedeva la gestione di tonnellate di XML e interfacce da implementare, per cui si può senz’altro apprezzare questo nuovo corso volto alla semplificazione.
Nelle prossime puntate vedremo come, partendo questo semplice POJO, sia possibile espandere le funzioni infrastrutturali del servizio, ad esempio introducendo dei filtri (in gergo “handler”) che permettano la gestione in maniera trasparente di alcuni importanti aspetti come la gestione degli errori, il log e la sicurezza.
Figura 2 – L’organizzazione del progetto segue la tipica organizzazione di un progetto Ant.
Una volta che si è definito il POJO di cui sopra, lo si può trasformare in un web service semplicemente tramite una operazione di impacchettamento e di deploy nel container: per svolgere questo lavoro si può adoperare un IDE compatibile con le recenti API Java JAXWS (NetBeans a tal proposito può offrire un valido aiuto). Nel nostro caso utilizzeremo un approccio più rudimentale che però permette di comprendere nel dettaglio il funzionamento delle varie fasi.
Figura 3 – I sorgenti generati al termine della operazione di build contengono tutti gli artefatti necessari per generare il servizio web.
La struttura segue quella che comunemente viene adottata per un progetto Ant: la directory “ant” contiene il file di build (che vedremo fra poco), la dir “lib” le librerie necessarie per poter eseguire le operazioni di build e test (si veda il paragrafo conclusivo dell’articolo per maggiori dettagli su questa cartella) e la directory “src” con i sorgenti organizzati in due sottogruppi (2 sottodirectory rispettivamente). I sorgenti generati manualmente (ovvero scritti dal programmatore) sono stati posti in una sottocartella “source”, mentre nella “generated” troviamo i file .java generati automaticamente da un apposito task Ant e che poi verranno utilizzati per il build finale.
Come si può notare dalla figura 2 tali classi corrispondono ai singoli elementi che costituiscono il web service: la classe, il messaggio di request e quello di risposta, il fault eventuale e la classe corrispondente alla eccezione. Tutto il set di queste classi concorre a definire la entità web service nel suo complesso. La generazione di tali sorgenti e la successiva compilazione completa avviene tramite l’utilizzo del tool APT (Annotation Processing Tool) strumento del JDK introdotto con Java5 per poter generare i sorgenti a partire da una annotazione comprensibile dal tool stesso. APT è lo stesso che viene utilizzato per interpretare una annotazione EJB 3.0 e generare un session bean o un entity.
In Ant non è disponibile di default il task per eseguire il tool APT, per cui per prima cosa è necessario eseguire quella che potremmo definire l’installazione del plugin corrispondente: in Ant questo plug avviene con una semplice importazione/definizione di un nuovo task
A questo punto il task APT è utilizzabile per interpretare le annotazioni introdotte nel POJO come mostrato precedentemente. Il target che esegue il task APT è il seguente:
destdir="${basedir}/build/classes"
sourcedestdir="${basedir}/src/generated"
sourcepath="${basedir}/src/source">
Questo target (il cui nome ha un significato piuttosto intuitivo) esegue la generazione dei sorgenti e la relativa compilazione utilizzando come classpath le risorse definite da i tre pathelement (ovvero le directory con i sorgenti) e la directory build.
Al termine di questo target, nella directory dei compilati troveremo tutti i pezzi necessari per l’esecuzione del servizio: in altre parole il web service è pronto. È sufficiente adesso eseguire il build e deploy per poterlo mandare in esecuzione.
Il target che segue esegue proprio questa operazione:
dir="${basedir}/web"
includes="**/*.htm"
prefix="/"/>
Di fatto il web service viene impacchettato sotto forma di web application, opzione questa che rende molto più semplice la successiva gestione e invocazione (vedi oltre).
Si noti la presenza del file sun-jaxws.xml (necessario se si vuole effettuare il deploy nell’application server Glassfish), la cui funzione è piuttosto interessante, dato che abilita il web service alla invocazione remota, ovvero serve per definire il service-point:
url-pattern='/moka-ws'/>
Il name dell’endpoint definisce l’istanza del web service che è implementato dalla classe definita dall’attributo “implementation”. Molto importante il valore del campo “url-pattern” che serve per specificare l’URL con ilquale il servizio potrà essere invocato dal client.
Questa operazione è resa possibile perche’ nella web application che contiene il servizio, viene anche attivata una servlet presente nel pacchetto JAXWS. Tale servlet di fatto lavora come proxy fra il browser e il servizio web, consentendone l’invocazione. Ci si può rendere conto di questo passaggio consultando il file di deploy web.xml:
wsit-jaxws-servlet
wsit-jaxws-servlet
JAX-WS endpoint - fromjava
com.sun.xml.ws.transport.http.servlet.WSServlet
1
wsit-jaxws-servlet
/moka-ws
In buona sostanza il parametro url-pattern nel deployment descriptor del web service deve essere lo stesso dell’url pattern che è associato alla servlet.
Deploy e funzionamento del servizio
Dopo aver eseguito le operazioni di impacchettamento, il servizio può essere installato (deploy) nell’application server. Questa operazione, che dipende in larga parte dal server che si è deciso di utilizzare, coinvolge anche una serie di considerazioni relative al runtime scelto. Parleremo nel dettaglio di questi aspetti nella seconda puntata di questa serie. Per adesso, ci limiteremo a prendere ad esempio Glassfish.
Per prima cosa è necessario accendere il server sul dominio applicativo che ci interessa (in questo caso domain1, quello di default fornito alla installazione del server): questa cosa può essere fatta nel seguente modo (qui si usa la sintassi unix-like)
./asadmin start-domain domain1
comando da esguire nella directory
/bin
Il deploy potrà essere eseguito tramite una semplice copia del file .war nella cartella
/domains/domain1/autodeploy
dove domain1 è il dominio che si è deciso di attivare per l’application server.
A tal punto si potrà utilizzare il browser per verificare se per verificare se il servizio sia correttamente installato: collegandosi all’indirizzo
http://localhost:8080/wsit-moka/moka-ws
si potrà avere il risultato riportato in figura 4
Figura 4 – Utilizzando il browser si può controllare se il servizio è disponibile: in questo caso la WSServlet ci fornisce una pagina riepilogativa dello stato di funzionamento del web service.
Cliccando sul link relativo al file WSDL
http://localhost:8080/wsit-moka/moka-ws?wsdl
si potrà avere la visualizzazione di tale descrittore. L’URL del WSDL file è molto importante perche’ permette al client di invocare il servizio o, come mostrato poco più avanti, di creare il client stesso.
Creare il client: dal WSDL al web service client
A questo punto, sempre utilizzando un apposito task di Ant, è possibile creare il client partendo dal file WSDL: per svolgere questo compito si può utilizzare il task wsimport, che come per APT deve essere per prima cosa importato
Successivamente si può eseguire il task
verbose="${verbose}"
keep="${keep-generated-client-src}"
extension="${extension}"
destdir="${build.classes.home}"
wsdl="${client.wsdl}">
Per motivi di spazio non è possibile analizzare ogni singolo dettaglio del target Ant, (si veda il riferimento [IMP] in bibliografia): si noti il attributo keep (forza il mantenimento dei sorgenti creati dopo il wsimport e prima della compilazione), e il wsdl, altrove definito nel seguente modo
Infine, per poter funzionare, il WSImport necessita di un paio di file XML/XSD che definiscono la struttura del WSDL (al fine di verificarne l’eventuale validità): ecco un esempio del primo
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns_wsdl="http://schemas.xmlsoap.org/wsdl/"
wsdlLocation="http://localhost:8080/wsit-moka/moka-ws?wsdl"
>
dove si può notare il riferimento all’URL di pubblicazione del WSDL.
Per eseguier il client, si può usare il target run-client, il quale in questo caso lancia ogni volta il target di build (si vedano le dipendenze).
Note sull’esempio allegato
L’esempio allegato all’articolo dovrebbe essere piuttosto semplice da eseguire per chi abbia un minimo di conoscenze di Ant. L’archivio contiene tutti le risorse necessarie per il funzionamento, anche se per motivi di download sono state rimosse tutte le librerie (anche per motivi di licenza: spesso allegare una libreria all’esempio non è cosa banale).
Il lettore dovrà quindi procedere a scaricare ogni singolo file .jar e posizionarlo sotto la directory lib, secondo ilo schema riportato in figura 5.
Figura 5 – La directory lib contiene due sottocartelle con tutte le librerie necessarie per compilare ed eseguire gli esempi.
Per reperire le librerie necessarie si può fare riferimento alla risorsa [JAXWS] nei riferimenti. Per chi volesse utilizzare un ambiente grafico, interessante anche il riferimento [NB], sebbene sia tarato su una versione non recentissima del popolare IDE.
Ricordiamo infine che per far funzionare l’esempio è necessario installare GlassFish e impostare nel file di properties di Ant (file build.properties sotto la cartella ant) la directory dove è stato installato l’application server; per fare questo si modifichi la proprietà glassfish_home che si trova in tale file e opzionalmente anche il domain name nel caso se ne usi uno differente da quello di default (domain1):
domain=domain1
glassfish_home=
deploy.dir=${glassfish_home}/domains/${domain}/autodeploy
Riferimenti
[WSI] WSI Organisation home page
http://www.ws-i.org/
[JAXWS] JAXWS Metro Home page
https://jax-ws.dev.java.net/
[NB] Building vJAX-WS 2.0 Services with NetBeans 5.0
http://www.netbeans.org/kb/50/jaxws20.html