JAXM
Le
JAXM supportano le due tipologie di interazione di SOAP
(richiesta/risposta ed a senso unico), fornendo una
infrastruttura per formulare e ricevere messaggi SOAP.
Quest'ultima possibilità è però
prerogativa di applicazioni che girino all'interno di
Web Container J2EE in quanto il supporto è costruito
attorno alla tecnologia delle Servlet. Sempre quest'ultime
consentono di supportare l'invio di messaggi asincroni,
chiamate che non bloccano il client in attesa di una
risposta.
Purtroppo,
anche le SAAJ hanno capacità di messaging (tramite
le classi SOAPConnection e SOAPConnectionFactory), la
separazione tra le due API non è dunque netta
(le due sono infatti nate insieme e separate in un secondo
tempo per consentire ad altre tecnologie Java come JAX-RPC
la condivisione di codice per SOAP). Il resto del testo
potrebbe dunque riferirsi in modo generico a JAXM intendendo
sia queste che SAAJ.
Object
model SOAP
Le classi e le interfacce di SAAJ consentono una completa
modellazione di un messaggio SOAP complesso. In particolare
esistono entità per rappresentare i singoli elementi,
come il Body, Header, Envelope e la sezione MIME. Ciascuna
entità possiede metodi specifici per manipolare
il tag di appartenenza, ad esempio per specificare la
tipologia di encoding o per impostare i namespace.
Le
singole entità sono tra loro collegate tramite
opportuni metodi. Ad esempio, a partire da un messaggio
SOAP di tipo SOAPMessage è possibile ottenere
riferimenti ai singoli elementi (come Body, Header,
Envelope), come si evince dal listato 1.
Listato
1 - Modello ad oggetti di SAAJ
SOAPPart sp = msg.getSOAPPart();
SOAPEnvelope envelope = sp.getEnvelope();
SOAPHeader hdr = envelope.getHeader();
SOAPBody bdy = envelope.getBody();
Per
ottenere nuovi messaggi SOAP con JAXM è necessario
però partire da una factory, sia essa ottenuta
tramite la classe MessageFactory, nel caso di programmi
standalone, oppure attraverso un "provider"
se l'applicazione è in funzione in un ambiente
J2EE ed il tipo di messaggio richiesto è di tipo
asincrono.
Si
sarebbe potuto creare un messaggio SOAP semplicemente
concatenando delle stringhe, ad esempio utilizzando
StringBuffer ed il risultato sarebbe stato il medesimo.
Lo scopo finale di SAAJ infatti, è quello di
produrre un flusso XML ben formattato. Ovviamente, l'utilizzo
di API specifiche permette di organizzare meglio il
codice e renderlo più manutenibile, oltre a semplificare
operazioni di creazione particolarmente complesse. Inoltre,
API specifiche semplificano molto le operazioni di parsing
della risposta, in quanto forniscono chiamate già
pronte per accedere ai singoli elementi di un messaggio
SOAP.
Nel
primo caso, è necessario chiamare il metodo newInstance()
della classe MessageFactory.
MessageFactory
mf = MessageFactory.newInstance();
La
factory possiede il metodo createMessage() che consente
la creazione di nuovi messaggi posizionati nella part
MIME principale:
SOAPMessage
msg = mf.createMessage();
Inizialmente
i tag sono vuoti: il loro contenuto andrà valorizzato
successivamente operando direttamente tramite gli appositi
oggetti.
La gerarchia di classi di SAAJ è strutturata
a partire da Node, superinterfaccia di SOAPElement,
da cui derivano le altre, come ad esempio SOAPHeader
e SOAPBody. Non si confonda Node con quello di JAXP
in quanto, sebbene esprimano concetti simili (nodi di
un alberatura) appartengono a gerarchie di classi diverse.
Per
costruire un completo messaggio SOAP come quello visto
nel listato 8, è necessario chiamare gli opportuni
metodi per esplicitare ogni singolo dettaglio XML da
includere nell'output. Si consideri il listato 12: contiene
un estratto del programma ExchangeClient.java, un client
per il servizio di cambio valuta che tratta con i messaggi
SOAP visti nei listati 8 e 9.
Il
servizio di cambio valuta è disponibile su XMethods
(http://www.xmethods.com). Questo sito raccoglie una
vasta collezione di servizi Web costruiti sulle più
disparate piattaforme: da Java a .NET, da Apache SOAP
ad Axis, da Perl a Klix. Oltre al catalogo dei servizi
presenti su Internet, fornisce anche alcuni servizi
di prova, come ad esempio quello preso qui in esame.
Una funzione interessante del sito è l'analizzatore
di WSDL (la "descrizione del servizio", si
veda la sezione seguente di questo capitolo per ulteriori
dettagli) che consente di visualizzare le informazioni
relative alle funzioni, ai parametri ed ai valori di
ritorno di un servizio, in forma molto leggibile.
Come
si può notare dal listato, viene fatto uso della
factory per ottenere un nuovo messaggio vuoto e da questo
vengono estratti i riferimenti a ciascun elemento del
messaggio SOAP. Questi sono poi oggetto di manipolazione,
in particolare:
- vengono
aggiunte le dichiarazione dei namespace alla busta
(Envelope), tramite il metodo addNamespaceDeclaration();
- viene
impostato il tipo di encoding sul tag "getRate"
a SOAP encoding;
- vengono
esplicitamente impostati gli attributi type sugli
elementi country.
Si
noti che per creare i tag, viene fatto uso del metodo
createName() presente in SOAPEnvelope. Questo metodo
è disponibile in due forme: con un solo parametro,
per creare un tag semplice; con tre parametri per creare
un tag che appartenga ad un namespace, come si può
notare nella riga di codice che crea l'elemento SOAPBodyElement.
Listato
1 - Namespace, encoding e attributi con JAXM
//Il messaggio qui è già stato creato
SOAPPart sp = msg.getSOAPPart();
SOAPEnvelope env = sp.getEnvelope();
SOAPHeader hdr = env.getHeader();
SOAPBody bdy = env.getBody();
String xsi = "http://www.w3.org/2001/XMLSchema-instance";
env.addNamespaceDeclaration("xsi", xsi);
env.addNamespaceDeclaration("xsd", "http://www.w3.org/2001/XMLSchema");
env.setEncodingStyle("http://schemas.xmlsoap.org/soap/encoding/");
javax.xml.soap.Name
xsiTypeString = env.createName("type", "xsi",
xsi);
SOAPBodyElement gltp = bdy.addBodyElement(env.createName("getRate","ns1",
"urn:xmethods-CurrencyExchange"));
SOAPElement e1 = gltp.addChildElement(env.createName("country1"))
.addTextNode(divisaIniziale);
e1.addAttribute( xsiTypeString, "xsd:string"
);
Se
eseguito, questo codice produce in output lo stesso
flusso XML visto nel listato 8.
Le
API per il messaging
Una volta costruito il messaggio SOAP, è necessario
inviarlo. Per fare questo è possibile utilizzare
due approcci diversi, utilizzando o meno un "provider
di connessioni". Il provider è una sorta
di server di messaggistica che vive all'interno di contenitori
J2EE e che si occupa dell'instradamento del messaggio
al posto del runtime di JAXM. Questo meccanismo consente
l'implementazione di sistemi asincroni e l'utilizzo
di profili che possono interagire con il messaggio prima
che questo venga effettivamente inviato al reale destinatario.
In alternativa, è possibile utilizzare le API
SAAJ che forniscono chiamate sincrone, tramite le classi
SOAPConnectionFactory e SOAPConnection. In Tabella 1
sono riassunte le differenze tra connessioni SAAJ e
tramite provider.
Tabella
1 - Riassunto caratteristiche JAXM per connessione
con e senza provider
Per
inviare un messaggio senza provider è sufficiente
chiamare il metodo call() sulla connessione SOAPConnection,
passandogli l'indirizzo del servizio (chiamato endpoint).
JAXM definisce la classe URLEndpoint per rappresentare
un endpoint rappresentato come URL. E' possibile vedere
un esempio di quanto detto nel listato 2.
Listato
2 - Invio di un messaggio SOAP tramite connessione
standalone
URLEndpoint urlEndpoint = new URLEndpoint("http://services.mokabyte.it:80/endpoint");
SOAPConnectionFactory
scf = SOAPConnectionFactory.newInstance();
SOAPConnection con = scf.createConnection();
SOAPMessage reply = con.call( msg, urlEndpoint );
Con
un provider, l'esempio è simile ma non coinvolge
l'uso degli endpoint. Innanzitutto si parte da un contesto
JNDI tramite InitialContext(), poi si ottiene da questo
la factory e da questa la connessione. L'invio avviene
tramite la chiamata al metodo send().
Listato
3 - Utilizzo del provider di connessioni
Context ctx = new InitialContext();
ProviderConnectionFactory pcf = (ProviderConnectionFactory)
ctx.lookup("ProviderXYZ");
ProviderConnection con = pcf.createConnection();
con.send( msg );
I
due provider presenti in JAXM 1.1 riguardano ebXML-M
e SOAP-RP. Il protocollo di trasporto ebXML-M (utilizzato
nella tecnologia ebXML, si veda la fine del capitolo
per ulteriori informazioni), utilizza SOAP come base,
ma richiede la popolazione di particolari informazioni
nel tag header del messaggio. Con il profilo relativo,
JAXM consente di gestire le maggiori informazioni nell'intestazione.
SOAP-RP (ora WS-Routing) è invece uno standard
per il routing dei messaggi SOAP che inserisce nell'intestazione
del messaggio, un blocco XML che definisce tutto il
percorso che il messaggio dovrà seguire. In questo
scenario, infatti, il messaggio non viene semplicemente
trasferito dal client al server, ma da questo viene
instradato per raggiungere più server diversi
fino ad arrivare al destinatario finale. Questo può
essere utile per eseguire operazioni intermedie distribuite
sul messaggio: come un controllo antivirus, un controllo
di parità o di consistenza. Tutte queste operazioni
potrebbero essere svolte in nodi diversi e specializzati.
Per supportare diversi profili, è necessario
implementare factory di messaggi personalizzate. Le
implementazioni di riferimento di SUN per ebXML-M e
SOAP-RP sono comprese nei package com.sun.xml.messagin.jaxm.ebxml
e com.sun.xml.messagin.jaxm.soaprp.
Un
client per il servizio cambio valuta
A questo punto è semplice mettere insieme i pezzi
e creare un semplice client per la fruizione del servizio
di cambio valuta. L'implementazione di servizio a cui
si accederà è quello di prova di XMethods,
disponibile all'indirizzo http://services.xmethods.net:80/soap.
Nell'esempio, l'endpoint specifico viene costruito estraendo
l'url da una proprietà di sistema chiamata "endpoint".
Questa viene passata dallo script di Ant allegato al
codice e può assumere il valore visto prima,
nel caso si desideri accedere al vero e proprio servizio
XMethods, oppure un indirizzo locale, in caso si voglia
testare l'implementazione della parte server del servizio
illustrata nel paragrafo successivo.
Il codice completo del client è presente nel
listato 4: si riscontrerà quanto già visto
nei paragrafi precedenti in merito al modello ad oggetti
SOAP ed alle chiamate per la messaggistica.
Listato
4 - ExchangeClient.java -
Un client di esempio del servizio Currency-Exchange
import java.net.*;
import java.io.*;
import java.util.*;
import
javax.xml.messaging.*;
import javax.xml.soap.*;
class
ExchangeClient {
public ExchangeClient() {
sendMessage( "us", "euro" );
}
protected void sendMessage(String divisaIniz, String
divisaFin){
try {
MessageFactory mf = MessageFactory.newInstance();
SOAPMessage msg = mf.createMessage();
SOAPPart sp = msg.getSOAPPart();
SOAPEnvelope env = sp.getEnvelope();
SOAPHeader hdr = env.getHeader();
SOAPBody bdy = env.getBody();
String xsi = "http://www.w3.org/2001/XMLSchema-instance";
env.addNamespaceDeclaration("xsi",
xsi);
env.addNamespaceDeclaration("xsd",
"http://www.w3.org/2001/XMLSchema");
env.setEncodingStyle("http://schemas.xmlsoap.org/soap/encoding/");
javax.xml.soap.Name xsiTypeString
= env.createName("type",
"xsi", xsi);
SOAPBodyElement gltp
= bdy.addBodyElement(
env.createName("getRate",
"ns1",
"urn:xmethods-CurrencyExchange"));
SOAPElement e1 = gltp.addChildElement(
env.createName("country1"))
.addTextNode(divisaIniz);
e1.addAttribute( xsiTypeString,
"xsd:string" );
SOAPElement e2 = gltp.addChildElement(
env.createName("country2")).addTextNode(divisaFinale);
e2.addAttribute( xsiTypeString,
"xsd:string" );
FileOutputStream sentFile =
new FileOutputStream("sent.xml");
msg.writeTo(sentFile);
sentFile.close();
URLEndpoint endpoint = new
URLEndpoint(System.getProperty("endpoint"));
SOAPConnectionFactory scf =
SOAPConnectionFactory.newInstance();
SOAPConnection conn = scf.createConnection();
SOAPMessage response = conn.call(
msg, endpoint );
if( response != null ) {
//L'intestazione
XML viene gi messa da sola
FileOutputStream
replyFile = new FileOutputStream("reply.xml");
response.writeTo(replyFile);
replyFile.close();
sp = response.getSOAPPart();
env = sp.getEnvelope();
bdy = env.getBody();
Iterator ii = bdy.getChildElements();
while( ii.hasNext()
) {
SOAPElement
e = (SOAPElement)ii.next();
printSOAPElement(
e );
Iterator
kk = e.getChildElements();
while(
kk.hasNext() ) {
SOAPElement
ee = (SOAPElement)kk.next();
printSOAPElement(
ee );
}
}
}
}
catch(
Exception e ) {
e.printStackTrace();
}
}
protected void printSOAPElement( SOAPElement e ) {
System.out.print( e.getElementName().getLocalName()
);
System.out.print( " = " );
System.out.println( e.getValue() );
}
public static void main( String[] args ) {
new ExchangeClient();
}
}
Un
elemento degno di attenzione è legato alle modalità
di estrazione delle informazioni dal messaggio: ogni
sottoclasse di SOAPElement, dispone del metodo getChildElements(),
che fornisce un iteratore che elenca i nodi figli dell'elemento
attuale:
Iterator
ii = bdy.getChildElements();
while( ii.hasNext() ) {
SOAPElement e = (SOAPElement)ii.next();
}
Unitamente
al codice descritto in questo capitolo, è presente
un file di compilazione per lo strumento Ant, build.xml.
Per compilare ed eseguire il codice qui descritto, è
necessario indicare ad Ant di eseguire il target "client",
che indica di compilare ed eseguire la sola parte client
(il file build.xml è utilizzato nel resto del
paragrafo su JAXM per gli altri esempi). Per lanciare
il client, utilizzare il seguente comando:
Ant
client
Attenzione
a due aspetti: all'inizio del file build.xml sono presenti
alcune proprietà che indicano il percorso di
installazione del JWSDP e del codice di esempio: è
necessario modificare questi parametri per riflettere
i reali percorsi in uso; inoltre è necessario
fare attenzione alla proprietà endpoint specificata
nel file: indica al programma l'indirizzo del servizio
Web a cui collegarsi. Si consideri infatti il seguente
frammento di codice dal file ExchangeClient.java:
URLEndpoint
endpoint = new
URLEndpoint(System.getProperty("endpoint"));
L'indirizzo
dell'endpoint è acquisito dalla proprietà
di sistema chiamata endpoint specificata nel file build.xml.
In caso di accesso al servizio di prova di XMethods,
è necessario dunque attivare la seguente riga:
<property
name="endpoint" value="http://services.xmethods.net:80/soap"
/>
Un
server per implementare il servizio di cambio valuta
Oltre alla realizzazione dei client, JAXM e SAAJ consentono
la costruzione di servizi Web basati su SOAP. Attualmente
è supportato solo il protocollo SOAP su HTTP,
infatti l'infrastruttura si basa sulla servlet JAXMServlet
che a sua volta deriva da HttpServlet. Le servlet che
diverranno servizi Web devono poi implementare una specifica
interfaccia, in funzione del fatto che queste ritornino
o meno una risposta al client. In questo caso l'interfaccia
da implementare è ReqRespListener; in caso di
servlet solo riceventi, l'interfaccia è OnewayListener.
In particolare, le due interfacce definiscono il metodo
onMessage, il cui corpo deve essere implementato nella
servlet che implementa il servizio. In Tabella 2 sono
presenti le due firme di onMessage() per le due interfacce.
Tabella 2 - Le tipologie di servlet di JAXM
Per
capire come realizzare un servizio con JAXMServlet,
verrà ora preso in esame un esempio che implementa
proprio il servizio di cambio valuta utilizzato nel
paragrafo precedente (ovviamente non sarà presente
logica applicativa, ma verrà ritornata una semplice
costante numerica).
La struttura della servlet è inversa rispetto
al client visto sopra: prima è necessario decodificare
il messaggio ed estrarne i contenuti formativi, in seguito
è possibile formulare un messaggio di risposta
valorizzando gli opportuni tag con le informazioni che
il client si aspetta.
Il codice completo è presente nel listato 5.
Listato
5 - ExchangeServer.java - Un esempio di servlet
SOAP
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import
javax.xml.messaging.*;
import javax.xml.soap.*;
public
class ExchangeServlet extends JAXMServlet
implements ReqRespListener {
public void init(ServletConfig sc) throws
ServletException {
super.init(sc);
}
public SOAPMessage onMessage(SOAPMessage
message) {
SOAPMessage response = null;
String c1 = null, c2 = null;
System.out.println("Messaggio
ricevuto");
try {
message.writeTo(System.out);
SOAPPart sp = message.getSOAPPart();
SOAPEnvelope env
= sp.getEnvelope();
SOAPHeader hdr =
env.getHeader();
SOAPBody bdy = env.getBody();
Iterator ii = bdy.getChildElements();
while( ii.hasNext()
) {
SOAPElement
e = (SOAPElement)ii.next();
Iterator
kk = e.getChildElements();
while(
kk.hasNext() ) {
SOAPElement
ee = (SOAPElement)kk.next();
String
name = ee.getElementName().getLocalName();
if(
name.equals("country1") ) {
c1
= ee.getValue();
}
if(
name.equals("country2") ) {
c2
= ee.getValue();
}
}
}
float f = getRate(
c1, c2 );
response = createResponse(
f );
}
catch(Exception e) {
e.printStackTrace();
}
return response;
}
protected float getRate( String
c1, String c2 ) {
return (float)10.55;
}
protected SOAPMessage createResponse(float
f)
throws
SOAPException {
MessageFactory mf
= MessageFactory.newInstance();
SOAPMessage msg
= mf.createMessage();
SOAPPart sp = msg.getSOAPPart();
SOAPEnvelope env
= sp.getEnvelope();
SOAPHeader hdr =
env.getHeader();
SOAPBody bdy = env.getBody();
String xsi = "http://www.w3.org/2001/XMLSchema-instance";
env.addNamespaceDeclaration("xsi",
xsi);
env.addNamespaceDeclaration("xsd",
"http://www.w3.org/2001/XMLSchema");
env.addNamespaceDeclaration("soapenc",
"http://schemas.xmlsoap.org/soap/encoding/");
env.setEncodingStyle("http://schemas.xmlsoap.org/soap/encoding/");
Name
xsiTypeString = env.createName("type", "xsi",
xsi);
SOAPBodyElement
gltp = bdy.addBodyElement(
env.createName("getRateResponse",
"ns1",
"urn:xmethods-
CurrencyExchange"));
SOAPElement e1 =
gltp.addChildElement(
env.createName("Result")).addTextNode(""+f);
e1.addAttribute(
xsiTypeString, "xsd:float" );
return msg;
}
}
Come si può notare osservando il codice, il messaggio
SOAP ricevuto dalla servlet, già trasformato
in un oggetto SOAPMessage, viene scomposto nelle sue
parti fondamentali dagli opportuni metodi getter, ed
analizzato alla ricerca dei tag country, che costituiscono
i parametri di chiamata. Questa parte funge da adattatore
tra il messaggio SOAP e l'implementazione Java del servizio.
Questo è infatti implementato dal metodo getRate()
della servlet:
protected
float getRate( String c1, String c2 ) {
return (float)10.55;
}
Ovviamente,
implementazioni più complesse della una mera
restituzione di una costante potrebbero risiedere in
componenti esterni alla servlet, allo scopo di meglio
separare le competenze di ciascun modulo.
Il risultato prodotto dal metodo sopra citato viene
costruito dal metodo createResponse(), che come si può
notare osservando il codice, contiene le classiche chiamate
JAXM per la costruzione di un messaggio SOAP. L'elemento
XML che contiene il risultato effettivo è Result,
che è di tipo xsd:float.
Per compilare ed eseguire il codice server è
possibile utilizzare il target server del file build.xml
di Ant. Il comando:
Ant
server
si
occupa infatti di compilare la servlet, creare il file
war necessario alla sua installazione in un web container,
ed alla sua installazione in Tomcat. In merito a quest'ultimo
passaggio è necessario fare attenzione a due
aspetti: per prima cosa è indispensabile utilizzare
la versione di Ant fornita con il JWSDP, in quanto dispone
delle funzionalità aggiuntive necessarie ad operare
con il web container di Apache; in secondo luogo, è
necessario modificare i parametri di autenticazione
a Tomcat, username e password, per riflettere quelli
effettivamente specificati in fase di installazione
del JWSDP.
Il file build.xml contiene anche un task per la rimozione
del servizio da Tomcat, in caso si a necessario modificarlo
e reinstallarlo, questo task si chiama remove.
In
caso di errore?
Ma cosa succede se il servizio va in errore, magari
perché gli viene passato un dato di input erroneo,
come una nazione inesistente? Il servizio di XMethods
non ha una gestione degli errori particolarmente evoluta:
in caso venga passato un parametro non valido, il componente
solleva una eccezione, fornendo come ritorno un Fault
SOAP al posto della normale risposta. Nel corpo dei
dettagli presenta lo stack trace della chiamata, mentre
faultcode contiene il codice d'errore. Nel listato 6
è presente un esempio.
Listato
6 - Esempio di Fault SOAP
<?xml version='1.0' encoding='UTF-8'?>
<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns:xsd='http://www.w3.org/2001/XMLSchema'
xmlns:soapenc='http://schemas.xmlsoap.org/soap/encoding/'
soap:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
<soap:Body>
<soap:Fault>
<faultcode>soap:Server</faultcode>
<faultstring/>
<detail>
<e:electric-detail xmlns:e='http://www.themindelectric.com/'>
<class>java.lang.NullPointerException</class>
<message/>
<trace>
java.lang.NullPointerException
at net.xmethods.services.currencyexchange.CurrencyExchange
.getRate(CurrencyExchange.java:196)
at net.xmethods.services.currencyexchange.CurrencyExchange
.getRate(CurrencyExchange.java:19)
at java.lang.reflect.Method.invoke(Native Method)
at electric.util.Function.execute(Function.java:138)
at electric.net.tcp.Request.run(TCPServer.java:262)
at electric.util.ThreadPool.run(ThreadPool.java:105)
at java.lang.Thread.run(Thread.java:479)
</trace>
</e:electric-detail>
</detail>
</soap:Fault>
</soap:Body>
</soap:Envelope>
Il
client visto nei paragrafi precedenti era molto semplice
e non prevedeva una gestione di eventuali Fault SOAP.
In applicazioni reali è però bene sempre
considerare il fatto che un servizio SOAP può
rispondere con un errore al posto della struttura di
risposta prevista: per gestire i fault, SAAJ dispone
delle classi SOAPFault, Detail e DetailEntry. La prima
definisce l'intero corpo dell'errore (da cui è
possibile estrarre il codice d'errore, l'attore e la
stringa di descrizione utilizzando gli opportuni metodi,
come getFaultCode()); le altre consentono di estrarre
il contenuto del tag Detail, che può a sua volta
contenere diversi tag XML.
Un esempio di utilizzo di queste classi è presente
nel listato 7, un estratto di una versione migliorata
- completa di gestione dell'errore - di ExchangeClient.
Listato
7 - Esempio di gestione di un Fault SOAP
protected void processFault( SOAPFault fault ) {
System.out.println("FAULT: " + fault.getFaultCode()
);
Detail d = fault.getDetail();
Iterator ii = d.getDetailEntries();
while( ii.hasNext() ) {
DetailEntry de = (DetailEntry)ii.next();
//
}
}
Il
codice completo è contenuto nel file ExchangeClientFault.java
allegato. Per provarlo, utilizzare il comando:
Ant runClientFault
Un
esempio "divertente"
Per completare la trattazione di JAXM, si vedrà
un esempio particolare. Dilbert è un ingegnere,
protagonista dell'omonima e famosa striscia e che lavora
in una non meglio specificata grande azienda statunitense.
Ogni giorno Dilbert ha a che fare con capi incompetenti,
colleghi fannulloni e situazioni surreali. A parte le
gustose scenette di vita vissuta in cui molti potrebbero
riconoscersi, Dilbert ha anche un sito Internet (http://www.dilbert.com),
dove è possibile leggere la vignetta del giorno,
quelle del mese passato e persino comprare libri e gadget
con lui come protagonista. Quello che è più
interessante, però, è che il sito di Dilbert
ospita un servizio Web che fornisce online la vignetta
del giorno. Ancora più interessante è
che questa è codificata in base64 (viene proprio
trasmessa l'immagine in formato gif, non un link web)
e che il servizio SOAP richiede alcuni l'utilizzo di
alcune chiamate JAXM non immediate, come l'utilizzo
di una SOAPAction particolare. Si vedrà dunque
un esempio di client per accedere a questo particolare
servizio.
Le cose importanti sono sostanzialmente due. Per prima
cosa, è necessario impostare l'azione SOAP a
http://tempuri.org/DailyDilbertImage. Questo è
fattibile ottenendo dal messaggio SOAP le intestazioni
MIME ed impostandone una (chiamata SOAPAction) con il
valore richiesto. Importante, al termine delle operazioni,
è la chiamata al metodo saveChanges() sul messaggio
SOAP, per memorizzare le informazioni impostate (listato
8).
Listato
8 - Modifica le intestazioni MIME per includere SOAPAction
MimeHeaders mh = msg.getMimeHeaders();
mh.setHeader( "SOAPAction", "http://tempuri.org/DailyDilbertImage"
);
msg.saveChanges();
In
fase di ricezione della risposta è necessario
utilizzare la classe MimeUtility presente in JavaMail.
Questa contiene il metodo decode() che si occupa di
decodificare un blocco dati codificato (con varie codifiche,
tra cui base64) fornendo il risultato come stream. Una
volta ottenuto lo stream decodificato, è possibile
impostare un ciclo di lettura per caricare le informazioni
in un array di byte tramite la classe ByteArrayOutputStream
ed in seguito convertirla in immagine tramite la classe
ImageIcon di Swing.
Nel listato 9 è presente l'estratto di codice
che esegue questa serie di operazioni.
Listato
9 - Decodifica di una immagine codificata in base64
e visualizzazione
InputStream is = MimeUtility.decode(
new ByteArrayInputStream( e.getValue().getBytes() ),
"base64"
);
ByteArrayOutputStream
imageFile = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
while( true ) {
int size = is.read( buf );
if( size == -1 )
break;
imageFile.write( buf, 0, size );
}
label.setText("");
label.setIcon( new ImageIcon( imageFile.toByteArray()
) );
Il
codice completo è riportato nel Listato
10 allegato all'articolo.
|