From 6e9137e8603346155f46bd90f5843eca721ccc40 Mon Sep 17 00:00:00 2001 From: Gustavo Martin Morcuende <gu.martinm@gmail.com> Date: Sat, 19 Dec 2015 22:43:11 +0100 Subject: [PATCH] jaxb2: many improvements about how Spring WS works --- HOWSPRINGWORKS.txt | 138 ++++++++++++++++++ .../src/main/resources/schemas/parent.xsd | 6 + .../webservices/exceptions/BusinessException.java | 22 +++ .../impl/CustomBindingExampleServiceImpl.java | 6 + .../src/main/resources/schemas/examples.xsd | 13 +- .../resources/spring-configuration/ws/soap-ws.xml | 155 +++++++++++++++++---- 6 files changed, 314 insertions(+), 26 deletions(-) create mode 100644 HOWSPRINGWORKS.txt create mode 100644 jaxb2/web-services-spring-jaxb2-server/src/main/java/de/spring/webservices/exceptions/BusinessException.java diff --git a/HOWSPRINGWORKS.txt b/HOWSPRINGWORKS.txt new file mode 100644 index 0000000..42f5276 --- /dev/null +++ b/HOWSPRINGWORKS.txt @@ -0,0 +1,138 @@ + + +*************************** Porque estoy usando sws:annotation-driven *************************** + +Porque estoy usando sws:annotation-driven y no declarando explÃcitamente los beans en XML existe un +orden de búsqueda de por defecto de macheadores de endpoints y excepciones. Ver: org.springframework.ws.config.AnnotationDrivenBeanDefinitionParser +Cuanto más bajo es el valor de order mayor es la prioridad. + + + 1. Manejadores de excepción, orden por defecto inicializado en AnnotationDrivenBeanDefinitionParser: + a) SoapFaultAnnotationExceptionResolver será el primer manejador de excepciones que se intente usar por defecto. order = 0 + b) SimpleSoapExceptionResolver será el último manejador de excepciones que se intente usar por defecto. order = Ordered.LOWEST_PRECEDENCE + Se usará si la excepción generada no pudo ser manejada por SoapFaultAnnotationExceptionResolver (porque la excepción no fue anotada con + @SoapFault. Este manejador se traga cualquier excepción. + + 2. Endpoints a buscar, orden por defecto inicializado en AnnotationDrivenBeanDefinitionParser: + a) PayloadRootAnnotationMethodEndpointMapping será el primer tipo de endpoints que se buscará y que se intentará usar. order = 0 + Si el XML SOAP que llega no machea ningún metodo anotado con este EndPoint pasamos a b). + b) SoapActionAnnotationMethodEndpointMapping será el segundo tipo de endpoints que se intentará usar. order = 1 + Si el XML SOAP que llega no machea ningún metodo anotado con este EndPoint pasamos a c). + c) AnnotationActionEndpointMapping será el último tipo de endpoints que se buscará. order = 2 + Si el XML SOAP que llega no machea tampoco métodos anotado con este EndPoint se + lanza NoEndpointFoundException desde org.springframework.ws.server.MessageDispatcher.dispatch() + + + EN LUGAR DE USAR LA ANOTACIÃN PODRÃAMOS HABER DECLARADO EXPLÃCITAMENTE CADA BEAN TAL QUE ASÃ: + + <bean id="soapFaultMappingExceptionResolver" + class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver"> + <property name="order" value="0" /> + </bean> + + <bean id="payloadRootAnnotationMethodEndpointMapping" + class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping"> + <property name="order" value="0" /> + </bean> + + CON LA ANOTACIÃN ME AHORRO DECLARAR bean POR bean PERO LO MALO ES QUE INSTANCIO MANEJADORES QUE LUEGO NO USO :( + + + + +*************************** org.springframework.ws.server.MessageDispatcher *************************** + + +org.springframework.ws.server.MessageDispatcher ES LA CLASE QUE BUSCA EndPoints Y MANEJADORES DE EXCEPCIÃN + +ORDEN DE BUSQUEDA DE IMPLEMENTACIONES DE MANEJADORES DE EXCEPCION +Busca en el ApplicationContext manejadores de excepción siguiendo este orden. Cuanto más bajo es el valor de order +mayor es la prioridad. + +Por haber usado sws:annotation-driven el orden es el siguiente: + + +1. Primero se busca por excepciones anotadas con @SoapFault. Si la excepcion generada +está anotada son @SoapFault entonces se usa este manejador de excepcion. + +Implementation of the org.springframework.ws.server.EndpointExceptionResolver interface +that uses the SoapFault annotation to map exceptions to SOAP Faults. +org.springframework.ws.soap.server.endpoint.SoapFaultAnnotationExceptionResolver + + + +2. Segundo usa este manejador de excepción. Este manejador machea cualquier excepción asà que si +este manejador llega primero siempre resolverá la excepción. +Simple, SOAP-specific EndpointExceptionResolver implementation that stores +the exception's message as the fault string. +org.springframework.ws.soap.server.endpoint.SimpleSoapExceptionResolver + + +3. Un manejador de excepciones inyectado por mi en un archivo XML de Spring. Si no se pone prioridad por defecto +Spring le pone la prioridad más baja que es lo que me pasó a mà al principio y SimpleSoapExceptionResolver +se meterá siempre por el medio :( +org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver + + +Si ninguno machea MessageDispatcher lanza la Excepción desde org.springframework.ws.server.MessageDispatcher.processEndpointException() + + + +ORDEN DE BUSQUEDA DE ENDPOINTS EN EL APPLICATION CONTEXT: +Busca en el ApplicationContext metodos anotados siguiendo este orden. Si el XML SOAP que me llega +machea con alguna de estas anotaciones, entonces ese método anotado será usado. Y NO SE CONTINUARà +BUSCANDO MÃS MANEJADORES. EN CUANTO UNO MACHEA YA NO SE BUSCA POR MÃS. + + +Por haber usado sws:annotation-driven el orden es el siguiente: + + +1. Primero se busca por métodos de este modo. Si el XML SOAP machea con algún metodo anotado +de este modo, entonces ese método será usado y no se continúa buscando matches. + +Implementation of the org.springframework.ws.server.EndpointMapping interface that uses the +PayloadRoot annotation to map methods to request payload root elements. +org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping +@Endpoint +public class MyEndpoint { + + @PayloadRoot(localPart = "Request", namespace = "http://springframework.org/spring-ws") + public Source doSomethingWithRequest() { + ... + } +} + +2. Segundo se busca por métodos de este modo. Si el XML SOAP machea con algún metodo anotado +de este modo, entonces ese método será usado y no se continúa buscando matches. + +Implementation of the org.springframework.ws.server.EndpointMapping interface that uses the +SoapAction annotation to map methods to the request SOAPAction header. +org.springframework.ws.soap.server.endpoint.mapping.SoapActionAnnotationMethodEndpointMapping +@Endpoint +public class MyEndpoint{ + + @SoapAction("http://springframework.org/spring-ws/SoapAction") + public Source doSomethingWithRequest() { + ... + } +} + + +3. Tercero se busca por métodos de este modo. Si el XML SOAP machea con algún metodo anotado +de este modo, entonces ese método será usado. + +Implementation of the org.springframework.ws.server.EndpointMapping interface that uses the +@Action annotation to map methods to a WS-Addressing Action header. +org.springframework.ws.soap.addressing.server.AnnotationActionEndpointMapping +@Endpoint +@Address("mailto:joe@fabrikam123.example") +public class MyEndpoint{ + + @Action("http://fabrikam123.example/mail/Delete") + public Source doSomethingWithRequest() { + ... + } +} + + +Si ninguno machea MessageDispatcher lanza NoEndpointFoundException desde org.springframework.ws.server.MessageDispatcher.dispatch() diff --git a/jaxb2/web-services-spring-jaxb2-globalxsds/src/main/resources/schemas/parent.xsd b/jaxb2/web-services-spring-jaxb2-globalxsds/src/main/resources/schemas/parent.xsd index 0ce508e..3c340a4 100644 --- a/jaxb2/web-services-spring-jaxb2-globalxsds/src/main/resources/schemas/parent.xsd +++ b/jaxb2/web-services-spring-jaxb2-globalxsds/src/main/resources/schemas/parent.xsd @@ -12,5 +12,11 @@ <xs:enumeration value="FIVETH"/> </xs:restriction> </xs:simpleType> + + <xs:simpleType name="limitedString"> + <xs:restriction base="xs:string"> + <xs:maxLength value="20" /> + </xs:restriction> + </xs:simpleType> </xs:schema> diff --git a/jaxb2/web-services-spring-jaxb2-server/src/main/java/de/spring/webservices/exceptions/BusinessException.java b/jaxb2/web-services-spring-jaxb2-server/src/main/java/de/spring/webservices/exceptions/BusinessException.java new file mode 100644 index 0000000..f13ae91 --- /dev/null +++ b/jaxb2/web-services-spring-jaxb2-server/src/main/java/de/spring/webservices/exceptions/BusinessException.java @@ -0,0 +1,22 @@ +package de.spring.webservices.exceptions; + +/** + * This exception will be caught by org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver + * + */ +public class BusinessException extends RuntimeException { + + private static final long serialVersionUID = -4042139454770293299L; + + public BusinessException() { + super(); + } + + public BusinessException(String message) { + super(message); + } + + public BusinessException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/jaxb2/web-services-spring-jaxb2-server/src/main/java/de/spring/webservices/services/impl/CustomBindingExampleServiceImpl.java b/jaxb2/web-services-spring-jaxb2-server/src/main/java/de/spring/webservices/services/impl/CustomBindingExampleServiceImpl.java index caa7e41..de65baa 100644 --- a/jaxb2/web-services-spring-jaxb2-server/src/main/java/de/spring/webservices/services/impl/CustomBindingExampleServiceImpl.java +++ b/jaxb2/web-services-spring-jaxb2-server/src/main/java/de/spring/webservices/services/impl/CustomBindingExampleServiceImpl.java @@ -2,6 +2,7 @@ package de.spring.webservices.services.impl; import org.springframework.stereotype.Service; +//import de.spring.webservices.exceptions.BusinessException; import de.spring.webservices.auto.CustomBindingExampleRequest; import de.spring.webservices.auto.CustomBindingExampleResponse; import de.spring.webservices.auto.ParentEnumType; @@ -16,6 +17,11 @@ public class CustomBindingExampleServiceImpl implements @Override public CustomBindingExampleResponse requestResponse(final CustomBindingExampleRequest request) { + + // Example about how works org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver + // see soap-ws.xml Spring configuration file. +// throw new BusinessException("This feature has not been implemented yet."); + CustomBindingExampleResponse response = new CustomBindingExampleResponse(); response.setData("CUSTOM BINDING SNAKE EYES AND " + request.getData()); diff --git a/jaxb2/web-services-spring-jaxb2-server/src/main/resources/schemas/examples.xsd b/jaxb2/web-services-spring-jaxb2-server/src/main/resources/schemas/examples.xsd index 96c3737..8b4bb95 100644 --- a/jaxb2/web-services-spring-jaxb2-server/src/main/resources/schemas/examples.xsd +++ b/jaxb2/web-services-spring-jaxb2-server/src/main/resources/schemas/examples.xsd @@ -12,10 +12,21 @@ <!-- We are going to use catalog.cat in order to avoid downloading parent.xsd from remote server + when creating Java objects from examples.xsd. --> <xs:import namespace="http://gumartinm.name/spring-ws/parent" schemaLocation="http://gumartinm.name/spring-ws/parent/parent.xsd" /> + <!-- Spring requires the following: + 1. XSD elements being used as request must end with Request name. + 2. XSD elements being used as response must end with Response name. + + IN THIS WAY SPRING FINDS OUT HOW TO CREATE THE wsdl:operation IN THE AUTOGENERATED WSDL. + + ExampleRequest and ExampleResponse will be associated to the wsdl:operation Example in the autogenerated wsdl and + the wsdl:operation Example will have the wsdl:request ExampleRequest and wsdl:response ExampleResponse elements. + The same for CustomBindingExample. + --> <!-- Using inheritance and annox plugin --> <xs:element name="ExampleRequest"> <xs:complexType> @@ -26,7 +37,7 @@ </xs:appinfo> </xs:annotation> <xs:all> - <xs:element name="data" type="xs:string" /> + <xs:element name="data" type="parent:limitedString" /> </xs:all> </xs:complexType> </xs:element> diff --git a/jaxb2/web-services-spring-jaxb2-server/src/main/resources/spring-configuration/ws/soap-ws.xml b/jaxb2/web-services-spring-jaxb2-server/src/main/resources/spring-configuration/ws/soap-ws.xml index a59c214..e70b466 100644 --- a/jaxb2/web-services-spring-jaxb2-server/src/main/resources/spring-configuration/ws/soap-ws.xml +++ b/jaxb2/web-services-spring-jaxb2-server/src/main/resources/spring-configuration/ws/soap-ws.xml @@ -14,38 +14,81 @@ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> - <!-- Searches for @Endpoint --> + <!-- Searches for beans in packages (instead of XML configuration we can use in this way annotations like @Service, @Endpoint, etc, etc) --> <context:component-scan base-package="de.spring.webservices"/> <!-- - Aqui se podrÃa especificar un unmarshaller (para la request) o un - marshaller (para la response) especifico. Por ejemplo Castor. + Three ways of using a marshallers/unmarshallers. - Por la anotacion que uso para el EndPoint y porque tengo JAXB2 en el - classpath, Spring lo que está haciendo es el equivalente a si se - escribiera lo siguiente: + 1. No declarar nada en el XML y dejar que Spring lo haga internamente todo por nosotros. + Esto equivale a esta configuracion en XML + + <oxm:jaxb2-marshaller id="marshaller" context-path="de.spring.webservices"/> + El context-path Spring supongo que lo rellena automáticamente en base al component-scan declarado arriba. + + 2. Especificando el context-path para ser escaneado por Spring usando anotaciones. Esto + se hace de este modo: <oxm:jaxb2-marshaller id="marshaller" context-path="de.spring.webservices.auto"/> + Esto es lo mismo que harÃa Spring si no declaramos nada en el XML pero asà tenemos opción de + de especificar un context-path en concreto. - Searches for @PayloadRoot - <sws:annotation-driven marshaller="marshaller" unmarshaller="marshaller"/> + 3. Especificando la implementación concreta del marshaller. + Con esta opción además puedo usar packagesToScan, contest-path si no recuerdo mal tenÃa problemas + cuando habÃa dos ObjectFactory con el mismo package. Uno está en globalxsds y otro en este proyecto. + De todos modos, probablemente habrÃa que usar un package distinto para lo que hay + en globalxsds (quizás incluso basado en el namespace del xsd) y asà podrÃa evitar esta configuración. --> - - <!-- - VOY A INSTANCIAR EXPLÃCITAMENTE EL Marshaller Y VOY A USAR packagesToScan - PORQUE TENGO DOS ObjectFactory CON EL MISMO package. UNO ESTà EN globalxsds Y OTRO - ESTà EN ESTE PROYECTO server. - - SI NO RECUERDO MAL HABÃA PROBLEMAS CUANDO SE TENÃA DOS ObjectFactory IGUALES Y - SE USABA contest-path. - - DE TODOS MODOS, PROBABLEMENTE HABRÃA QUE USAR UN PACKAGE DISTINTO PARA LO QUE HAY - EN globalxsds Y ASà PODRÃA EVITAR ESTA CONFIGURACIÃN :( - --> <bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller"> <property name="packagesToScan" value="de.spring.webservices.auto"/> </bean> + <!-- Searches for @PayloadRoot --> <sws:annotation-driven marshaller="marshaller" unmarshaller="marshaller" /> + + + <!-- + CONSECUENCIAS DE USAR sws:annotation-driven VER: org.springframework.ws.config.AnnotationDrivenBeanDefinitionParser + Cuanto más bajo es el valor de order mayor es la prioridad. + + 1. Manejadores de excepción, orden por defecto inicializado en AnnotationDrivenBeanDefinitionParser: + a) SoapFaultAnnotationExceptionResolver será el primer manejador de excepciones que se intente usar por defecto. order = 0 + b) SimpleSoapExceptionResolver será el último manejador de excepciones que se intente usar por defecto. order = Ordered.LOWEST_PRECEDENCE + Se usará si la excepción generada no pudo ser manejada por SoapFaultAnnotationExceptionResolver (porque la excepción no fue anotada con + @SoapFault. Este manejador se traga cualquier excepción. + + 2. Endpoints a buscar, orden por defecto inicializado en AnnotationDrivenBeanDefinitionParser: + a) PayloadRootAnnotationMethodEndpointMapping será el primer tipo de endpoints que se buscará y que se intentará usar. order = 0 + Si el XML SOAP que llega no machea ningún metodo anotado con este EndPoint pasamos a b). + b) SoapActionAnnotationMethodEndpointMapping será el segundo tipo de endpoints que se intentará usar. order = 1 + Si el XML SOAP que llega no machea ningún metodo anotado con este EndPoint pasamos a c). + c) AnnotationActionEndpointMapping será el último tipo de endpoints que se buscará. order = 2 + Si el XML SOAP que llega no machea tampoco métodos anotado con este EndPoint se + lanza NoEndpointFoundException desde org.springframework.ws.server.MessageDispatcher.dispatch() + + + EN LUGAR DE USAR LA ANOTACIÃN PODRÃAMOS HABER DECLARADO EXPLÃCITAMENTE CADA BEAN TAL QUE ASÃ: + + <bean id="soapFaultMappingExceptionResolver" + class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver"> + <property name="order" value="0" /> + </bean> + + <bean id="payloadRootAnnotationMethodEndpointMapping" + class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping"> + <property name="order" value="0" /> + </bean> + + CON LA ANOTACIÃN ME AHORRO DECLARAR bean POR bean PERO LO MALO ES QUE INSTANCIO MANEJADORES QUE LUEGO NO USO :( + + + ATENCION: + Solo se usa una instancia de org.springframework.oxm.jaxb.Jaxb2Marshaller que será usada por todas las llamadas (todos los hilos). + Es hilo seguro, por debajo usa javax.xml.bind.JAXBContext que es hilo seguro. + JAXBContext debe ser siempre un singleton porque la primera vez tarda mucho en inicializarse. Cada vez que se haga marshalling y unmarshalling + se deben crear objetos ligeros y no seguros mediante los métodos JAXBContext.createMarshaller() y JAXBContext.createUnmarshaller() + (esto es lo que hace Jaxb2Marshaller por nosotros) Los objetos ligeros creados son javax.xml.bind.Marshaller y javax.xml.bind.Unmarshaller + que no son hilo seguro y por tanto serán creados por cada petición (todo esto lo está haciendo Spring ya, asà que genial) + --> <!-- Spring makes the WSDL file for us from the XSD file. @@ -75,10 +118,16 @@ </property> </bean> + <!-- The interceptors in this set are automatically configured on each registered EndpointMapping + What means, every class annotated with @Endpoint will be using these interceptors. + + This configuration enables us to log the SOAP XML Request (received from client), Response (sent by this server) and Fault (sent by this server). + --> <sws:interceptors> <bean class="org.springframework.ws.soap.server.endpoint.interceptor.SoapEnvelopeLoggingInterceptor"> <property name="logRequest" value="true"/> <property name="logResponse" value="true"/> + <property name="logFault" value="true"/> </bean> <!-- @@ -102,15 +151,71 @@ </bean> </sws:interceptors> - - <bean id="exceptionResolver" + + <!-- + PARA METER MAS COSAS A LA RESPUESTA CON ERROR, por ejemplo si quisiéramos enviar en Reason Text la pila de excepción tengo que + implementar mi propio manejador de excepción que extienda de AbstractSoapFaultDefinitionExceptionResolver y añada todo aquello + que yo necesite. + + A mà me parecen muy pobres las implementaciones que trae Spring asà que implementaré el mÃo propio que devuelva la pila + de excepción si la hay. + --> + <bean id="soapFaultMappingExceptionResolver" class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver"> + <!-- + No quiero usar un valor por defecto porque si no, si mi manejador no machea ninguna excepción + devolvera este valor por defecto. + + Cuando se escribe value="SERVER" Spring está seteando SoapFaultDefinition.SERVER en defaultValue + de org.springframework.ws.soap.server.endpoint.AbstractSoapFaultDefinitionExceptionResolver Esto parece + magia pero Spring lo logra usando org.springframework.ws.soap.server.endpoint.SoapFaultDefinitionEditor.setAsText("SERVER") + <property name="defaultFault" value="SERVER"/> + --> <property name="exceptionMappings"> - <value> - org.springframework.oxm.ValidationFailureException=CLIENT,Invalid request - </value> + <props> + <!-- + <prop key="de.spring.webservices.exceptions.BusinessException">SERVER,something went wrong in server side,en_US</prop> + + Si hago esto en lugar de devolver al cliente el mensaje que va dentro de la excepcion devuelvo siempre: + + <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"> + <env:Header/> + <env:Body> + <env:Fault> + <env:Code> + <env:Value>env:Receiver</env:Value> Receiver y Server significa lo mismo. Cuando pongo "SERVER" en primer token aquà aparece Receiver. + </env:Code> + <env:Reason> + <env:Text xml:lang="en-US">something went wrong in server side</env:Text> en-US porque puse como Locale en_US en el tercer token. + </env:Reason> + </env:Fault> + </env:Body> + </env:Envelope> + + El primer token es el Value. Si es SERVER o RECEIVER se muestra Receiver. Si es CLIENT o SENDER se muestra Sender. + + El segundo token es el mensaje que SIEMPRE se mostrará. + + El tercer token mapea a un Locale en SoapFaultDefinition (por defecto Locale.ENGLISH). El mapeo se hace con + org.springframework.util.StringUtils.parseLocaleString("en_US") + + + + + + Yo prefiero que se devuelva el mensaje que va dentro de la excepción. Para eso SOLO puede haber un token y + el Locale siempre será entonces Locale.ENGLISH. + + Uso SERVER porque de.spring.webservices.exceptions.BusinessException es una excepción generada en el lado servidor. + --> + <prop key="de.spring.webservices.exceptions.BusinessException">SERVER</prop> + </props> </property> + <!-- Asà mi manejador de excepciones entra antes que SimpleSoapExceptionResolver pero después que + SoapFaultAnnotationExceptionResolver + --> + <property name="order" value="1" /> </bean> </beans> -- 2.1.4