Es posible que en alguna ocasión nos veamos en la necesidad de realizar algún tipo de operación directamente con el mensaje SOAP que recibe y/o envía nuestro servicio web. Esta tarea puede realizarse con el mecanismo de Handlers («manejadores») definidos en la especificación JAX-WS y que permite «interceptar» las peticiones y respuestas SOAP en un método que implementamos. Este mecanismo puede ser aplicado tanto al servidor como a los clientes.
Existen dos tipos de handlers:
- SOAP handler : Permite acceder a todo el mensaje SOAP completo, incluyendo los headers de la petición. Por este motivo también se denominan Protocol Handler
- Logical handler: Sólo proporciona acceso al contenido (payload) del mensaje.
CXF incluye un mecanismo de interceptores de mensajes SOAP que ofrece una funcionalidad similar a los handlers especificados en JAX-WS y que permite que implementemos nuestros propios interceptores (de hecho internamente CXF implementa los handlers de JAX-WS mediante interceptores). En esta cuarta parte del tutorial nos vamos a ceñir exclusivamente al estándar JAX-WS.
- Servidor
- Clientes
- Securización TLS + BASIC
- Handlers
Implementación de un SOAP handler
Para implementar un SOAP handler crearemos una clase que implemente javax.xml.ws.handler.soap.SOAPHandler. Implementaremos el método handleMessage en el que recibimos un objeto de tipo SOAPMessageContext que nos dará acceso al mensaje completo SOAP.
Vamos añadir al proyecto de ejemplo un SOAPHandler como el que sigue. Se detecta si el mensaje SOAP es de entrada o de salida, si es de entrada se añade un parámetro al header, y se imprime el mensaje completo en el log.
package com.danielme.demo.jaxws.cxf.ws.handler; import java.io.ByteArrayOutputStream; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPHeaderElement; import javax.xml.soap.SOAPMessage; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.SOAPHandler; import javax.xml.ws.handler.soap.SOAPMessageContext; import org.apache.log4j.Logger; public class CustomSOAPHandler implements SOAPHandler<SOAPMessageContext> { private static final Logger LOG = Logger.getLogger(CustomSOAPHandler.class); @Override public boolean handleMessage(SOAPMessageContext soapMessageContext) { LOG.info("========= handleMessage ========= "); Boolean outboundProperty = (Boolean) soapMessageContext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); SOAPMessage message = soapMessageContext.getMessage(); if (outboundProperty) { LOG.info("mensaje de salida"); } else { LOG.info("mensaje de entrada"); // añadir un parámetro al header QName qname = new QName("http://ws.cxf.jaxws.demo.danielme.com/", "ejemplo"); try { SOAPHeaderElement soapHeaderElement = message.getSOAPHeader().addHeaderElement(qname); soapHeaderElement.addTextNode("valor"); message.saveChanges(); } catch (SOAPException ex) { LOG.error("error añadiendo cabecera", ex); } } ByteArrayOutputStream out = new ByteArrayOutputStream(); try { message.writeTo(out); LOG.info("SOAP\n" + new String(out.toByteArray(), "UTF-8")); } catch (Exception ex) { LOG.error("error imprimiendo mensaje SOAP", ex); } LOG.info("======= fin handleMessage ========= "); // se devuelve true para continuar con la ejecución de los siguientes // handler return true; } @Override public boolean handleFault(SOAPMessageContext context) { return true; } @Override public void close(MessageContext context) { // nothing here } @Override public Set<QName> getHeaders() { return null; } }
En el applicationContext.xml se añaden los handlers a los servicios. Podemos añadir todos los handlers que sean necesarios y se ejecutarán en el orden en que se definan excepto si en alguno de ellos se «rompe» la cadena de ejecución, por ejemplo porque detectamos un error y queremos abortar devolviendo false en el método handleMessage.
<jaxws:endpoint id="teamServiceWS" implementor="#teamService" address="/v/1/teamService"> <jaxws:handlers> <bean id="customSOAPHandler" class="com.danielme.demo.jaxws.cxf.ws.handler.CustomSOAPHandler"/> </jaxws:handlers> </jaxws:endpoint>
Esta configuración es válida tanto para el servidor como para el cliente con Spring. Para el cliente sin Spring los handlers se pueden añadir de forma programática creando un listado:
JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean(); jaxWsProxyFactoryBean.setServiceClass(ITeamService.class); jaxWsProxyFactoryBean.setAddress(properties.getProperty("endpoint")); List<Handler> handlers = new LinkedList<Handler>(); handlers.add(new CustomSOAPHandler()); jaxWsProxyFactoryBean.setHandlers(handlers ); ITeamService teamServiceClient = (ITeamService) jaxWsProxyFactoryBean.create();
Implementación de un Logical handler
La implementación de un Logical handler es análoga a la de un SOAP handler, la única diferencia es que en este caso la clase a implementar será de tipo javax.xml.ws.handler.LogicalHandler.
El siguiente handler imprime el payload o cuerpo del mensaje SOAP.
package com.danielme.demo.jaxws.cxf.ws.handler; import java.io.ByteArrayOutputStream; import javax.xml.transform.OutputKeys; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.ws.LogicalMessage; import javax.xml.ws.handler.LogicalHandler; import javax.xml.ws.handler.LogicalMessageContext; import javax.xml.ws.handler.MessageContext; import org.apache.log4j.Logger; public class CustomLogicalHandler implements LogicalHandler<LogicalMessageContext> { private static final Logger logger = Logger.getLogger(CustomLogicalHandler.class); @Override public boolean handleMessage(LogicalMessageContext logicalMessageContext) { logger.info("========= handleMessage ========= "); Boolean outboundProperty = (Boolean) logicalMessageContext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); if (outboundProperty) { logger.info("mensaje de salida"); } else { logger.info("mensaje de entrada"); } LogicalMessage logicalMessage = logicalMessageContext.getMessage(); Source payload = logicalMessage.getPayload(); //imprimir el payload try { TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); ByteArrayOutputStream out = new ByteArrayOutputStream(); Result result = new StreamResult(out); transformer.transform(payload, result); logger.info("SOAP\n" + new String(out.toByteArray(), "UTF-8")); } catch (Exception ex) { logger.error("error procesando xml de payload", ex); } return true; } @Override public boolean handleFault(LogicalMessageContext context) { return true; } @Override public void close(MessageContext context) { // nothing here } }
Se registra en el cliente y/o servidor de igual forma que el SOAP handler anterior.
Código de ejemplo
El código de ejemplo del tutorial completo se encuentra en GitHub Para más información sobre cómo utilizar GitHub, consultar este artículo.