01 noviembre, 2013

Spring Web Service Contract-First

Existen dos aproximaciones para desarrollar servicios web: Contract-Last y Contract-First, en este post vamos a desarrollar un web service con Spring y por lo tanto usaremos el acercamiento Contract-First que consiste en preparar primero los contratos de datos y de servicios, para luego implementar las clases y todo lo demás...En un post anterior puedes ver como definir un contrato así que puedes darle una mirada si aun no los has visto.
Ok... Aquí vamos a diseñar una aplicación de ejemplo muy simple, nuestro web service nos responderá con un simple saludo estilo "Hola mundo!" donde el nombre que le pasemos en la solicitud será usado para completar el saludo.

Estructura de la aplicación

Como siempre, creamos una aplicación Maven con la siguiente estructura...

Configurando las dependencias

En el archivo pom.xml incluimos las librerias que necesitaremos...
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.codelious.saludows</groupId>
  <artifactId>SaludoWS</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>SaludoWS</name>
  <description>Ejemplo de Servicios Web</description>
  <dependencies>
  <!-- Spring -->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>3.1.1.RELEASE</version>
   <exclusions>
    <!-- Exclude Commons Logging in favor of SLF4j -->
    <exclusion>
     <groupId>commons-logging</groupId>
     <artifactId>commons-logging</artifactId>
     </exclusion>
   </exclusions>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>3.1.1.RELEASE</version>
  </dependency>
    
  <!-- AspectJ -->
  <dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjrt</artifactId>
   <version>1.6.10</version>
  </dependency> 
  
  <!-- Logging -->
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-api</artifactId>
   <version>1.6.6</version>
  </dependency>
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>jcl-over-slf4j</artifactId>
   <version>1.6.6</version>
  </dependency>
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-log4j12</artifactId>
   <version>1.6.6</version>
  </dependency>
  <dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>1.2.15</version>
   <exclusions>
    <exclusion>
     <groupId>javax.mail</groupId>
     <artifactId>mail</artifactId>
    </exclusion>
    <exclusion>
     <groupId>javax.jms</groupId>
     <artifactId>jms</artifactId>
    </exclusion>
    <exclusion>
     <groupId>com.sun.jdmk</groupId>
     <artifactId>jmxtools</artifactId>
    </exclusion>
    <exclusion>
     <groupId>com.sun.jmx</groupId>
     <artifactId>jmxri</artifactId>
    </exclusion>
   </exclusions>
   <scope>runtime</scope>
  </dependency>

  <!-- @Inject -->
  <dependency>
   <groupId>javax.inject</groupId>
   <artifactId>javax.inject</artifactId>
   <version>1</version>
  </dependency>
    
  <!-- Servlet -->
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>servlet-api</artifactId>
   <version>2.5</version>
   <scope>provided</scope>
  </dependency>
  <dependency>
   <groupId>javax.servlet.jsp</groupId>
   <artifactId>jsp-api</artifactId>
   <version>2.1</version>
   <scope>provided</scope>
  </dependency>
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>jstl</artifactId>
   <version>1.2</version>
  </dependency>
 
  <!-- Test -->
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.7</version>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>org.springframework.ws</groupId>
   <artifactId>spring-ws-core</artifactId>
   <version>2.1.4.RELEASE</version>
  </dependency>
  <dependency>
   <groupId>jdom</groupId>
   <artifactId>jdom</artifactId>
   <version>1.1</version>
  </dependency>
  <dependency>
   <groupId>jaxen</groupId>
   <artifactId>jaxen</artifactId>
   <version>1.1.4</version>
  </dependency>
 </dependencies>
</project>

Escribiendo el contrato

Como estamos usando el enfoque Contract-First, lo primero es el contrato (revisa el post definiendo un contrato), para esto simplemente escribimos el archivo saludo-service.xsd que define la estructura que tendrá nuestro wsdl...
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
 elementFormDefault="qualified" attributeFormDefault="qualified"
 targetNamespace="http://localhost:18080/com/codelious/saludows/saludar-service"
 xmlns:tns="http://localhost:18080/com/codelious/saludows/saludar-service">

 <element name="SaludoRequest">
  <complexType>
   <sequence>
    <element type="string" name="nombre" />
   </sequence>
  </complexType>
 </element>

 <element name="SaludoResponse">
  <complexType>
   <sequence>
    <element type="string" name="saludo" />
   </sequence>
  </complexType>
 </element>
 
</schema>

Las Clases del Servicio

Nuestro servicio es simplemente un saludo, por tanto tendremos una interfaz SaludarService.java que tiene un metodo getSaludo()
package com.codelious.saludows.servicio;

public interface SaludarService {
 
    String getSaludo(String nombre);

}

Luego escribimos la clase SaludarServiceImpl.java que implementa la interfaz anterior...
package com.codelious.saludows.servicio;

public class SaludarServiceImpl implements SaludarService {

    public String getSaludo(String nombre) {
  
        String mensaje = "Hola " + nombre + " !!";
        return mensaje;

    }
}

Escribiendo la clase de EndPoint

Existen varias formas de escribir esta clase, en este caso extenderemos de AbstractDomPayloadEndpoint
package com.codelious.saludows.endpoint;

import org.springframework.ws.server.endpoint.AbstractDomPayloadEndpoint;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;

import com.codelious.saludows.servicio.SaludarService;

public class SaludarServiceEndPoint extends AbstractDomPayloadEndpoint {

    public static final String NAMESPACE = "http://localhost:18080/com/codelious/saludows/saludar-service";
 
    private SaludarService saludarService;
 
    public void setSaludarService(SaludarService saludarService) {
        this.saludarService = saludarService;
    }
 
    @Override
    protected Element invokeInternal(Element requestElement,
            Document responseDocument) throws Exception {
  
        // recibe un elemento de solicitud y un documento
  
        String solicitudString = buscarStringSolicitud(requestElement);
        String saludoString = invocaServicioRetornaRespuesta(solicitudString);
        Element respuestaXml = preparaRespuestaXml(responseDocument, saludoString);
  
        return respuestaXml;
    }

    private Element preparaRespuestaXml(Document responseDocument,
          String saludoString) {
  
        // prepara los nodos de la respuesta
        Element elementoRespuesta = responseDocument.createElementNS(NAMESPACE, "SaludoResponse");
        Element elementoSaludo = responseDocument.createElementNS(NAMESPACE, "saludo");
        // prepara el texto contenido en la respuesta
        Text textoRespuesta = responseDocument.createTextNode(saludoString);
        elementoRespuesta.appendChild(elementoSaludo);
        elementoSaludo.appendChild(textoRespuesta);
  
        return elementoRespuesta;
    }

    private String invocaServicioRetornaRespuesta(String solicitudString) {
  
        // invoca el servicio pasandole el nombre y retorna el saludo
        String saludoString = saludarService.getSaludo(solicitudString);
  
        return saludoString;
    }

    private String buscarStringSolicitud(Element requestElement) {
  
        // recupera el primer elemento llamado "nombre"
        Element elementoNombre = (Element) requestElement.getElementsByTagNameNS(NAMESPACE, "nombre").item(0);
        // obtiene el texto contenido
        String solicitudString = elementoNombre.getTextContent();
  
        return solicitudString;
    }
 
}

Configurando la aplicación

Nuestro archivo web.xml define el MessageDispatcherServlet, que a diferencia del DispatcherServlet de una aplicación MVC que maneja controladores, este se encargará de manipular nuestros endpoints...
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns="http://java.sun.com/xml/ns/javaee" 
 xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
  http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 id="WebApp_ID" version="2.5">
 
 <display-name>SaludoWS</display-name>
 
 <servlet>
  <servlet-name>spring-ws</servlet-name>
  <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
  <init-param>
   <param-name>transformWsdlLocations</param-name>
   <param-value>true</param-value>
  </init-param>
 </servlet>

 <servlet-mapping>
  <servlet-name>spring-ws</servlet-name>
  <url-pattern>/*</url-pattern>
 </servlet-mapping>
 
</web-app>

Luego nuestro spring-ws-servlet.xml definirá la configuración de los endpoints...
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans    
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
 
    <bean id="saludarService" class="com.codelious.saludows.servicio.SaludarServiceImpl">
    </bean>

    <bean id="saludarServiceEndpoint"
        class="com.codelious.saludows.endpoint.SaludarServiceEndPoint">
        <property name="saludarService" ref="saludarService" />
    </bean>

    <bean id="payloadMapping"
        class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping">
        <property name="defaultEndpoint" ref="saludarServiceEndpoint" />
    </bean>

    <bean id="saludarSchema" class="org.springframework.xml.xsd.SimpleXsdSchema">
        <property name="xsd" value="/WEB-INF/saludo-service.xsd" />
    </bean>

    <bean id="saludar"
        class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
        <property name="schema" ref="saludarSchema" />
        <property name="portTypeName" value="saludar" />
        <property name="locationUri"
            value="http://localhost:18080/SaludoWS/services" />
    </bean>

</beans>
Este archivo spring-ws-servlet.xml define varias cosas como la interfaz del servicio, la clase EndPoint, el contrato (xsd) que usará para generar el wsdl y finalmente la ruta del wsdl que usarán los clientes para consumir el web service...
Para ver el WSDL podemos visitar la direccion http://localhost:18080/SaludoWS/services/saludar.wsdl (reemplazando el puerto 18080 por el que usemos) y también podemos probar el funcionamiento usando una herramienta como SoapUI...
That's all Folks! el código está en github... https://github.com/codelious/SaludoWS

No hay comentarios.:

Publicar un comentario