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.

lunes, 5 de noviembre de 2012

Java RESTfull web service with json and jboss

SOAP vs REST. Java Web Services  

Aunque la comparativa entre servicios SOAP y RESTfull no es nueva ha adquirido un enfoque especial debido a las plataformas móviles que están en el mercado con una fuerza innegable, cuyos dos estandartes son iOS y Android.

Vamos a ver un poco de historia de ambos, las principales ventajas e inconvenientes bajo la humilde opinión del que suscribe y finalmente en sucesivos artículos veremos un ejemplo de creación de un servicio RESTfull con Java, JSON, maven y jboss 7.1.1, así como un ejemplo de clientes en Android e iOS.


SOAP

Cuando se habla de servicios web a aquellos que nos dedicamos a la consultoría 'estándar', lo primero que nos suele venir a la cabeza es SOAP (Simple Object Access Protocol). SOAP es un estándar de comunicaciones basado en XML, mediante SOAP se define un contrato (WSDL) que es implementado por el servidor y consumido por el cliente.

La tarea de definir un servicio SOAP es relativamente sencilla, una vez definido el servicio las herramientas de los servidores de aplicaciones o los frameworks de terceros se encargan de generar el contrato (WSDL) así como los archivos necesarios para los clientes. SOAP ofrece unas ventajas muy claras como son:

  • Definición de un contrato en todos los métodos que ofrece el servicio web.
  • Independencia de la capa de transporte, se puede usar SOAP sobre HTTP, SMTP, TCP, JMS, etc…
  • Independencia del lenguaje de programación usado para crear el servicio y para consumirlo, se puede crear en servicio en Java y consumirlo en .NET por ejemplo.
  • Permite la interoperabilidad de distintas plataformas.

Un poco de historia de SOAP en java

Los servicios web en java tiene su primera especificación formal en JAX-RPC 1.0 y JAX-RPC 1.1 (Java Api for XML - Remote Procedure Call). Esta especificación fué mejorada con lo que iba a ser JAX-RPC 2.0 que finalmente acabó siendo JAX-WS (Java Api for Xml - Web Services). Hay varias implementaciones de JAX-RPC como por ejemplo Axis (Apache) o JWSDP (Java Web Service Developer Pack, de Sun Microsystems). En cuanto a JAX-WS tenemos Axis2 (apache) o Metro (evolución de JWSDP, Oracle).

JAX_RPC está basado en java 4, mientras que JAX-WS está basado en Java 5 usando anotaciones y demás características introducidas en esta versión de java.

Para detalles concretos os dejo este enlace de ibm.

En la práctica, y bajo mi experiencia profesional y personal la verdad es que cumple con sus ventajas aunque con algunos 'peros':

  • Es cierto que ofrece un contrato cerrado, pero esto también ofrece unas limitaciones con ciertos objetos del lenguaje usado para crear el servicio, lo cual añade una restricción al uso del lenguaje.
  • Hacer un cambio en la signatura de algún método del servicio web implica tener que regenerar el cliente del servicio web, lo cual para los clientes reales -las personas- puede ser un verdadero trastorno, por no hablar de los inconvenientes de los fallos puntuales por la falta de comunicación de estos cambios, bien sea por no transmitirlos o porque el receptor creyó entender otra cosa… algo que sobre el papel parece ridículo, pero que es mas común de lo que se puede desear.
  • El transporte sobre XML es una ventaja porque te desvincula del transporte usado (HTTP, etc..), pero esto también puede ser un problema… usar esto por ejemplo en algo ligero como Javascript puede ser literalmente la muerte en vida… En una plataforma móvil, la cual está limitada en recursos, la carga añadida de trafico de red por el volumen del XML así como su tratamiento de recepción/envío es una carga añadida que nos puede jugar una mala pasada en picos de actividad del dispositivo…

RESTfull

Fue definido por Roy Fielding en su tesis doctoral. REST (Representational State Transfer), es un estilo de arquitectura. Las principales ventajas son:

  • No añade capas adicionales como SOAP lo cual lo hace mas ligero.
  • Usa el transporte HTTP como medio de comunicación.
  • No mantiene estado en las peticiones.
  • Puede transferir los datos a través de JSON (JavaScript Object Notation), a través de XML o de ambos.
  • Se basa en Java 5 o superior, con lo cual aporta anotaciones que facilitan mucho su codificación.
  • Define un método como una URI, en un formato que recuerda mucho al de directorios, y lo hace accesible a través de HTTP.

Historia REST en Java

Al principio, cuando Roy Fielding lo presentó en California en el año 2000, no tuvo una gran transcendencia, pero años después acabó desplazando a SOAP y se convirtió en una parte de la especificación de java, la JSR-311. La implementación de java es JAX-RS.

Sitios como Google, Facebook, Flickr, Twitter, etc… están basados en servicios de este tipo.


SOAP Vs REST

Pues ahora que hemos visto un poco de ambos ya nos podemos hacer una ligera idea de los puntos fuertes y débiles de cada uno… así que al final nuestra decisión de cual usar dependerá de que es lo que queramos hacer,  por poner dos ejemplos:

  • Si necesitamos mantener transacciones, o mantener el estado deberíamos optar por SOAP.
  • Si vamos a integrarnos con plataformas como JavaScript, Android, iOS, etc.. deberiamos optar por REST.

En el próximo articulo vamos a realizar un ejemplo práctico de la creación de un servicio web REST con Java que transmitira datos en formato JSON, para desplegarlo en JBOSS 7.1.1. Finalmente, en un tercer artículo realizaremos un cliente en Android y otro iOS que lo consuman.