martes, 6 de noviembre de 2012

Java RESTfull web service with json and jboss II

En el articulo anterior vimos las principales ventajas e incovenientes (desde el humilde punto de vista de quien suscribe) de los servicios web SOAP y REST.

Hoy vamos a realizar un ejemplo práctico de un servicio REST que produzca datos en formato JSON, para luego consumirlo desde plataformas moviles (iOS y Android).

El software que he usado para hacer este ejemplo es:
  • Mac OS X 10.8
  • JDK 1.6
  • Jboss 7.1.1
  • Eclipse Juno
  • Maven3
Vamos a crear una aplicacion ear con un modulo war en principio donde alojaremos nuestro servicio REST. Al final del post veremos como podemos transformar de una forma sorprendentemente facil nuestro componente web en un EJB de sesión sin estado, lo cual abre muchas otras opciones. El ejemplo tiene un caracter didactico, pero para el proposito nos vale perfectamente.

Vamos a crear una clase que nos ofrezca un servicio web que devolverá una lista de paises. El servicio aceptara un parametro de forma que:
  • Si vale 0 devolverá paises europeos.
  • Si vale 1 devolverá paises no europeos.

Esta distinción en nuestro ejemplo la haremos a hardcode con un 'if'. Pero planteará la base para poder bifurcar y profundizar nuestro código como queramos/necesitemos.

Bien... comenzamos!, primero crearemos la estructura de maven en eclipse, vamos a hacerlo sin asistentes ni ayudas para que todo quede mas claro:

Creamos un nuevo proyecto en eclipse:  A través de File->New->Java Project creamos un nuevo proyecto que llamaremos 'CountryList'. En el protecyo recien creado añadiremos la siguiente estructura:



Con nuestro proyecto seleccionado accedemos a Project -> Properties -> Java Build Path. En el cuadro de diálogo de la derecha seleccionamos la pestaña 'Source'. Pulsamos el botón 'Add Folder...' y seleccionamos la carpeta interna de proyecto CountryList '/war/src/main/java' y pulsamos OK.

Ahora, como se puede ver tenemos un proyecto maven (pom.xml en el directorio raiz) que es padre de otros dos proyectos maven (pom.xml dentro de ear y pom.xml dentro de war). Vamos a dejar la definición de cada pom.xml para el final.

Creamos dentro de nuestro directorio de fuentes del war (que hemos creado hace dos párrafos) el siguiente paquete: 'com.country.list'.

Dentro del nuevo paquete que hemos creado, añadimos una clase llamada CountryList.java.

El codigo de la clase será algo asi como:

package com.country.list;

import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;


@Path("/countryList")
public class CountryList {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/getCountry")
    public List<String> getCountry(@DefaultValue("") @QueryParam("countryType") String tipo){

        ArrayList<String> lista = new ArrayList<String>();
        if ("0".equals(tipo)) {
        
           lista.add("Espana");
           lista.add("Francia");
           lista.add("Alemania");
           lista.add("Portugal");
         
        } else if ("1".equals(tipo)) {
           lista.add("USA");
           lista.add("Argentina");
           lista.add("Guatemala");
           lista.add("Jamaica");
         
        }

        return lista;
    }
}

Vemos que hemos usado una serie de anotaciones, estas anotaciones pertencen a la librería de java JAX-RS mediante la cual podemos generar servicios web de tipo REST.

Para eliminar los errores de compilacion de las clases que usamos se puede, para aquellos que tengais m2eclipse, usar el menu contextual para usar la opción 'maven -> enable dependency management'. No es mi caso, asi que añadiré la librería de jboss a mi proyecto. La librería está en <JBOSS7.1.1_HOME>/modules/javax/ws/rs/api/main/jboss-jaxrs-api_1.1_spec-1.0.0.Final.jar. A traves de (con el proyecto seleccionado) Project -> properties -> Java Build Path, Pestaña 'Libraries' pulsamos 'Add External jars...' y seleccionamos dicho jar. También podemos copiarla a nuestro directorio 'lib' y usar el botón 'Add Jars...'.
 
Una vez resueltos los problemas de compilación, pasemos describir las anotaciones usadas:

  • @Path -> Especifica el path relativo dentro de la URL para nuestra clase o método.
  • @GET -> Especifica el tipo de peticion HTTP (también existen @POST, @PUT, @DELETE, @HEAD, etc...).
  • @Produces -> especifica el tipo MIME de la respuesta, en este caso JSON. También existe @Consumes para especificar el MIME de la entrada.
  • @DefaultValue -> Permite indicar el valor por defecto de un parametro si no esta especificado en la petición.
  • @QueryParam -> Usado para asociar a un parametro en la peticion con una variable.

Hemos identificado nuestra clase como '/countryList' y a nuestro método como '/getCountry'. Una vez hayamos definido el artifact del war veremos como invocar mediante una URL a nuestro servicio.

¿Ya hemos acabado?

Aun no... nos queda un pequeño paso. Indicar a Jboss que tipo de URI debe tratar como servicios web REST, para ello editamos el archivo web.xml, que debe quedar asi:

<web-app id="CountryList" version="2.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/j2ee" xsi:schemalocation="http://java.sun.com/xml/ns/j2ee

    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <display-name>CountryList</display-name>

    <servlet-mapping>

        <servlet-name>javax.ws.rs.core.Application</servlet-name>

        <url-pattern>/*</url-pattern>

    </servlet-mapping>

</web-app>

Indicamos que el servlet Application debe atender todas la url que cumplan el patron *. Esto es, que toda peticion que llegue al contexto de nuestro war, la trate con un servicio REST. Este servlet es parte del JAX-RS y nos proporciona un 'puente' entre una URL dentro del contexto de nuestra aplicacion web hacía el servicio REST.


El pom.xml del proyecto Raiz quedará como sigue (este es el padre):

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.country.list</groupId>
  <version>1.0</version>
  <artifactId>countrylist</artifactId>
  <packaging>pom</packaging>
  <name>countrylist</name>
  <modules>
  <module>ear</module>
  <module>war</module>
  </modules>

  <repositories>
    <repository>
      <id>java.net2</id>
      <name>Java.Net Maven2 Repository, hosts the javaee-api dependency</name>
      <url>http://download.java.net/maven/2</url>
    </repository>
  </repositories>

  <build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>2.3.2</version>
      <configuration>
        <source>1.6</source>
        <target>1.6</target>
        <encoding>ISO-8859-1</encoding>
      </configuration>
    </plugin>
  </plugins>
  </build>

  <dependencies>
  <dependency>
       <groupId>org.jboss.spec.javax.ws.rs</groupId>
         <artifactId>jboss-jaxrs-api_1.1_spec</artifactId>
          <version>1.0.0.Final</version>
          <scope>provided</scope>
   </dependency>
    </dependencies>
</project>


El pom.xml del subproyecto ear quedará como sigue (/ear/pom.xml)

<project>
   <modelVersion>4.0.0</modelVersion>
   <parent>
    <groupId>com.country.list</groupId>
    <version>1.0</version>
    <artifactId>countrylist</artifactId>
  </parent>
  <groupId>com.country.list</groupId>
  <artifactId>ear</artifactId>
  <packaging>ear</packaging>
  <version>1.0</version>
  <name>Modulo ear Java EE 6</name>
  <url>http://maven.apache.org</url>
  <build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-ear-plugin</artifactId>
      <version>2.3.2</version>
      <configuration>
        <version>1.4</version>
        <defaultJavaBundleDir>lib/</defaultJavaBundleDir>
        <defaultLibBundleDir>lib</defaultLibBundleDir>
        <generateApplicationXml>true</generateApplicationXml>
      </configuration>
    </plugin>
  </plugins>
  <finalName>countrylist</finalName>
  </build>
  <dependencies>
    <dependency>
      <groupId>com.country.list</groupId>
      <artifactId>countrylistWar</artifactId>
      <version>1.0</version>
      <type>war</type>
    </dependency>
  </dependencies>
</project>


Por ultimo, el subproyecto del war quedará como sigue (/war/pom.xml)

<project>
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.country.list</groupId>
   <artifactId>countrylistWar</artifactId>
   <packaging>war</packaging>
   <version>1.0</version>
   <name>Modulo war</name>
   <parent>
      <groupId>com.country.list</groupId>
      <artifactId>countrylist</artifactId>
      <version>1.0</version>
   </parent>
   <dependencies>
      <dependency>
       <groupId>org.jboss.spec.javax.ws.rs</groupId>
         <artifactId>jboss-jaxrs-api_1.1_spec</artifactId>
          <version>1.0.0.Final</version>
          <scope>provided</scope>
   </dependency>
  </dependencies>
  <build>
      <resources>
          <resource>
              <directory>
                  src/main/resources
              </directory>
          </resource>
      </resources>  
  </build>
</project>


Ahora que ya tenemos nuestro proyecto completo, abrimos un terminal y navegamos hasta el workspace, necesitamos conectividad con internet para este paso. Si tu conexion a internet está a través de un http Proxy debe editar la configuracion de maven (en maven3): <MAVEN_HOME>/conf/settings.xml, buscamos la entrada '<proxies>' y añadimos el nuestro ahi:

    <proxy>

      <id>ID</id> <!--Identificador que queramos darle -->

      <active>true</active> <!-- Si esta activo, indicamos que si -->

      <protocol>http</protocol> <!-- Protocolo, generalmente http -->

      <username></username> <!-- Usuario, si el proxy requiere autenticacion, si no es asi dejar en blanco -->

      <password></password> <!--Password, si el proxy requiere autenticacion, si no es asi dejar en blanco -->

      <host>IP</host> <!-- Ip de la maquina que hace de proxy -->

      <port>PUERTO</port> <!-- Puerto de escucha de la maquina que hace de proxy -->

      <nonProxyHosts>acme.com|local.net|some.host.com</nonProxyHosts> <!-- Ip y/p host que no queramos que salgan por el proxy  -->

    </proxy>

    
Como ibamos diciendo, abrimos un terminal y accedemos al workspace de eclipse, justo al directorio raiz del proyecto <WORKSPACE_PATH>/CountryList. Ejecutamos 'mvn clean install'. Esta orden compilara y generara nuestro proyecto (nuestro ear), el cual se generará en el directoro 'target' dentro dde ear en nuestro proyecto (F5 para refrescar).

Ahora solo nos falta copiarlo al directorio standalone de jboss, en la ruta <JBOSS7.1.1_HOME>/standalone/deployments y arrancarlo con <JBOSS7.1.1_HOME>/bin/standalone.sh (o .cmd en caso de windows). Cuando arraque, abrimos un navegador y escribimos los siguiente (si tienes jboss escuchando en otro puerto y/o otra ip, actualiza la URL convenientemente):

http://localhost:8080/countrylistWar/countryList/getCountry?countryType=0, lo cual nos dará un array en formato JSON de paises europeos.

http://localhost:8080/countrylistWar/countryList/getCountry?countryType=1 nos dará un array en formato JSON de paises no europeos. Donde:

  • countrylistWar -> Es el contexto de nuestra aplicacion Web
  • countryList -> es el mapeo que le dimos a nuestra clase.
  • getCountry -> es el mapeo que le dimos a nuestro metodo.
  • countryType -> es como llamamos a nuestro parametro.

Y ya está, con un poco de trabajo y de una forma sencilla tenemos nuestro servicio básico REST en java funcionando.

Si queremos que además nuestra clase sea un EJB, solo tenemos que anotarla con @Sateless y añadir la dependencia de la libreria de ejb para maven.

En el proximo articulo realizaremos un cliente Android y otro iOS que consuman este servicios.

No hay comentarios:

Publicar un comentario