jaxb2: many improvements about how Spring WS works
authorGustavo Martin Morcuende <gu.martinm@gmail.com>
Sat, 19 Dec 2015 21:43:11 +0000 (22:43 +0100)
committerGustavo Martin Morcuende <gu.martinm@gmail.com>
Sat, 19 Dec 2015 21:43:11 +0000 (22:43 +0100)
HOWSPRINGWORKS.txt [new file with mode: 0644]
jaxb2/web-services-spring-jaxb2-globalxsds/src/main/resources/schemas/parent.xsd
jaxb2/web-services-spring-jaxb2-server/src/main/java/de/spring/webservices/exceptions/BusinessException.java [new file with mode: 0644]
jaxb2/web-services-spring-jaxb2-server/src/main/java/de/spring/webservices/services/impl/CustomBindingExampleServiceImpl.java
jaxb2/web-services-spring-jaxb2-server/src/main/resources/schemas/examples.xsd
jaxb2/web-services-spring-jaxb2-server/src/main/resources/spring-configuration/ws/soap-ws.xml

diff --git a/HOWSPRINGWORKS.txt b/HOWSPRINGWORKS.txt
new file mode 100644 (file)
index 0000000..42f5276
--- /dev/null
@@ -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()
index 0ce508e..3c340a4 100644 (file)
             <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 (file)
index 0000000..f13ae91
--- /dev/null
@@ -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);
+    }
+}
index caa7e41..de65baa 100644 (file)
@@ -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());
index 96c3737..8b4bb95 100644 (file)
 
     <!--
         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>
index a59c214..e70b466 100644 (file)
                       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.
         </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>
 
         <!-- 
         </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>