Terminiamo questa serie su Spring-WS illustrando alcune caratteristiche di questo framework che ci permettono di rendere più agevole e flessibile la pubblicazione di un Web Service. In questo articolo analizzeremo come sia possibile gestire versioni multiple dello stesso servizio definendo un interceptor che applica, al payload XML, una trasformazione XSLT.
Il mese scorso ci eravamo lasciati, tenendoci per “ultima cartuccia” la gestione di versioni multiple dello stesso Web Service. In questa quinta e ultima parte, concludiamo con tale argomento la nostra serie su Spring-WS.
Implementazione dell’interceptor di trasformazione XSLT
Sempre a livello di definizione del mapping di un endpoint, Spring-WS definisce la classe PayloadTransformingInterceptor quale interceptor basato su stylesheet XSLT. Attraverso questo interceptor è possibile trasformare il payload del messaggio SOAP mediante una specifica trasformazione XSLT. Vedremo come l’adeguamento dell’XML scambiato tra client ed endpoint ci consente di gestire, senza duplicazioni di codice, versioni multiple dello stesso servizio.
Lo scenario di applicazione è quello in cui l’interfaccia di un servizio subisce un adeguamento. Terminata la definizione del nuovo contratto e l’implementazione della logica applicativa del servizio, ci troviamo a dover gestire la migrazione dei client che consumavano il vecchio contratto. I client potrebbero semplicemente ricreare i loro proxy a partire dalla pubblicazione del nuovo WSDL per poi adeguare il software alle nuove informazioni definite nella nuova interfaccia. Potremmo rendere più efficiente questa migrazione se riuscissimo a mantenere in linea i due servizi senza ridondanze di implementazione. In questo modo i client potrebbero adeguarsi gradualmente alla nuova interfaccia senza brusche interruzioni del business, fino alla completa dismissione del vecchio servizio.
Nell’esempio riportato, la modifica del contratto del servizio consiste nell’aggiunta nella request di due campi, la nazione e il codice della tessera che fidelizza il cliente; conseguentemente ne è stata adeguata la logica applicativa ed è stato pubblicato il nuovo servizio. Viene quindi definito un opportuno mapping per le richieste SOAP dei client che implementavano la vecchia interfaccia. Questo fa sì che le richieste di questi client vengano indirizzate verso l’endpoint implementato dalla nuova interfaccia ma per il cui mapping è definita la trasformazione XSLT transformRequest.xslt, che adegua il messaggio SOAP alla nuova interfaccia.
Nell’esempio, la trasformazione XSLT completa il messaggio XML con dei valori di default gestiti (“ITALIA”, “999999”).
Di seguito, nelle figure, la definizione del mapping e dell’interceptor di trasformazione XSLT associato, da applicare ai messaggi che dichiarano nella busta SOAP il namespace del vecchio contratto di servizio.
Figura 1 – Definizione dell’interceptor di trasformazione XSLT.
Figura 2 – Esempio di payload di request secondo la vecchia interfaccia del servizio.
Figura 3 – Payload di request trasformato dallo stylesheet XSLT.
Riportiamo di seguito lo stylesheet XSLT di trasformazione. Per ragioni di leggibilità, nei tipi complessi sono state omesse alcune direttive “xsl:value-of select”, mentre abbiamo evidenziato le direttive che adeguano la request. Nell’esempio allegato (scaricabile dal menu in alto a sinistra) il lettore troverà il file nella sua integrità.
Figura 4 – Stylesheet XSLT di trasformazione.
Implementazione dell’interceptor di trasformazione custom
In tema di implementazione di un endpoint, Spring-WS fornisce la possibilità di creare una classe che estende PayloadTransformingInterceptor dove eventualmente codificare la trasformazione del messaggio SOAP scambiato o gestire operazioni prima che la request venga inviata alla classe di endpoint.
Analogamente a quanto fatto per la PayloadTransformingInterceptor, indichiamo questa classe custom nel bean che definisce l’endpoint mapping nel file di configurazione di Spring-WS.
Figura 5 – Definizione di un endpoint custom.
Nel fragment di codice riportato di seguito, troviamo l’esempio più semplice di implementazione di questa classe, dove, nei metodi che eseguono l’overload dei metodi handleRequest e handleResponse, vengono richiamati i metodi che effettuano la trasformazione XSLT nella classe estesa.
publicclass TransformazioneEsplicitaVecchiMessaggiDOM extends PayloadTransformingInterceptor { publicboolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception { returnsuper.handleRequest(messageContext, endpoint); } publicboolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception { returnsuper.handleResponse(messageContext, endpoint); }
I metodi handleRequest e handleResponse, ottenendo in input le variabili messageContext ed endpoint, quando ritornano true informano il framework circa la prosecuzione dell’elaborazione, mentre con false interrompono la catena di chiamate verso l’endpoint.
messageContext
L’interfaccia MessageContext, implementata dalla classe org.springframework.ws.context.DefaultMessageContext, rappresenta il contenitore del messaggio web contenente sia la request che la response sotto forma di SaajSoapMessage.
Figura 6 – Variabile messageContext.
SaajSoapMessage è l’implementazione specifica di SAAJ dell’interfaccia SoapMessage. Creata attraverso una SaajSoapMessageFactory rappresenta una classe wrapper di SOAPMessage che rappresenta a sua volta una astrazione per i messaggi SOAP.
Un oggetto SOAPMessage consiste di un fragment SOAP e uno o più attachment opzionali. Ogni SOAPMessage contiene di default gli oggetti:
- SOAPPart
- SOAPEnvelope
- SOAPBody
- SOAPHeader
in corrispondenza dei quali si ottengono gli oggetti Part, Envelope, Body e Header
SOAPPart sp = message.getSOAPPart(); SOAPEnvelope se = sp.getEnvelope(); SOAPBody sb = se.getBody(); SOAPHeader sh = se.getHeader();
Sarà quindi possibile accedere alla request attraverso l’interfaccia WebServiceMessage che rappresenta un messaggio XML protocol-agnostic.
WebServiceMessage messageRequest = messageContext.getRequest();
endpoint
Questa variabile definisce la classe che implementa l’endpoint del servizio e il tipo di marshalling dichiarato nel file di configurazione di Spring-WS.
Figura 7 – Variabile endpoint.
Una interessante implementazione di questo tipo è quella descritta nell’articolo [1], dove a fronte dello stesso endpoint viene presentata una trasformazione da RCP-encoded a document-literal analizzando l’encoding style del payload.
Senza annoiare il lettore, che rimandiamo al sorgente allegato dove troverà la definizione di un interceptor custom in cui viene codificato attraverso DOM l’adeguamento della request alla nuova interfaccia, riportiamo di seguito il fragment di implementazione che ottiene un oggetto Document delle api DOM a partire dalla variabile messageContext di input.
//***MESSAGGIO WEB CHE CONTIENE REQUEST E RESPONSE WebServiceMessage message = messageContext.getRequest(); Transformer transformerSource = createTransformer(); //***OTTENIAMO UN DOMResult DOMResult domResult = new DOMResult(); transformerSource.transform(message.getPayloadSource(), domResult); Document doc = null; doc = (Document)domResult.getNode();
Conclusioni
Spesso il rischio di potersi trovare di fronte a scenari evolutivi non prevedibili rappresenta l’ostacolo all’introduzione nello sviluppo di un framework che, con il mutare delle esigenze, potrebbe rivelarsi restrittivo. In questa serie di articoli abbiamo introdotto Spring-WS con l’obiettivo di agevolare lo sviluppo contract-first di un Web Service, scoprendone poi alcune caratteristiche di flessibilità ed estendibilità. Abbiamo visto come sia possibile implementare il parser dell’XML attraverso le diverse librerie della famiglia DOM (W3C DOM, JDOM, dom4j) o con le meglio performanti SAX o StAX, concludendo con le tecniche di marshalling dell’XML, attraverso l’integrazione di JAXB, Castor, XMLBeans, JiBX o XStream, che eliminano l’effort della codifica. Aspetto che assume ancor più significato in congiunzione alla definizione degli interceptor che eseguono la validazione dell’XML scambiato e lo tracciano su file di log.
Abbiamo anche avuto modo di evidenziare come sia possibile gestire versioni diverse dell’interfaccia dello stesso servizio attraverso gli interceptor di trasformazione XSLT. Naturalmente l’overhead computazionale introdotto, direttamente proporzionale alla complessità dell’XML, potrebbe non essere trascurabile e rivelarsi critico. In questo senso potremmo mitigare questo effetto attraverso l’implementazione di un interceptor di trasformazione custom, adeguando l’XML in modo più performante, per esempio attraverso le API STAX, perdendo però di generalizzazione rispetto a una trasformazione XSLT.
Riferimenti
[1] Tareq Abedrabbo, “Migrating Smoothly from rpc/encoded to document/literal Web Services with Spring-WS”
[2] Log4J
[3] SAAJ
[4] W3C DOM
[5] JDOM
[6] Dom4j
[7] SAX
[8] StAX
[9] JAXB
[10] Castor
[11] XMLBeans
[12] JiBX
[13] XStream