javax.xml.soap.DetailEntry to xml

He encontrado documentacion de como crear los mensajes SoapFaul con el detalle como si fuese un XML. Pero ninguno que indique el procedimiento contrario. Tengo un SOAPFault con un xml en el detail y lo quiero serializar.

Este es el mensaje que me llega desde el WebService al que me conecto:

<?xml version='1.0' encoding='UTF-8'?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <soapenv:Body>
                <soapenv:Fault>
                        <faultcode>soapenv:Server</faultcode>
                        <faultstring>0301 - Organismo no autorizado 'P3600000H' 'CDISFWS01'</faultstring>
                        <faultactor>CDISFWS01</faultactor>
                        <detail>
                                <Atributos xmlns="http://www.map.es/scsp/esquemas/V2/soapfaultatributos">
                                        <IdPeticion>1390408700541</IdPeticion>
                                        <NumElementos>1</NumElementos>
                                        <TimeStamp>2014-01-22T17:37:49.601+01:00</TimeStamp>
                                        <Estado>
                                                <CodigoEstado>0301</CodigoEstado>
                                                <CodigoEstadoSecundario />
                                                <LiteralError>Organismo no autorizado 'P3600000H' 'CDISFWS01'</LiteralError>
                                                <TiempoEstimadoRespuesta>0</TiempoEstimadoRespuesta>
                                        </Estado>
                                        <CodigoCertificado>CDISFWS01</CodigoCertificado>
                                </Atributos>
                        </detail>
                </soapenv:Fault>
        </soapenv:Body>
</soapenv:Envelope>

Como lo trato:

	public String realizarConsulta(String datosPeticion) {
		try {
			// llamada al servicio, la parte, en la que va todo bien.
			// ...
		} catch (Exception e) {
			if(e instanceof javax.xml.ws.soap.SOAPFaultException){
				javax.xml.soap.SOAPFault fault = ((javax.xml.ws.soap.SOAPFaultException) e).getFault();
				es.map.scsp.esquemas.v2.soapfaultatributos.Atributos atributos = getSoapfaultatributo(fault);
				if(atributos != null){
					String codigoEstado = atributos.getEstado().getCodigoEstado();
					String literalError = atributos.getEstado().getLiteralError();
					return errorMesagge(codigoEstado, literalError);
				}else{
					String faultCode = fault.getFaultCode();
					String faultString = fault.getFaultString();
					return errorMesagge(faultCode, faultString);					
				}
			}else{
				LOGGER.error("ERROR", e);				
				return errorMesagge(null, e.getMessage());
			}
		}
	}

	@SuppressWarnings("unchecked")
	private es.map.scsp.esquemas.v2.soapfaultatributos.Atributos getSoapfaultatributo(javax.xml.soap.SOAPFault fault) {
		es.map.scsp.esquemas.v2.soapfaultatributos.Atributos atributos = null;
		try {
			javax.xml.soap.Detail detail = fault.getDetail();
			if(detail != null){
				Iterator<DetailEntry> iterator = detail.getDetailEntries();
				while(iterator.hasNext()){
					DetailEntry detailEntry = (DetailEntry) iterator.next();

					Document document = detailEntry.getOwnerDocument(); 
					DOMImplementationLS domImplLS = (DOMImplementationLS) document .getImplementation(); 
					LSSerializer serializer = domImplLS.createLSSerializer(); 
					String str = serializer.writeToString(detailEntry);
					str = str.replace("UTF-16", "UTF-8");
					LOGGER.trace("detailEntry: "+ str);
					
					Object xmlBinderObjeto = xmlBinder.unmarshal(str);

					if(xmlBinderObjeto instanceof es.map.scsp.esquemas.v2.soapfaultatributos.Atributos){
						atributos = (es.map.scsp.esquemas.v2.soapfaultatributos.Atributos) xmlBinderObjeto;
					}
				}
			}
		} catch (Exception e) {
			LOGGER.warn("ERROR al deserializar ATRIBUTOS del SOAPFault. ", e);
		}
		
		return atributos;
	}

Ahora bien, por ejemplo, no tienes el xsd del mensaje o te lo han cambiado si avisar…

	@SuppressWarnings("unchecked")
	@Override
	public String realizarConsulta(String datosPeticion) {
		try {
			// llamada al servicio, la parte, en la que va todo bien.
			// ...
		} catch (Exception e) {
			if(e instanceof javax.xml.ws.soap.SOAPFaultException){
				javax.xml.soap.SOAPFault fault = ((javax.xml.ws.soap.SOAPFaultException) e).getFault();
				String faultCode = fault.getFaultCode();
				String faultString = fault.getFaultString();
				LOGGER.info("faultCode: "+faultCode);
				LOGGER.info("faultString: "+faultString);
				
				javax.xml.soap.Detail detail = fault.getDetail();
				if(detail != null){
					Iterator<DetailEntry> iterator = detail.getDetailEntries();
					while(iterator.hasNext()){
						DetailEntry detailEntry = (DetailEntry) iterator.next();
						String NamespaceUri_soapfaultatributos = "http://www.map.es/scsp/esquemas/V2/soapfaultatributos";
						if(NamespaceUri_soapfaultatributos.equalsIgnoreCase(detailEntry.getElementQName().getNamespaceURI())){
							Iterator<?> it = detailEntry.getChildElements();
							String codigoEstado = null; 
							String literalError = null;
							String idPeticion = null;
							String timeStamp = null;
							while(it.hasNext()){
								Object object = it.next();
								if(object instanceof SOAPElement){
									SOAPElement element = (SOAPElement) object;
									String name = element.getLocalName();
									if("IdPeticion".equalsIgnoreCase(name)){
										idPeticion = element.getValue();
										LOGGER.info("IdPeticion: "+idPeticion);
									}else if("TimeStamp".equalsIgnoreCase(name)){
										timeStamp = element.getValue();
										LOGGER.info("TimeStamp: "+timeStamp);
									}else if("Estado".equalsIgnoreCase(name)){
										Iterator<?> itElements = element.getChildElements();
										while(itElements.hasNext()){
											Object objeto = itElements.next();
											if(objeto instanceof SOAPElement){
												SOAPElement elementHijo = (SOAPElement) objeto;
												String nameHijo = elementHijo.getLocalName();
												if("CodigoEstado".equalsIgnoreCase(nameHijo)){
													codigoEstado = elementHijo.getValue();
													LOGGER.info("CodigoEstado: "+codigoEstado);
												}else if("LiteralError".equalsIgnoreCase(nameHijo)){
													literalError = elementHijo.getValue();
													LOGGER.info("LiteralError: "+literalError);
												}
											}
										}
									}
								}
							}

						}

					}
				}

				return errorMesagge(faultCode, faultString);
			}else{
				LOGGER.error("ERROR", e);				
				return errorMesagge(null, e.getMessage());
			}
		}
	}

Ni decir falta que habria que refactorizar un poco 😉 jeje.

Anuncios
javax.xml.soap.DetailEntry to xml

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 no firmar, encriptar los SoapFault de respuesta

En cxf cuando firmamos, encriptamos… los mensajes SOAP, tanto cliente como servidor, deben de comprobar siempre que la cabecera esta firmada, o encriptada o … Pero que pasa cuando los mensajes SOAPFault no estan firmados, o encriptados o …

Pues que hay un error indicando: org.apache.cxf.binding.soap.SoapFault: No SIGNED element found matching XPath /soapenv:Envelope/soapenv:Body

Como indicar a CXF que no intente comprobar la seguridad de los mensajes que son SoapFault:

	<cxf:bus>
		<cxf:features>
			<cxf:logging />
		</cxf:features>
		<cxf:inInterceptors>
			<ref bean="checkResponse" />
			<bean class="org.apache.cxf.ws.security.wss4j.DefaultCryptoCoverageChecker">
            	<property name="checkFaults" value="false"/>
        	</bean>
		</cxf:inInterceptors>
		<cxf:outInterceptors>
			<ref bean="SignRequest" />
		</cxf:outInterceptors>
	</cxf:bus>

Jira en CXF informando y solucionando este bug.

CXF no firmar, encriptar los SoapFault de respuesta

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

Cliente del “Servicio de Verificación y Consulta de Datos: Plataforma de Intermediación” con CXF

Me ha tocado realizar un cliente para este Servicio Web del Estado (Español).
En los documentos adjuntos, hay mucha informacion relativa a lo que hace y como quiere los datos.
Sobre seguridad no indica nada mas que permite: WS-Security, XMLDSig y Xades. Que se tiene que firmar, pero no indica, que algoritmos hay que usar, como firmar las cabeceras, que partes hay que firmar, etc. Cosa sorprendente cuando ves que hay varias maneras de firmar y de hacer las cosas. Pero te dan una peticion y una respuesta tipo. En base a esa peticion he ido realizando pruebas y puedo afirmar que ya lo tengo.

En CXF recomiendan el uso de WS-Policy siempre y cuando el WSDL contenga estos apartados, pero en este caso tenemos que usar los interceptors de CXF. En el cliente antes de enviar la peticion SOAP, tenemos que firmar el soap:body. Esto se hace en el soap:header, con WS-Security y los Interceptors.

Por lo que he deducido las caracteristicas de WS-Security son:

Dejo mi fichero de configuración de spring para CXF y mi fichero de propiedades:

<?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:util="http://www.springframework.org/schema/util" xmlns:jaxws="http://cxf.apache.org/jaxws"
	xmlns:cxf="http://cxf.apache.org/core" xmlns:http="http://cxf.apache.org/transports/http/configuration"
	xmlns:p="http://cxf.apache.org/policy" xmlns:wsa="http://cxf.apache.org/ws/addressing"
	xmlns:wsp="http://www.w3.org/2006/07/ws-policy" xmlns:sec="http://cxf.apache.org/configuration/security"
	xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
	xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
	xsi:schemaLocation="
	http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
	http://cxf.apache.org/transports/http/configuration
	http://cxf.apache.org/schemas/configuration/http-conf.xsd
	http://cxf.apache.org/configuration/security
	http://cxf.apache.org/schemas/configuration/security.xsd
	http://cxf.apache.org/ws/addressing 
	http://cxf.apache.org/schema/ws/addressing.xs
	http://cxf.apache.org/transports/http/configuration 
	http://cxf.apache.org/schemas/configuration/http-conf.xsd
	http://www.w3.org/2006/07/ws-policy	
	http://www.w3.org/2006/07/ws-policy.xsd
	http://cxf.apache.org/policy 
	http://cxf.apache.org/schemas/policy.xsd
	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*:spring-xsd.xml" />
	<import resource="classpath:META-INF/cxf/cxf.xml" />
	<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

	<http:conduit name="https://localhost:.*">
		<http:tlsClientParameters disableCNCheck="true">
			<!-- <sec:trustManagers> -->
			<!-- <sec:keyStore type="jks" password="cspass" resource="clientKeystore.jks" 
				/> -->
			<!-- </sec:trustManagers> -->
		</http:tlsClientParameters>
	</http:conduit>

	<cxf:bus>
		<cxf:features>
			<cxf:logging />
		</cxf:features>
		<cxf:inInterceptors>
			<ref bean="SignResponse" />
			<bean class="org.apache.cxf.ws.security.wss4j.DefaultCryptoCoverageChecker" />
		</cxf:inInterceptors>
		<cxf:outInterceptors>
			<ref bean="SignRequest" />
		</cxf:outInterceptors>
	</cxf:bus>

	<util:map id="mapKeystorePasswords">
		<entry key="${org.apache.ws.security.crypto.merlin.keystore.alias}" value="${org.apache.ws.security.crypto.merlin.keystore.password}" />
	</util:map>
	
	<bean id="clientPasswordCallback"
		class="es.una.ruta.callback.ClientPasswordCallback" >
		</bean>

	<bean id="url" class="java.lang.String">
		<constructor-arg
			value="https://${paxase.host}:${paxase.port}/${paxase.wsname}" />
	</bean>
	<bean id="clientFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
		<property name="serviceClass" value="es.map.xml_schemas.PeticionPortType" />
		<property name="address" value="#{url}/consultarIdentidad" />
	</bean>
	<bean id="clientConsultaIdentidad" class="es.map.xml_schemas.PeticionPortType"
		factory-bean="clientFactory" factory-method="create" />

	<bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor"
		id="SignRequest">
		<constructor-arg>
			<map>
				<entry
					key="#{T(org.apache.ws.security.handler.WSHandlerConstants).IS_BSP_COMPLIANT}"
					value="#{T(java.lang.Boolean).FALSE.toString()}" />
				<entry
					key="#{T(org.apache.ws.security.handler.WSHandlerConstants).MUST_UNDERSTAND}"
					value="#{T(java.lang.Boolean).FALSE.toString()}" />
				<entry
					key="#{T(org.apache.ws.security.handler.WSHandlerConstants).ACTION}"
					value="#{T(org.apache.ws.security.handler.WSHandlerConstants).SIGNATURE}" />
				<entry
					key="#{T(org.apache.ws.security.handler.WSHandlerConstants).USER}"
					value="${org.apache.ws.security.crypto.merlin.keystore.alias}" />
				<entry
					key="#{T(org.apache.ws.security.handler.WSHandlerConstants).SIG_PROP_FILE}"
					value="client_sign.properties" />
				<entry
					key="#{T(org.apache.ws.security.handler.WSHandlerConstants).SIG_KEY_ID}"
					value="${signature.key.identifier}" />
				<entry
					key="#{T(org.apache.ws.security.handler.WSHandlerConstants).SIG_ALGO}"
					value="#{T(org.apache.ws.security.WSConstants).RSA_SHA1}" />
				<entry
					key="#{T(org.apache.ws.security.handler.WSHandlerConstants).SIG_DIGEST_ALGO}"
					value="#{T(org.apache.ws.security.WSConstants).SHA1}" />
				<entry
					key="#{T(org.apache.ws.security.handler.WSHandlerConstants).SIG_C14N_ALGO}"
					value="#{T(org.apache.ws.security.WSConstants).C14N_EXCL_OMIT_COMMENTS}" />
				<entry
					key="#{T(org.apache.ws.security.handler.WSHandlerConstants).SIGNATURE_PARTS}"
					value="#{'{}{' + T(org.apache.ws.security.WSConstants).URI_SOAP11_ENV + '}' + T(org.apache.ws.security.WSConstants).ELEM_BODY + ';'}" />
               	<entry key="#{T(org.apache.ws.security.handler.WSHandlerConstants).PW_CALLBACK_REF}" 
					value-ref="clientPasswordCallback" />
			</map>
		</constructor-arg>
	</bean>

	<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"
		id="SignResponse">
		<constructor-arg>
			<map>
				<entry
					key="#{T(org.apache.ws.security.handler.WSHandlerConstants).ACTION}"
					value="#{T(org.apache.ws.security.handler.WSHandlerConstants).SIGNATURE}" />
				<entry
					key="#{T(org.apache.ws.security.handler.WSHandlerConstants).SIG_PROP_FILE}"
					value="client_sign.properties" />
				<entry key="#{T(org.apache.ws.security.handler.WSHandlerConstants).PW_CALLBACK_REF}" 
					value-ref="clientPasswordCallback" />
				<entry
					key="#{T(org.apache.ws.security.handler.WSHandlerConstants).SIG_ALGO}"
					value="#{T(org.apache.ws.security.WSConstants).RSA_SHA1}" />
			</map>
		</constructor-arg>
	</bean>

</beans>

En mi fichero de propiedades del keystore tengo puesto el tipo de firma:

# Defines which key identifier type to use for signature.
# Valid values are: - "IssuerSerial" - "DirectReference" - "X509KeyIdentifier" - "Thumbprint" - "SKIKeyIdentifier" - "KeyValue" - "EmbeddedKeyName" - "EncryptedKeySHA1"
signature.key.identifier =DirectReference
#
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin
org.apache.ws.security.crypto.merlin.keystore.file=clientKeystore.jks
org.apache.ws.security.crypto.merlin.keystore.password=cspass
org.apache.ws.security.crypto.merlin.keystore.type=jks
org.apache.ws.security.crypto.merlin.keystore.alias=myclientkey
Cliente del “Servicio de Verificación y Consulta de Datos: Plataforma de Intermediación” con CXF