Realizziamo un Web Service con Spring-WS

IV parte: Endpoint del servizio e routing del payloaddi

In questo quarto articolo 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. Inoltre vederemo le funzionalità di scrittura su file di log dell‘XML scambiato e di validazione della richiesta e risposta.

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.

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 3 - Definizione della classe di implementazione dell'endpoint.

 

 

 

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 dellAbstractMarshallingPayloadEndpoint, 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.

 

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.

 

 

 

 

In figura 7 il diagramma di classe della response.

 

 

Figura 7 - Class diagram della response.

 

E in figura 8, quello 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 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.

 

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.

 

 

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.

 

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.

 

 

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, "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/

 

 

 

Condividi

Pubblicato nel numero
151 maggio 2010
Luigi Bennardis si è laureato in Scienze Statistiche ed Economiche all’università di Roma e si occupa di informatica da diversi anni. Dopo una lunga esperienza di system integration su piattaforma Java EE e Microsoft in una importante società di consulenza, attualmente si occupa di progettazione e sviluppo su tecnologie distribuite…
Articoli nella stessa serie
Ti potrebbe interessare anche