MokaByte 73- Aprile 2003
Corso di Java Web Services
III parte: JAXM e SAAJ
di
Massimiliano Bigatti

L'implementazione standard di SOAP in Java è definita dalle due specifiche JAXM (Java API for XML Messaging) e SAAJ (SOAP w/Attachments API for Java). Le prime definiscono le chiamate standard per una generica messaggistica XML (package javax.xml.messaging); le seconde sono l'implementazione del modello informativo di SOAP 1.1 in Java (package javax.xml.soap).
Perché SOAP w/Attachments (con allegati)? In realtà la piattaforma Java supporta, oltre a SOAP 1.1, anche un altro insieme di specifiche che indica come utilizzare MIME per inviare messaggi binari (come immagini o file jar) come parte di un allegato ad un messaggio SOAP. Questo aspetto entrerà maggiormente in gioco quando si parlerà di JAX-RPC e WSDL.


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.

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it