Premessa

Continuiamo la nostra serie di articoli su Spring-WS, illustrando alcune caratteristiche di questo framework che ci permettono di rendere più agevole e flessibile la pubblicazione di un Web Service senza dover rinunciare all'implementazione di tipo contract-first document-based.

Vedremo come, attraverso l'integrazione con un framework di XOM (Xml Object Mapping), si potrà ottenere il payload XML sotto forma di oggetto Java, abbattendo l'effort relativo alla codifica del parser, attività non trascurabile quando la definizione del servizio risulta complessa. Inoltre, attraverso la definizione di specifici interceptor, il framework fornisce, in maniera dichiarativa e senza codificare nulla, funzionalità di scrittura su file di log dell'XML scambiato e di validazione della richiesta e risposta.

Spring-WS: endpoint del servizio e routing del payload

Nelle parti precedenti abbiamo visto come, attraverso Spring-WS, sia possibile pubblicare un Web Service a partire dalla definizione dello schema XML, dall'implementazione della classe di endpoint e di quella che ne realizza la logica applicativa.

I consumatori dei servizi pubblicati invieranno le richieste SOAP all'URL della MessageDispatcherServlet che, pubblicata dall'istanza di Spring-WS in una web application, rappresenta l'entry point di tutti i Web Service pubblicati nel contesto di Spring-WS.

Nel nostro esempio l'entry point dell'istanza di Spring-WS definito è all'URL http://localhost:9081/ECommSpringWS/servizi, così come definita nel descrittore di distribuzione di cui riportiamo un fragment di seguito.

Figura 1 - Definizione della MessageDispatcherServlet di Spring-WS nel descrittore di distribuzione
Figura 1 - Definizione della MessageDispatcherServlet di Spring-WS nel descrittore di distribuzione

Spring-WS inoltrerà quindi le richieste arrivate alla MessageDispatcher verso la classe che implementa l'endpoint definito per il namespace della busta SOAP del messaggio di request, in altre parole il PayloadRootQNameEndpointMapping eseguito sulla base della corrispondenza del nome qualificato del root element del payload XML in request.

Di seguito riportiamo i fragment XML definiti nel file di contesto di Spring-WS relativi a queste definizioni e una request di esempio.

Figura 2 - Mapping tra namespace della request e bean che definisce l'endpoint
Figura 2 - Mapping tra namespace della request e bean che definisce l'endpoint
Figura 3 - Definizione della classe di implementazione dell'endpoint
Figura 3 - Definizione della classe di implementazione dell'endpoint
Figura 4 - Messaggio SOAP di request del client
Figura 4 - Messaggio SOAP di request del client

Nel nostro esempio abbiamo definito nella busta SOAP del messaggio XML il namespace xmlns:ecom="http://www.luigibennardis.it/Ecommerce.Acquisto.ws.types" che, indicato nel bean che definisce il mapping, viene associato alla classe che implementa l'endpoint.

Spring-WS: classi di endpoint

Un endpoint di Spring-WS rappresenta la classe Java il cui compito è quello di processare il messaggio XML in request e di produrre la corrispondente response. Spring WS fornisce l'implementazione di diverse classi di endpoint in relazione alle modalità con cui viene gestito l'XML di input/output. Abbiamo visto nelle parti precedenti un esempio di implementazione con AbstractDomPayloadEndpoint, che utilizza le API W3C DOM. In maniera del tutto analoga, ma utilizzando le API JDOM, si può usare la classe AbstractJDomPayloadEndpoint.

Attraverso la classe di endpoint AbstractXomPayloadEndpoint è possibile integrare un framework di XOM (Object XML Mapping) attraverso cui effettuare l'un-marshalling, o deserializzazione, della request XML in un oggetto e il marshalling, o serializzazione, dell'oggetto nella response XML. In questo caso la classe di endpoint dovrà implementare l'interfaccia AbstractMarshallingPayloadEndpoint.

Tutte le interfacce di endpoint implementano un metodo invokeInternal con firma diversa a seconda della API di riferimento: nel caso dell'AbstractMarshallingPayloadEndpoint, nella firma del metodo invokeInternal, troviamo il generico Java Object corrispondente all'XML serializzato/deserializzato della Request/Response.

Di seguito riportiamo le diverse firme del metodo invokeInternal.

Classe di implementazione che estende AbstractMarshallingPayloadEndpoint:

public class AcquistoEndPointMarshaller extends AbstractMarshallingPayloadEndpoint {
    protected Object invokeInternal(Object objRequest) throws Exception {
        ...
    }
}

Classe di implementazione che estende AbstractDomPayloadEndpoint:

public class AcquistoEndPoint extends AbstractDomPayloadEndpoint {
    protected org.w3c.dom.Element invokeInternal(
        org.w3c.dom.Element acquistoRequest,
        org.w3c.dom.Document responseDocument) throws Exception {
        ...
    }
}

Classe di implementazione che estende AbstractJDomPayloadEndpoint:

public class AcquistoEndPointJDOM extends AbstractJDomPayloadEndpoint {
    protected org.jdom.Element invokeInternal(
        org.jdom.Element elemRequest) throws Exception {
        ...
    }
}

Diversamente dalle altre due implementazioni, per cui è necessario codificare il parser dell'XML, l'endpoint AbstractMarshallingPayloadEndpoint ci permette, attraverso l'integrazione di un framework di XOM, di ottenere il payload XML in request e response in termini di una classe Java.

Implementazione dell'endpoint AbstractMarshallingPayloadEndpoint

Generazione dei JAX Bean

Per implementare un endpoint di tipo AbstractMarshallingPayloadEndpoint è necessario integrare in Spring-WS un framework di Object XML Mapping. Nel nostro esempio abbiamo utilizzato JAXB, attraverso il quale verranno generate le classi Java corrispondenti allo schema XML che definisce l'interfaccia del servizio.

Riportiamo di seguito il task di Ant attraverso il quale vengono generati i JAX Bean corrispondenti allo schema XSD del servizio pubblicato al percorso WEB-INF/xsd/EcommerceAcquistoTypes.xsd.

Figura 5 - Task di Ant di generazione dei JAX Bean
Figura 5 - Task di Ant di generazione dei JAX Bean

Dopo l'esecuzione del task di Ant, aggiornando il workspace, troveremo le classi JAXB nel package definito nello stesso task. Ecco l'interfaccia:

Figura 6 - Rappresentazione dell'interfaccia del servizio in termini di JAX Bean
Figura 6 - Rappresentazione dell'interfaccia del servizio in termini di JAX Bean

La figura 7 mostra il diagramma di classe della response generata dal servizio Spring-WS.

Figura 7 - Class diagram della response
Figura 7 - Class diagram della response

La figura 8 mostra invece il corrispondente diagramma di classe della request del servizio.

Figura 8 - Class diagram della request
Figura 8 - Class diagram della request

Esaminando i class diagram di request e response possiamo notare come la classe ObjectFactory di JAXB si occupa della serializzazione della classe Java nell'XML e deserializzazione da quest'ultimo alla rappresentazione in termini di Java Object.

Definizione dell'endpoint

Di seguito riportiamo i fragment che nel file di contesto di Spring-WS definiscono questo endpoint. Per questo tipo di endpoint indicheremo per marshaller Jaxb2Marshaller, lo schema e le classi JAXB di request e response verso cui deserializzare l'XML.

Figura 9 - Mapping tra namespace della busta SOAP e definizione dell'endpoint
Figura 9 - Mapping tra namespace della busta SOAP e definizione dell'endpoint
Figura 10 - Definizione dell'endpoint e del marshaller
Figura 10 - Definizione dell'endpoint e del marshaller

Definiti correttamente i bean, passiamo all'implementazione Java dell'endpoint. Di seguito ne riportiamo una versione estremamente semplificata, dove non vengono controllate le eccezioni rilanciate dai metodi che implementano il servizio, per evidenziare quanto sia più agevole la codifica rispetto alla versione che implementa l'end-point AbstractDomPayloadEndpoint, presentata nella parte II di questa serie, dove è necessario codificare il parsing dell'XML.

protected Object invokeInternal(Object req) throws Exception {
AcquistoOnLineRequest request = (AcquistoOnLineRequest)req;
AcquistoOnLineResponse response = new AcquistoOnLineResponse();
try{

//*** INVOCAZIONE DELLA LOGICA DEL SERVIZIO
String codicePrenotazione = acquistoServiceImpl.verificaDisponibilitaProdotti(
request.getAcquisto().getCodiceProdotto(),
request.getAcquisto().getQuantita());
String codiceAutorizzazioneCarta = acquistoServiceImpl.eseguiPagamento(
codicePrenotazione,
request.getPagamento().getNumeroCarta(),
request.getPagamento().getScadenza(),
request.getPagamento().getIntestazione());
String codiceAcquisto = acquistoServiceImpl.processaOrdine(
codicePrenotazione, request.getIndirizzo().getNome().concat(" ").concat(
request.getIndirizzo().getCognome()),
request.getIndirizzo().getIndirizzo(),
request.getIndirizzo().getCap(),
request.getIndirizzo().getCitta() );
//***CREAZIONE DELLA RESPONSE
AcquistoRispostaType risposta = new AcquistoRispostaType();
risposta.setCodiceAcquisto(codiceAcquisto);
risposta.setCodiceOutorizzazioneCarta(codiceAutorizzazioneCarta);
risposta.setDataAcquisto(request.getAcquisto().getDataAcquisto());
response.setAcquistoRisposta(risposta);
return response;
}catch (Exception ex){
//***GESTIONE DELLE ECCEZIONI
}
}

Definizione degli interceptor di validation e di logging

Spring-WS estende la definizione dell'AbstractMarshallingPayloadEndpoint a un interceptor di validazione, l'AbstractValidatingMarshallingPayloadEndpoint, attraverso il quale è possibile validare il payload deserializzato dalla request e quello serializzato nella response rispetto allo schema XSD del servizio. Dalla definizione di questo interceptor otteniamo dal framework la validazione dell'XML scambiato senza dover codificare e mantenere il codice relativo ai controlli di validità. Ne deriva una implementazione dell'endpoint orientata esclusivamente alla sue funzioni core, limitandone le chiamate alle sole request valide. Sempre attraverso la definizione di altri due interceptor, il PayloadLoggingInterceptor e il SoapEnvelopeLoggingInterceptor, e con l'integrazione di Log4J, sarà possibile scrivere su un file di log il payload XML di request e response o l'intera busta SOAP. Anche in questo caso otterremo queste funzionalità senza scrivere codice, gestendone l'abilitazione in maniera dichiarativa. Di seguito riportiamo il fragment che, nel file di contesto di Spring-WS, definisce gli interceptor di logging e validation nel bean del mapping del payload.

Figura 11 - Definizione degli interceptor di validation e logging
Figura 11 - Definizione degli interceptor di validation e logging

Nei bean che specificano l'interceptor di validation si dovrà indicare lo schema rispetto al quale viene validato il messaggio e le due proprietà che rispettivamente attivano la validazione della request e della response.

Figura 12 - Definizione delle proprietà dell'interceptor di validation
Figura 12 - Definizione delle proprietà dell'interceptor di validation

Gli interceptor di logging dispongono di due proprietà che abilitano la scrittura sul file di log dell'XML di request e di response.

Figura 13 - Definizione delle proprietà degli interceptor di logging
Figura 13 - Definizione delle proprietà degli interceptor di logging

Riportiamo di seguito la configurazione di Log4j definita nell'esempio allegato (menu in alto a sinistra), dove nella cartella LogEsempio sono riportati i log di esempio per i due interceptor.

log4j.logger.org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor=DEBUG,filePayloadLog
log4j.appender.filePayloadLog=org.apache.log4j.RollingFileAppender
log4j.appender.filePayloadLog.MaxFileSize=1000KB
log4j.appender.filePayloadLog.MaxBackupIndex=10
log4j.appender.filePayloadLog.File=logs//acquistoPayloadLog.log
log4j.appender.filePayloadLog.layout=org.apache.log4j.PatternLayout
log4j.appender.filePayloadLog.layout.ConversionPattern=%d %t %p %c - %m%n
log4j.logger.org.springframework.ws.soap.server.endpoint.interceptor.SoapEnvelopeLoggingInterceptor=DEBUG,fileSoapEnvelopeLog
log4j.appender.fileSoapEnvelopeLog=org.apache.log4j.RollingFileAppender
log4j.appender.fileSoapEnvelopeLog.MaxFileSize=1000KB
log4j.appender.fileSoapEnvelopeLog.MaxBackupIndex=10
log4j.appender.fileSoapEnvelopeLog.File=logs//acquistoSoapEnvelopeLog.log
log4j.appender.fileSoapEnvelopeLog.layout=org.apache.log4j.PatternLayout
log4j.appender.fileSoapEnvelopeLog.layout.ConversionPattern=%d %t %p %c - %m%n

Di seguito la busta SOAP di response inviata al client da Spring-WS quando in request è stato valorizzato un campo di tipo data non valido.

Figura 14 - Busta SOAP di response in caso di valorizzazione errata di un campo data
Figura 14 - Busta SOAP di response in caso di valorizzazione errata di un campo data

Conclusioni

Ci fermiamo qui, rimandando la conclusione al prossimo articolo, in cui analizzeremo come sia possibile gestire versioni multiple dello stesso servizio definendo un interceptor che applica, al payload XML, una trasformazione XSLT. Crediamo però che con gli articoli visti finora, il lettore si sia già potuto rendere conto di come, di fronte a scenari evolutivi non prevedibili, Spring-WS non è un framework restrittivo, ma consente soluzioni flessibili e valide

Riferimenti

  • Tareq Abedrabbo's, "Migrating Smoothly from rpc/encoded to document/literal Web Services with Spring-WS": http://www.cafebabe.me/2007/03/migrating-smoothly-from-rpcencoded-to.html
  • Log4J: http://logging.apache.org/
  • SAAJ: https://saaj.dev.java.net/
  • W3C DOM: http://www.w3.org/DOM/
  • JDOM: http://www.jdom.org/
  • Dom4j: http://www.dom4j.org/
  • SAX: http://www.saxproject.org/
  • StAX: http://stax.codehaus.org/Home
  • JAXB: https://jaxb.dev.java.net/
  • Castor: http://www.castor.org/
  • XMLBeans: http://xmlbeans.apache.org/
  • JiBX: http://jibx.sourceforge.net/
  • XStream: http://xstream.codehaus.org/