Servicios Web SOAP con JAX-WS, Spring y CXF (IV): Handlers

Última actualización: 03/03/2019

logo java

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.

  1. Servidor
  2. Clientes
  3. Securización TLS + BASIC
  4. 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.

JAX-WS SOAPHandler

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.

jax-ws logical handler

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.

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.