CXF SoapFault service details error

Cuando sucede un error en un servicio web. Se devuelve un mensaje SoapFault. Indicando codigo y error. Pero no tenemos traza del error. Si quieres que tu servicio devuelva tambien la traza de error en el mensaje SoapFault debes indicarlo asi:

	<jaxws:endpoint address="/consultarIdentidad" id="mockConsulta" 
		wsdlLocation="wsdl/ConsultaIdentidad.wsdl"
		implementor="es.depontevedra.soap.interoperabilidad.identidad.services.paxase.ConsultaIdentidad">
		<jaxws:properties>
			<entry key="exceptionMessageCauseEnabled" value="true" />
			<entry key="faultStackTraceEnabled" value="true" />
		</jaxws:properties>
	</jaxws:endpoint>

faultStackTraceEnabled: es la parte stack trace de la excepcion.
exceptionMessageCauseEnabled: es la parte Caused by:…

Bibliografia

CXF SoapFault service details error

CXF configurar bus cliente, servidor en un mismo servicio

Tengo un servicio que hace de proxy. La peticion que le llega la envia a otro Servicio Web y este otro devuelve la respuesta.

Por un lado tengo un modulo: cliente-servicio_externo.
Por otro lado tengo el modulo: servidor-proxy.

El cliente es una dependencia del servidor-proxy, para que mediante la configuracion de Spring, llamar al servicio externo.

	<cxf:bus bus="clientPaxaseBus">
		<cxf:features>
			<cxf:logging />
		</cxf:features>
		<cxf:inInterceptors>
			<ref bean="checkResponse" />
			<bean class="org.apache.cxf.ws.security.wss4j.DefaultCryptoCoverageChecker" />
		</cxf:inInterceptors>
		<cxf:outInterceptors>
			<ref bean="SignRequest" />
		</cxf:outInterceptors>
	</cxf:bus>
		
	<jaxws:client id="clientPaxaseConsultaIdentidad" address="#{url}/consultarIdentidad" bus="clientPaxaseBus"
		serviceClass="es.map.xml_schemas.PeticionPortType"/>

Pongo una configuracion de BUS generica, para todos los clientes. Especifico el nombre del bus: bus=”clientPaxaseBus”.

	<bean id="logInbound" class="org.apache.cxf.interceptor.LoggingInInterceptor" />
	<bean id="logOutbound" class="org.apache.cxf.interceptor.LoggingOutInterceptor" />

	<bean id="cdi" class="es.depontevedra.soap.interoperabilidad.identidad.services.spi.ConsultaIdentidadEnPaxase" />
	<jaxws:endpoint id="cdiService" implementor="#cdi" address="/consultaidentidad"
		publishedEndpointUrl="http://${service.host}:${service.port}/${service.wsname}/consultaidentidad">
		<jaxws:inFaultInterceptors>
			<ref bean="logInbound" />
		</jaxws:inFaultInterceptors>
		<jaxws:inInterceptors>
			<ref bean="logInbound" />
		</jaxws:inInterceptors>
		<jaxws:outFaultInterceptors>
			<ref bean="logOutbound" />
		</jaxws:outFaultInterceptors>
		<jaxws:outInterceptors>
			<ref bean="logOutbound" />
		</jaxws:outInterceptors>
	</jaxws:endpoint>

Evito la configuracion bus, poniendo los interceptors a mano en cada endpoint. No he sido capaz de generar un cxf:bus generico nombrando o etiquetandolo de alguna manera. Esta solucion no me gusta, pero funciona. A ver si encuentro una manera mas limpia…

CXF configurar bus cliente, servidor en un mismo servicio

Crear un Handler para Web Services en Apache CXF

Los handler que son propios de los Web Services, no son la forma mas adecuada para Apache CXF. La mejor forma, por que te da muchas mas opciones, son los interceptors. Pero como vamos a ver a continuación, no hay ningún problema en añadir un handler a un Web Service creado con Apache CXF.

Lo primero de todo es crear el Handler. Queremos mostrar por el log, los mensajes de entrada y salida.

package mi.paquete.ws.cxf.handler;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Set;

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPException;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

@SuppressWarnings("restriction")
public class LogHandler implements SOAPHandler<SOAPMessageContext> {

	private static final Logger LOGGER = LoggerFactory.getLogger(LogHandler.class);
	
	public boolean handleMessage(SOAPMessageContext mc) {
		logMessage(mc);
		return true;
	}

	public boolean handleFault(SOAPMessageContext context) {
		return true;
	}

	public void close(MessageContext context) {
	}

	public Set<QName> getHeaders() {
		return null;
	}

	private Boolean isResponse(SOAPMessageContext mc) {
		Boolean isReponse = (Boolean) mc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
		return isReponse;
	}

	private void logMessage(SOAPMessageContext mc){
		boolean isResponse = isResponse(mc);
		
		String type = isResponse ? "XML RESPONSE : " : "XML REQUEST: ";
		String strMessage = "";
		
		try {
			strMessage = messageToString(mc.getMessage());
		} catch (SOAPException e) {
			LOGGER.warn("SOAPException: ", e);
		} catch (IOException e) {
			LOGGER.warn("IOException: ", e);
		}
				
		LOGGER.info("WS "+ type + strMessage);
	}
	
	private String  messageToString(final SOAPMessage message)
			throws SOAPException, IOException {

		ByteArrayOutputStream out = new ByteArrayOutputStream();
		message.writeTo(out);
		return out.toString();
	}

}

Ahora simplemente configuramos cxf para que ejecute el handle a su momento.
Hay varias maneras de hacerlo:
1 – con un fichero handler.xml ubicado:
1.1 – en la siguiente ruta: /src/main/resources/mi/paquete/ws/cxf/spi/handler.xml, asi en la creacion del jar el fichero handler.xml estará en la misma carpeta que la clase que lo llama. La anotacion en la clase que implementa el web service (la clase que lo llama) seria @HandlerChain(handler.xml)
1.2 – o en una ruta cualquiera ya que la anotacion tiene una ruta absoluta: @HandlerChain(file=”../../common/handlers/myhandlers.xml”).
2 – en la configuracion de cxf (fichero applicationContext.xml de spring), que a todas luces no es nada intrusiva y por configuracion indicas si pones un handler dos, tres o ninguno.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
	xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:cxf="http://cxf.apache.org/core"
	xsi:schemaLocation="
	http://cxf.apache.org/core 
	http://cxf.apache.org/schemas/core.xsd
	http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://cxf.apache.org/jaxws 
	http://cxf.apache.org/schemas/jaxws.xsd">

	<import resource="classpath:META-INF/cxf/cxf.xml" />
	<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

	<context:annotation-config />
	<context:component-scan base-package="mi.paquete.ws.cxf" />

	<bean id="busqueda" class="mi.paquete.ws.cxf.spi.WebServiceImpl" />

	<jaxws:endpoint id="busquedaService" implementor="#busqueda" address="/busqueda">
		<jaxws:handlers>
			<bean class="mi.paquete.ws.cxf.handler.LogHandle" />
		</jaxws:handlers>
	</jaxws:endpoint>
	
</beans>

LIMITACIONES: si hay un error, una excepcion, el mensaje de salida no se loguea, es null. Supongo que para los handler hay fases de cuando quieres que se ejecute. Pero encontre los interceptors de CXF y no busque mas informacion sobre los handlers.

VENTAJAS: Los interceptors tienen la limitacion que solo se pueden usar con CXF, no se pueden llevar a jax-ws. Mientras que los handlers son extrapolables a todos los webservices.

Crear un Handler para Web Services en Apache CXF

Graba en bbdd la entrada y salida en el log del Web Service con Apache CXF

En referencia al articulo anterior: Mostrar la entrada y salida en el log del Web Service con Apache CXF.

Ahora lo que quiero es grabarlo en la bbdd.

Apache CXF en su modulo cxf-rt-management tiene un paquete denominado: org.apache.cxf.management.persistence. Vamos utilizar esto, que ya esta casi hecho para grabar en nuestra bbdd.

En el paquete vemos las siguientes clases:
ExchangeData: es un bean que contiene toda la informacion sobre la peticion y la respuesta. ¡Ya podian haber implementado un toString para cuando debugeas!.
ExchangeDataDAO: la interfaz que se llama para persistir.
ExchangeDataFilter: la interfaz que se llama para saber si se puede persistir. Es un filtro.
ExchangeDataProperty: el bean para las propiedades de los mensajes de entrada-salida. Esta clase se utiliza en el bean ExchangeData.
FilesystemExchangeDataDAO: implementación de la interfaz del DAO que graba en un fichero. Si no se le indica la ruta, lo hace en una carpeta temporal. En mi caso, en la carpeta /temp de $CATALINA_HOME.
StatusExchangeDataFilter: implementación de la interfaz del filtro. Esta implementación es solo para errores.

¿Como se usa?

A) usando las clases que hemos visto.

Es decir, vamos a grabar a fichero en la carpeta /temp de $CATALINA_HOME, solo los mensajes que den error (excepciones) en nuestro Web Service.
En nuestro fichero de configuración de CXF (un aplicattionContext.xml de spring), ponemos lo siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
	xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:cxf="http://cxf.apache.org/core"
	xsi:schemaLocation="
	http://cxf.apache.org/core 
	http://cxf.apache.org/schemas/core.xsd
	http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://cxf.apache.org/jaxws 
	http://cxf.apache.org/schemas/jaxws.xsd">

	<import resource="classpath:META-INF/cxf/cxf.xml" />
	<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
 
	<cxf:bus>
		<cxf:inInterceptors> <!-- logueamos entrada-salida y la persistimos -->
			<bean class="org.apache.cxf.interceptor.LoggingInInterceptor" />
			<ref bean="persistInInterceptor" />
		</cxf:inInterceptors>

		<cxf:inFaultInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingInInterceptor" />
			<ref bean="persistInInterceptor" />
		</cxf:inFaultInterceptors>

		<cxf:outInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
			<ref bean="persistOutInterceptor" />
		</cxf:outInterceptors>

		<cxf:outFaultInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
			<ref bean="persistOutInterceptor" />
		</cxf:outFaultInterceptors>
	</cxf:bus>

	<jaxws:endpoint id="busquedaService" implementor="#busqueda" address="/busqueda" />

	<bean id="exchangeDAOImpl" class="org.apache.cxf.management.persistence.FilesystemExchangeDataDAO" />

	<bean id="statusExchangeDataFilter" class="org.apache.cxf.management.persistence.StatusExchangeDataFilter" >
		<property name="statusFilter" value="ERROR" /> <!-- Registramos solo los errores, opcion por defecto que no hace falta poner -->
		<!-- <property name="statusFilter" value="OK" /> --> <!-- Tb podemos registrar los aciertos -->
	</bean>
	
	<bean id="persistInInterceptor" class="org.apache.cxf.management.interceptor.PersistInInterceptor" />
	<bean id="persistOutInterceptor" class="org.apache.cxf.management.interceptor.PersistOutInterceptor">
		<property name="exchangeDataDAO" ref="exchangeDAOImpl" />
		<property name="exchangeDataFilter" ref="statusExchangeDataFilter" />
	</bean>
	
</beans>

b) Personalizando el dao y el filtro.

Exactamente lo anterior pero Con nuestra propia implementación de las interfaces.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
	xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:cxf="http://cxf.apache.org/core"
	xsi:schemaLocation="
	http://cxf.apache.org/core 
	http://cxf.apache.org/schemas/core.xsd
	http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://cxf.apache.org/jaxws 
	http://cxf.apache.org/schemas/jaxws.xsd">

	<import resource="classpath:META-INF/cxf/cxf.xml" />
	<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
 
	<cxf:bus>
		<cxf:inInterceptors> <!-- logueamos entrada-salida y la persistimos -->
			<bean class="org.apache.cxf.interceptor.LoggingInInterceptor" />
			<ref bean="persistInInterceptor" />
		</cxf:inInterceptors>

		<cxf:inFaultInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingInInterceptor" />
			<ref bean="persistInInterceptor" />
		</cxf:inFaultInterceptors>

		<cxf:outInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
			<ref bean="persistOutInterceptor" />
		</cxf:outInterceptors>

		<cxf:outFaultInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
			<ref bean="persistOutInterceptor" />
		</cxf:outFaultInterceptors>
	</cxf:bus>

	<jaxws:endpoint id="busquedaService" implementor="#busqueda" address="/busqueda" />

	<bean id="exchangeDAOImpl" class="mi.paquete.cxf.interceptor.persistence.PersistExchangeDataDAO" />

	<bean id="exchangeDataFilter" class="mi.paquete.cxf.interceptor.persistence.NoFiltroNada" />
	
	<bean id="persistInInterceptor" class="org.apache.cxf.management.interceptor.PersistInInterceptor" />
	<bean id="persistOutInterceptor" class="org.apache.cxf.management.interceptor.PersistOutInterceptor">
		<property name="exchangeDataDAO" ref="exchangeDAOImpl" />
		<property name="exchangeDataFilter" ref="exchangeDataFilter" />
	</bean>
	
</beans>

El filtro, el cual queremos que no filtre nada, que persista siempre:

package mi.paquete.cxf.interceptor.persistence;

import org.apache.cxf.management.persistence.ExchangeData;
import org.apache.cxf.management.persistence.ExchangeDataFilter;

public class NoFiltroNada  implements ExchangeDataFilter {

	public boolean shouldPersist(ExchangeData data) {
		if(data == null){
			return false;
		}else{
			return true;
		}
	}

}

Nuestra implementación de como vamos a graba en la base de datos:

package mi.paquete.cxf.interceptor.persistence;

import org.apache.cxf.management.persistence.ExchangeData;
import org.apache.cxf.management.persistence.ExchangeDataDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import mi.paquete.cxf.interceptor.persistence.AuditoriaWsDao ;

public class ExchangeDataDaoImpl implements ExchangeDataDAO{

	@Autowired
	@Qualifier("auditoriaWsDao")
	private AuditoriaWsDao auditDao;
	
	public void save(ExchangeData exchangeData) throws Exception {
		if(exchangeData == null) return;
		
		/* 
		* GUARDAR:
		* (
		*             --descripcion del servicio--
		*   NombreDelServicio, NombreDeLaOperacionLLamada,
		*             --descripcion de la peticion--
		*   fechaPeticion, xmlPeticion, tipoDePeticion, pilaError,
		*             --descripcion de la respuesta--
		*   fechaRespuesta, xmlRespuesta, tipoDeRespuesta, pilaError,
		*  )
		*/
		auditDao.save(exchangeData.getUri(), exchangeData.getOperation(), 
			exchangeData.getInDate(), exchangeData.getRequest(), AuditDao.REQUEST, null,
			exchangeData.getOutDate(), exchangeData.getResponse(), AuditDao.RESPONSE, exchangeData.getStackTrace());
	}

}

la implementación del DAO, de la configurarión de la bbdd, de Hibernate, de Spring Transacctional, etc. no corresponden a este articulo.

Graba en bbdd la entrada y salida en el log del Web Service con Apache CXF

Mostrar la entrada y salida en el log del Web Service con Apache CXF

Tambien te puede interesar: Graba en bbdd la entrada y salida en el log del Web Service con Apache CXF.

Muchas veces te gustaria (deberias) saber cuales son los mensajes de entrada y salida del web service. Tan facil como configurar unos interceptors que vienen en CXF por defecto. En el fichero de configuracion de cxf, que es un applicationContext.xml de Spring.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
	xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:cxf="http://cxf.apache.org/core"
	xsi:schemaLocation="
	http://cxf.apache.org/core 
	http://cxf.apache.org/schemas/core.xsd
	http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://cxf.apache.org/jaxws 
	http://cxf.apache.org/schemas/jaxws.xsd">

	<import resource="classpath:META-INF/cxf/cxf.xml" />
	<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
	
	<context:annotation-config />
	<context:component-scan base-package="es.depontevedra.ws.cxf.cividas.search" />

	<cxf:bus>
	        <cxf:inInterceptors>
	            <bean class="org.apache.cxf.interceptor.LoggingInInterceptor" />
	        </cxf:inInterceptors>
	        
	  	<cxf:inFaultInterceptors>
	            <bean class="org.apache.cxf.interceptor.LoggingInInterceptor" />
	        </cxf:inFaultInterceptors>
	        
	        <cxf:outInterceptors>            
	            <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
	        </cxf:outInterceptors>
	        
	  	<cxf:outFaultInterceptors>           
	             <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
	        </cxf:outFaultInterceptors>
	    </cxf:bus>

	<bean id="busqueda" class="mi.paquete.spi.BusquedaImpl" />
	<jaxws:endpoint id="busquedaService" implementor="#busqueda" address="/busqueda" />
	
</beans>

¿Por que cuatro?. Dos de la entrada-salida normal y dos de la entrada-salida de error. Aunque me pica en la nariz que el cxf:inFaultInterceptors para el log no es necesario. Todavia no he conseguido hacerlo fallar… se me estan ocurriendo nuevos test malignos… Nop, no hay manera no consigo que el mensaje de entrada consiga ir por aqui cxf:inFaultInterceptors.

Mostrar la entrada y salida en el log del Web Service con Apache CXF