jueves, 6 de septiembre de 2012

Crear y Consumir Web Service usando Java con JAX-WS + Maven


Para esta primera entrada de java vamos a crear un Servicio Web que ataca a una base de datos y devuelve un recodset con la información de una serie de contactos almacenados en una tabla.

Para kien no sepa aun q es un Servicio Web, en pocas palabras, es una página web preparada para q sea consumida por un programa en lugar de un humano. Por ejemplo, imaginemos q keremos hacer un juego web con una liguilla de baloncesto, una especie de SuperManager donde cada jugador se hace un equipo; pues bien, la página nba.com nos poroveerá de webServices públicos donde nosotros podremos extraer las estadísticas de cada jugador y dar a cada equipo los puntos correspondientes en nuestro juego (logicamente no podríamos hacer un DAO con una select a su base de datos). Podremos concretar diciendo que un servicio web sirve para comunicar y hacer transacciones entre 2 distintas aplicaciones. De tal manera q nosotr@s primero haremos un webService como si fuesemos el programador/a de nba.com y luego lo consumiremos como un o una particular (o empresa) que quiere acceder a dichos datos.

He elegido estás tecnologías porque ultimamente uso maven para la creación y gestión de todos mis proyectos ya que hace todo con una gran limpieza y da una total independencia de los IDE; por otro lado usar JAX-WS sin ser ningún experto en SOA, simplemente porque cuando me puse a documentarme para iniciar este proyecto me dió las mejores sensaciones, el hecho de definir el endpoint en una clase Main para luego funcionar ejecutando el .jar me pareció muy interesante, eso y la simplicidad de con la que vamos a hacer todo, ya vereis :D

Creación del Servicio Web

  1. Creamos el proyecto, artefacto básico, no necesitamos más:
    $ mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=com.nak.wsdlTest -DartifactId=wsdlTest

  2.  Vamos a la raiz del proyecto y añadimos las dependencias necesarias en el pom.xml : junit para testeos, jaxws para crear el ws y mysql para conectar con la base de datos:
    <dependencies>
      <dependency>
       <groupid>junit</groupid>
       <artifactid>junit</artifactid>
       <version>4.10</version>
       <scope>test</scope>
      </dependency>
      <dependency>
       <groupid>com.sun.xml.ws</groupid>
       <artifactid>jaxws-rt</artifactid>
       <scope>compile</scope>
       <version>2.2.7</version>
      </dependency>
      <!-- mysql conector -->
      <dependency>
       <groupid>mysql</groupid>
       <artifactid>mysql-connector-java</artifactid>
       <version>5.1.21</version>
      </dependency>
     </dependencies>
    

  3. "mvn -U clean compile" viene bien para refrescar los cambios y que maven baje al repositorio las librerías pertinentes.
  4.  Vamos a abrir el proyecto con nuestro IDE, yo en mi caso eclipse, os recuerdo la web de maven a propósito de la integración de dicha herramienta, descarga, configuración, etc...
  5.  A continuación me dispongo a crear 4 clases: una será el POJO con las propiedades de la entidad; otra será un DAO para el acceso a datos; otra será la clase maestra del web service y el main del que hablé para definir el end point. Asi pues hacemos el bean (nombre, constructor, setters y getters):
    public class Persona implements Serializable {
     
     private int rut = 0;
     private String nombre = null;
     private String apellidoPaterno = null;
     private String apellidoMaterno = null;
     
     public Persona() {
      super();
     }
    
     public Persona(int rut, String nombre, String apellidoPaterno,
       String apellidoMaterno) {
      super();
      this.rut = rut;
      this.nombre = nombre;
      this.apellidoPaterno = apellidoPaterno;
      this.apellidoMaterno = apellidoMaterno;
     }
    
     public int getRut() {
      return rut;
     }
    
     public void setRut(int rut) {
      this.rut = rut;
     }
    
     public String getNombre() {
      return nombre;
     }
    
     public void setNombre(String nombre) {
      this.nombre = nombre;
     }
    
     public String getApellidoPaterno() {
      return apellidoPaterno;
     }
    
     public void setApellidoPaterno(String apellidoPaterno) {
      this.apellidoPaterno = apellidoPaterno;
     }
    
     public String getApellidoMaterno() {
      return apellidoMaterno;
     }
    
     public void setApellidoMaterno(String apellidoMaterno) {
      this.apellidoMaterno = apellidoMaterno;
     }
    
     
    }
    
    
  6.  Well, nada nuevo bajo el sol, vamos con el DAO, como veis no me estoy metiendo en jaleos de Hibernate, anotaciones y demás para hacer algo rápido y sencillo. Un DAO de toda la vida:
    package com.nak.wsdlTest.dao;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.ArrayList;
    import java.util.List;
    
    import com.nak.wsdlTest.model.Persona;
    
    public class PersonaDAO {
    
     private Connection connect = null;
     private PreparedStatement preparedStatement = null;
     private ResultSet res = null;
     
     public PersonaDAO() {
      super();
     }
     
     public void conectar() throws ClassNotFoundException, SQLException{
      // This will load the MySQL driver, each DB has its own driver
           Class.forName("com.mysql.jdbc.Driver");
           // Setup the connection with the DB
           connect = DriverManager
               .getConnection("jdbc:mysql://localhost:3306/test?"
                   + "user=usuario&password=user");
           
     }
     
     public List getPersonas() throws SQLException{
      Persona per = null;
      List personas = new ArrayList();
      String sql = "select * from personas";
      preparedStatement = connect.prepareStatement(sql);
      res = preparedStatement.executeQuery();
     
      while(res.next()){
       per = new Persona(res.getInt("rut"), res.getString("nombre"), 
              res.getString("apellido_paterno"), res.getString("apellido_materno"));
       personas.add(per);
      }
      
      return personas;
     }
     
     public void cerrarConexiones() throws SQLException{
      if (res != null) res.close();
      if (preparedStatement != null) preparedStatement.close();
      if (connect != null) connect.close();
     }
    }
    

  7. Hasta acoe ningún problema, ahora viene lo bueno, creamos la clase maestra q define clases y métodos (podemos tomar como apoyo el ejemplo de una de las web del crack chuidiang):
    package com.nak.wsdlTest.ws;
    
    import java.sql.SQLException;
    import java.util.List;
    import javax.jws.WebMethod;
    import javax.jws.WebService;
    import com.nak.wsdlTest.dao.PersonaDAO;
    import com.nak.wsdlTest.model.Persona;
    
    @WebService
    public class UnWebService {
    
     @WebMethod
     public List listarPenya(){
      List personas = null;
      PersonaDAO dao = new PersonaDAO();
      try {
       dao.conectar();
       personas = dao.getPersonas();
       
      } catch (Exception e) {
       e.printStackTrace();
      } finally{
       try {
        dao.cerrarConexiones();   
                                } catch (SQLException e) {
        e.printStackTrace();
       }
      }
      
      return personas;
     }
    }
    

  8.  En la clase main definimos el endpoint:
    package com.nak.wsdlTest.ws;
    import javax.xml.ws.Endpoint;
    
    public class Main {
    
     /**
      * @param args
      */
     public static void main(String[] args) {
      
      Endpoint.publish("http://localhost:8080/UnWebService", new UnWebService());
    
     }
    }
    

  9. Con esto tenemos tendríamos todo listo para levantar nuestro wsdl en el servidor, la utilidad wsgen nos permitiría descargarnos los xsd, etc... Pero no nos va a ser necesario. Lo que vamos a hacer es epaquetar nuestro proyecto y probarlo en un navegador (podríamos correr el main ya desde el propio eclipse y ver http://localhost:8080/UnWebService?wsdl si quisieramos probar), asi pues vamos a añadir unas líneas después de las dependencias a nuestro pom.xml para definir la compilación y cual es el MainClass de nuestro proyecto:
    <build>
      <plugins>
       <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>2.4</version>
        <configuration>
         <archive>
          <manifest>
           <addClasspath>true</addClasspath>
           <mainClass>com.nak.wsdlTest.ws.Main</mainClass>
          </manifest>
         </archive>
        </configuration>
       </plugin>
    
      </plugins>
     </build>
    

  10. Ya tenemos el webService listo para ser desplegado, salimos del eclipse y volvemos a nuestra kerida terminal :) para empaketar el proyecto, ya sabeis, desde la raiz del mismo (donde está el pom.xml):
    $ mvn -U clean package

  11. Notice que nuestro ws ataca a una base de datos mysql y que necesitamos añadir el .jar conector a nuestro jre si no keremos q nos de de hostias cuando vayamos a consumirlo, pongo rutas completas de mi caso para q sea más comprensible:
    $ cp /home/natxo/.m2/repository/mysql/mysql-connector-java/5.1.21/mysql-connector-java-5.1.21.jar /usr/lib/jvm/jdk1.6.0_33/jre/lib/ext/
  12. Ejecutamos el .jar:
    $ java -jar target/wsdlTest-1.0-SNAPSHOT.jar
  13. Vemos como nuestro servicio corre, si ponemos http://localhost:8080/UnWebService?wsdl en nuestro navegador, debe tener un look like this:

  14. Listo, estos son los fuentes por si alguien los quiere consultar. 

Consumir el Servicio Web

Ahora viene la parte contraria, crear un proyecto que consuma un servicio web (en este caso el q acabamos de hacer).

  1. Creamos el artefacto, uno normal y corriente:
    $ mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DgroupId=com.nak.wsTest -DartifactId=ws-proxy
  2.  La parte importante aquí es el pom, lo editamos con el vim para agregar las dependencias y plugins q vamos a usar, pongo la parte crítica y acontinuación explico:
    <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     </properties>
     <build>
      <plugins>
    
       <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>jaxws-maven-plugin</artifactId>
        <version>1.12</version>
        <executions>
         <execution>
          <goals>
           <goal>wsimport</goal>
          </goals>
         </execution>
        </executions>
        <configuration>
         <wsdlUrls>
          <!-- WS en entorno de test -->
          <wsdlUrl>http://localhost:8080/UnWebService?wsdl</wsdlUrl>
         </wsdlUrls>
         <sourceDestDir>${basedir}/src/main/java</sourceDestDir>
         <xadditionalHeaders>true</xadditionalHeaders>
        </configuration>
       </plugin>
       <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.5.1</version>
        <configuration>
         <source>1.6</source>
         <target>1.6</target>
        </configuration>
       </plugin>
      </plugins>
     </build>
     <dependencies>
      <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <version>4.10</version>
       <scope>test</scope>
      </dependency>
      <dependency>
       <groupId>com.sun.xml.ws</groupId>
       <artifactId>jaxws-rt</artifactId>
       <scope>compile</scope>
       <version>2.2.7</version>
      </dependency>
     </dependencies>
    

      Em pezando por el final, las dependencias son obvias, jaxws porque son las librerías que necesitamos para los servicios web y junit para los testeos; los testeos son muy importantes, en la parte anterior también hice aunque no hablé de ellos, en esta parte si veremos una pekeña muestra. Por otro lado tenemos los pugins, el plugin "jaxws-maven-plugin" será quien conecte con el WS y ejecutando el goal "wsimport", definiremos la URL del mismo y donde queremos que nos aloje las clases de acceso a los métodos del WS.
  3. Oki, los siguientes comandos maven leeran el pom, conectarán con el ws y generarán el código pertinente:
    mvn -U clean compile
        o
    mvn jaxws:wsimport

  4.  Ahora solo hay q crear una clase de testeo para comprobar que todo va ok:
    package com.nak.wsTest;
    
    import static org.junit.Assert.assertTrue;
    
    import java.net.URL;
    import java.util.List;
    
    import javax.xml.namespace.QName;
    
    import org.junit.Test;
    
    import com.nak.wsdltest.ws.Persona;
    import com.nak.wsdltest.ws.UnWebService;
    import com.nak.wsdltest.ws.UnWebServiceService;
    
    public class TestWS {
    
     @Test
     public void verTime() throws Exception {
      
      UnWebServiceService unWebServiceService = new UnWebServiceService(
                 new URL("http://localhost:8080/UnWebService?wsdl"),    // URL real del web service.
                 new QName("http://ws.wsdlTest.nak.com/",           // copiado del código generado por wsimport  
                       "UnWebServiceService"));
      
       UnWebService unWebService = unWebServiceService.getUnWebServicePort();
       
       List pers = unWebService.listarPenya();
       
       for(Persona p:pers){
      
        System.out.println(p.getNombre()+" "+p.getApellidoPaterno()+" "+p.getApellidoMaterno());
        
       }
       assertTrue(pers.size()>1);
      
     }
    }
    

  5. Solo falta volver a la terminar y ejecutar el goal de maven para testear:
    $ mvn -U clean test
Y ya está!! Si a ido todo ok, espero que si :) lo empaquetamos en un .jar "mvn -U clean package" para poder importarlo posteriormente en otros proyectos donde queramos listar a estos "pájaros" jeje.

Una vez tenemos el .jar creado en la carpeta ./target del proyecto lo vamos a subir a nuestro repositorio de maven:
$ cd target
$ mvn install:install-file -Dfile=ws-proxy-1.0-SNAPSHOT.jar -DgroupId=testNak -DartifactId=ws-proxy -Dversion=1.0 -Dpackaging=jar

Así pues, para invocarlo desde cualkier otro proyecto en el pom.xm:

<dependency> 
<groupId>testNak</groupId> 
<artifactId>ws-proxy</artifactId> 
<version>1.0</version> 
</dependency>

Y a correr!!

3 comentarios:

Unknown dijo...

MUCHAS GRACIAS POR EL POST

otreblanc dijo...
Este comentario ha sido eliminado por el autor.
otreblanc dijo...

Bien documentado, gracias
En el Endpoint, textualmente, va la referencia de /localhost:8080/; Pregunta: si lo quiero usar en un dominio http://undominio.com/UnWebService se tiene que modificar el endpoint?? o que otras modificaciones se tienen que tomar en cuenta??