Proyecto Java EE
 

Servicios RESTful con Jersey

Introducción

En esta sesión de integración construiremos una interfaz web alternativa a la desarrollada en la sesión anterior. En este caso, en lugar de crear una interfaz HTML para acceder a la aplicación desde un navegador web, crearemos una serie de servicios RESTful para que otras aplicaciones puedan acceder a la información sobre los libros de la biblioteca. Este proyecto también se construirá sobre la capa de negocio desarrollada en sesiones anteriores, al igual que el proyecto web desarrollado en la sesión anterior.

Proyecto jbib-rest

Vamos a crear en primer lugar un nuevo proyecto con Maven, de nombre jbib-rest, utilizando para ello el arquetipo webapp-javaee6, al igual que el proyecto web de la sesión anterior. Por lo tanto, necesitaremos contar también con el plugin Maven integration for WTP (optional), que ya deberemos tener instalado en Eclipse (si no es así, deberá instalarse siguiendo las instrucciones indicadas en la sesión anterior). Este nuevo proyecto contendrá todos los componentes necesarios para implementar los servicios RESTful.

En el espacio de trabajo del proyecto de integración crea un nuevo proyecto jbib-rest. Escoge la opción de Eclipse New > Project... > Maven > Maven Module, sin marcar la opción de Create simple project e introduciendo:

  • Module Name: jbib-rest
  • Parent Project: proyint-jbib
  • Archetype: webapp-javaee6 (se indica en la segunda pantalla del asistente)
  • Paquete: es.ua.jtech.jbib.rest (se indica en la tercera pantalla del asistente)

Al igual que en el proyecto web, deberemos eliminar las etiquetas groupId y version del fichero POM del nuevo proyecto para que coja los valores del proyecto padre. También eliminaremos el bloque <build> ... </build> ya que no es necesario.

<parent>
    <artifactId>jbib</artifactId>
    <groupId>es.ua.jtech.proyint</groupId>
    <version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>jbib-rest</artifactId>
<packaging>war</packaging>

<name>jbib-rest</name>

También en este fichero POM, justo antes de las dependencias, deberemos añadir el repositorio de Maven donde se encuentra Jersey:

<repositories>
    <repository>
        <id>maven2-repository.java.net</id>
        <name>Java.net Repository for Maven</name>
        <url>http://download.java.net/maven/2</url>
        <layout>default</layout>
    </repository>
    <repository>
        <id>maven-repository.java.net</id>
        <name>Java.net Maven 1 Repository</name>
        <url>http://download.java.net/maven/1</url>
        <layout>legacy</layout>
    </repository>	
</repositories>

Tras esto añade la dependencia con el proyecto jbib-negocio, jersey-server, jersey-servlet, y jersey-json:

<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-server</artifactId>
    <version>1.14</version>
</dependency>

<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-servlet</artifactId>
    <version>1.14</version>
</dependency>

<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-json</artifactId>
    <version>1.14</version>
</dependency>

<dependency>
    <groupId>es.ua.jtech.proyint</groupId>
    <artifactId>jbib-negocio</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <type>jar</type>
    <scope>compile</scope>
</dependency>

El fichero completo debe quedar tal como se indica en este pom.xml.

Ahora podemos ejecutar nuestra aplicación web directamente desde Eclipse como hemos hecho a lo largo del curso (Run As > Run on server). Tendremos que configurar Tomcat en Eclipse, si no está configurado ya en este workspace. Veremos una página de prueba creada automáticamente por Maven, que estará desplegada en la siguiente dirección:

http://localhost:8080/jbib-rest/

Configuración del acceso a datos

Vamos ahora a configurar el pool de conexiones y el realm de seguridad de la misma forma que con el proyecto de la sesión anterior.

Para ello creamos un fichero context.xml en la carpeta META-INF de nuestro proyecto REST:

<?xml version="1.0" encoding="ISO-8859-1"?>
<Context>
  <Resource
    name="jdbc/biblioteca"
    type="javax.sql.DataSource"
    auth="Container"
    username="root"
    password="expertojava"
    driverClassName="com.mysql.jdbc.Driver"
    url=
      "jdbc:mysql://localhost:3306/biblioteca?autoReconnect=true"/>

  <ResourceParams name="jdbc/biblioteca">
  
    <parameter>
      <name>maxActive</name>
      <value>20</value>
    </parameter>
    
    <parameter>
      <name>maxIdle</name>
      <value>5</value>
    </parameter>

    <parameter>
      <name>maxWait</name>
      <value>10000</value>
    </parameter>
  </ResourceParams>

<Realm  className="org.apache.catalina.realm.JDBCRealm"
  driverName="com.mysql.jdbc.Driver"
  connectionURL=
      "jdbc:mysql://localhost:3306/biblioteca?autoReconnect=true"
  connectionName="root" connectionPassword="expertojava"
  userTable="usuario" userNameCol="login" userCredCol="password"
  userRoleTable="usuario" roleNameCol="tipo" />
</Context>

También modificaremos el fichero web.xml de la aplicación para que referencie al recurso creado en el paso anterior. Para eso, añadimos estas líneas:

<resource-ref>
    <res-ref-name>jdbc/biblioteca</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
</resource-ref>

Si en el proyecto no se ha creado el descriptor de despliegue de forma automática, podéis utilizar este web.xml como plantilla.

También deberemos configurar la unidad de persistencia al igual que en la sesión anterior para que acceda a la fuente de datos que acabamos de crear. Se deberá poner este fichero persistence.xml en el directorio src/main/resources/META-INF del nuevo proyecto. También se copió en sesiones anteriores el driver de MySql al directorio de librerías de Tomcat. Si no lo tuviésemos copiado, deberemos hacerlo para que la aplicación funcione correctamente.

Configuración de Jersey

Vamos ahora a configurar en web.xml el servlet de Jersey mediante el cual accederemos a los servicios REST. Los recursos de Jersey estarán en el paquete es.ua.jtech.jbib.rest.resources, y accederemos a ellos a través de la URL /resources:

<servlet>
    <servlet-name>Jersey Web Application</servlet-name>
    <servlet-class>
       com.sun.jersey.spi.container.servlet.ServletContainer
    </servlet-class>
    <init-param>
        <param-name>
            com.sun.jersey.config.property.packages
        </param-name>
        <param-value>es.ua.jtech.jbib.rest.resources</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>Jersey Web Application</servlet-name>
    <url-pattern>/resources/*</url-pattern>
</servlet-mapping>

Configuración de la seguridad

Vamos ahora a definir seguridad declarativa para nuestra aplicación. Utilizaremos seguridad básica para proteger la operación de pedir préstamo, de forma que sólo puedan utilizarla los alumnos y profesores registrados.

Protección de recursos

Vamos a añadir las líneas de configuración de seguridad al fichero web.xml para proteger las operaciones que realizan modificaciones sobre los recursos (POST, PUT y DELETE). En primer lugar declaramos los roles que tendrán acceso:

<security-role>
    <role-name>PROFESOR</role-name>
</security-role>
<security-role>
    <role-name>ALUMNO</role-name>
</security-role>

Tras esto, protegeremos todos los recursos de la web para que sólo los usuarios con estos roles tengan acceso a las operaciones que alteran los datos:

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Todos los recursos</web-resource-name>
        <url-pattern>/*</url-pattern>
        <http-method>DELETE</http-method>
        <http-method>POST</http-method>
        <http-method>PUT</http-method>
    </web-resource-collection>
    <auth-constraint>
        <role-name>PROFESOR</role-name>
        <role-name>ALUMNO</role-name>
    </auth-constraint>
</security-constraint>

Autentificación básica

En este caso ya no podemos utilizar seguridad basada en formularios, ya que necesitamos proporcionar una interfaz con la que otras aplicaciones puedan autentificarse de forma sencilla. Por lo tanto, utilizaremos seguridad de tipo BASIC. En el fichero web.xml, añadimos el bloque login-config:

<login-config>
    <auth-method>BASIC</auth-method>
</login-config>	

Recursos REST

Vamos a crear recursos para gestionar el conjunto de libros y para manipular cada libro individual. Estos recursos se llamarán LibrosResource y LibroResource respectivamente, siendo el primero un recurso raíz mapeado a la ruta /libros y el segundo un subrecurso de éste, al que se accede cuando se añade el identificador de un libro a la ruta anterior. Todos los métodos soportarán las representaciones application/xml y application/json, salvo que se especifique lo contrario.

Vamos a utilizar beans JAXB para mapear los objetos Java que encapsulan la información de los libros a sus representaciones XML/JSON.

Beans JAXB

Los beans estarán en un paquete es.ua.jtech.jbib.rest.beans. En la clase del bean añadiremos métodos para poder crear de forma sencilla estos objetos a partir de su correspondiente objeto de dominio:

public class LibroBean {
 
    Long id;
    String isbn;
    String titulo;
    String autor;
    int numPaginas;
     
    public LibroBean() {
    } 
    
    public LibroBean(LibroDomain libro) {
        this.id = libro.getId();
        this.titulo = libro.getTitulo();
        this.autor = libro.getAutor();
        this.numPaginas = libro.getNumPaginas();
        this.isbn = libro.getIsbn();
    }
    	
    // Getters y setters
    ...		
}

Añade a esta clase las anotaciones JAXB necesarias para que la etiqueta con los datos del libro tenga nombre libro y para que todos sus campos se muestren como atributos.

Tendremos también otro bean para representar el conjunto de libros:

public class LibrosBean {
 
    List<LibroBean> libros;
     
    public LibrosBean() {
    }
     
    public LibrosBean(List<LibroDomain> libros) {
        this.libros = new ArrayList<LibroBean>(libros.size());
        for(LibroDomain libro: libros) {
            this.libros.add(new LibroBean(libro));
        }
    }
     
    public List<LibroBean> getLibros() {
        return libros;
    }
 
    public void setLibros(List<LibroBean> libros) {
        this.libros = libros;
    }  
}

Añade a esta clase las etiquetas necesarias para que la lista de libros se englobe en una etiqueta de nombre libros, y dentro de ella tengamos cada libro como un elemento de nombre libro.

Listado y búsqueda

Cuando accedamos mediante GET a /libros, obtendremos la lista de todos los libros registrados en la biblioteca. Mediante una serie de parámetros podremos realizar búsquedas o paginar la lista de libros.

Si especificamos un parámetro keyword en la query, realizará una búsqueda de los libros que contengan la palabra especificada en su título o autor.

http://localhost:8080/jbib-rest/resources/libros?keyword=Patterns
<libros>
    <libro autor="Clifton Nock" id="1" isbn="0131401572" 
           numPaginas="512" titulo="Data Access Patterns"/>
    <libro autor="Martin Fowler" id="2" isbn="0321127420" 
           numPaginas="533" 
           titulo="Patterns Of Enterprise Application Architecture"/>
</libros>

Si especificamos los parámetros firstResult y maxResults se mostrarán sólo maxResults libros en la lista empezando desde el que se encuentra en la posición firstResult en la lista completa.

http://localhost:8080/jbib-rest/resources/libros?firstResult=1&maxResults=1
<libros>
    <libro autor="Martin Fowler" id="2" isbn="0321127420" 
           numPaginas="533" 
           titulo="Patterns Of Enterprise Application Architecture"/>
</libros>

Si no se especifica ninguno de los parámetros anteriores, se obtendrá el listado de todos los libros.

http://localhost:8080/jbib-rest/resources/libros
<libros>
    <libro autor="Clifton Nock" id="1" isbn="0131401572" 
           numPaginas="512" titulo="Data Access Patterns"/>
    <libro autor="Martin Fowler" id="2" isbn="0321127420" 
           numPaginas="533" 
           titulo="Patterns Of Enterprise Application Architecture"/>
    <libro autor="Eric Newcomer and Greg Lomow" id="3" 
           isbn="0321180860" numPaginas="465" 
           titulo="Understanding SOA with Web Services"/>
</libros>

Todas las operaciones anteriores deben implementarse en un único método de LibrosResource, que según los parámetros recibidos realizará un filtrado distinto de los libros utilizando para ello las funciones implementadas en los objetos de negocio.

Consulta de libros

Si a la ruta raíz /libros le añadimos el id de un libro, y accedemos a esta nueva ruta mediante GET, veremos los datos de un libro concreto. Esto lo implementaremos en el subrecurso LibroResource. Si el id especificado no existe en la base de datos, devolverá un código 404 Not found.

http://localhost:8080/jbib-rest/resources/libros/3
<libro autor="Eric Newcomer and Greg Lomow" id="3" isbn="0321180860" 
       numPaginas="465" titulo="Understanding SOA with Web Services"/>

Imágenes de las portadas

Podremos también obtener las portadas de los libros de la base de datos. Para acceder a ellas añadiremos a la ruta de un libro individual, tras el id, el fragmento /imagen. Haciendo GET sobre dicha ruta obtendremos la imagen codificada mediante image/jpeg.

Para tener accesibles las imágenes primero descargaremos este pack de portadas y las copiaremos al directorio /imagenes de nuestro proyecto. Tras esto, podremos utilizar un código como el siguiente para obtener una imagen:

@GET
@Path("/imagen")
@Produces("image/jpeg")
public Response getImagen(@Context ServletContext sc) {		
    InputStream is = 
        sc.getResourceAsStream("/imagenes/" + 
                               this.libro.getIsbn() + ".jpg");
    if(is==null) {
        return Response.status(Status.NOT_FOUND).build();
    } else {
        return Response.ok(is).build();			
    }
}

Podemos ver que hemos inyectado el objeto ServletContext para poder localizar así la ruta en el disco donde están las imágenes y abrir un flujo de entrada para leerlas. Si la imagen existe devolvemos directamente el flujo de entrada para que los bytes de la imagen JPEG se envíen al cliente. Si no existe devolvemos 404 Not found.

Estilo HATEOAS

Vamos a añadir ahora enlaces a las representaciones anteriores siguiendo el estilo HATEOAS, con la codificación Atom. Para ello podemos utilizar una clase LinkBean como la vista en el módulo, y modificar LibroBean para que incluya una lista de enlaces.

En el caso del listado de libros, cada libro deberá incluir un enlace con relación self que apuntará a la URI que da acceso al libro individual.

<libros>
  <libro autor="Clifton Nock" id="1" isbn="0131401572" numPaginas="512" 
         titulo="Data Access Patterns">
    <link href="http://localhost:8080/jbib-rest/resources/libros/1" 
          rel="self"/>
  </libro>
  <libro autor="Martin Fowler" id="2" isbn="0321127420" numPaginas="533" 
         titulo="Patterns Of Enterprise Application Architecture">
    <link href="http://localhost:8080/jbib-rest/resources/libros/2" 
          rel="self"/>
  </libro>
  <libro autor="Eric Newcomer and Greg Lomow" id="3" isbn="0321180860" 
         numPaginas="465" titulo="Understanding SOA with Web Services">
    <link href="http://localhost:8080/jbib-rest/resources/libros/3" 
          rel="self"/>
  </libro>		
</libros>

En el caso de la consulta de un libro individual, deberemos obtener los siguientes enlaces:

  • self: Apunta a la misma URI del libro actual.
  • libro/imagen: Apunta a la URI en la que se encuentra la imagen del libro actual.
<libro autor="Eric Newcomer and Greg Lomow" id="3" isbn="0321180860" 
       numPaginas="465" titulo="Understanding SOA with Web Services">
  <link href="http://localhost:8080/jbib-rest/resources/libros/3" 
        rel="self"/>
  <link href="http://localhost:8080/jbib-rest/resources/libros/3/imagen" 
        rel="libro/imagen"/>
</libro>

Acceso a ejemplares

Vamos a añadir la posibilidad de acceder a los ejemplares de los libros. Para ello, añadiremos el fragmento /ejemplares a la ruta de un libro:

http://localhost:8080/jbib-rest/resources/libros/3/ejemplares

Con esto veremos la lista de ejemplares del libro indicado, envuelta en una etiqueta con los datos del libro:

<libro autor="Eric Newcomer and Greg Lomow" id="3" isbn="0321180860" 
       numPaginas="465" titulo="Understanding SOA with Web Services">
  <ejemplares>
    <ejemplar fechaAdquisicion="2007-01-25T00:00:00+01:00" id="6" 
              localizacion="SALA" numEjemplar="002">
      <link href="http://localhost:8080/jbib-rest/resources/ejemplar/6" 
            rel="self"/>
    </ejemplar>
    <ejemplar fechaAdquisicion="2007-01-25T00:00:00+01:00" 
              fechaDevolucion="2012-12-12T00:00:00+01:00" 
              fechaPrestamo="2012-12-05T00:00:00+01:00" 
              id="5" localizacion="SALA" numEjemplar="001">
      <link href="http://localhost:8080/jbib-rest/resources/ejemplar/5" 
            rel="self"/>
    </ejemplar>
  </ejemplares>
</libro>

Para hacer esto:

  • Crearemos un nuevo bean JAXB EjemplarBean.
  • Introduciremos una lista de ejemplares en el objeto LibroBean (sólo tomará un valor cuando se solicite la lista de ejemplares).
  • Introducimos en LibroResource la operación necesaria para obtener la lista de ejemplares.
  • Al obtener un libro, deberemos añadir el enlace que nos da acceso a sus ejemplares:
<libro autor="Eric Newcomer and Greg Lomow" id="3" isbn="0321180860" 
       numPaginas="465" titulo="Understanding SOA with Web Services">
  <link href="http://localhost:8080/jbib-rest/resources/libros/3" 
        rel="self"/>
  <link href="http://localhost:8080/jbib-rest/resources/libros/3/imagen" 
        rel="libro/imagen"/>
  <link href=
         "http://localhost:8080/jbib-rest/resources/libros/3/ejemplares" 
        rel="libro/ejemplares"/>
</libro>

Por último, tendremos que dar la posibilidad de acceder a un ejemplar individual. La ruta que nos dará acceso a ellos es:

http://localhost:8080/jbib-rest/resources/ejemplar/5

Hay que destacar que ya no está bajo la ruta del libro, sino que los ejemplares individuales tienen su propia ruta y su propio recurso raíz. Del ejemplar obtendremos la siguiente información:

<ejemplar fechaAdquisicion="2007-01-25T00:00:00+01:00" 
          fechaDevolucion="2012-12-12T00:00:00+01:00" 
          fechaPrestamo="2012-12-05T00:00:00+01:00" id="5" 
          localizacion="SALA" numEjemplar="001">
  <link href="http://localhost:8080/jbib-rest/resources/ejemplar/5" 
        rel="self"/>
</ejemplar>

Para hacer esto implementaremos un nuevo recurso raíz EjemplarResource que nos permita obtener ejemplares individualmente proporcionando su id.

Petición de préstamos

Vamos también a permitir pedir préstamos mediante REST. Para ello añadiremos a la ruta de un ejemplar individual el fragmento /prestamo, y realizaremos POST sobre esta ruta. Esta operación sólo la podrán realizar los usuarios de tipo alumno o profesor, si cualquier otro usuario intentase pedir un préstamo obtendrá un código 403 Forbidden. Para realizar esta comprobación inyectaremos un objeto de tipo SecurityContext.

La petición del préstamo se realizará para el usuario autentificado actualmente. Podemos obtenerlo mediante el método getUserPrincipal().getName() del contexto de seguridad.

Nota
Para probar esta funcionalidad ya no nos bastará con un navegador web, ya que debemos hacer una petición POST. Podemos utilizar el cliente genérico Swing visto en clase para realizar estas pruebas. Se debe introducir un login y password para poder realizar la operación, ya que está protegida mediante seguridad declarativa.

Una vez realizado el préstamo, esta operación nos devolverá los datos del ejemplar en los que ya figurará la fecha de préstamo y la fecha de devolución.

Además, haremos que los ejemplares que estén disponibles para préstamo tengan un enlace con relación ejemplar/prestamo a la URI correspondiente a la petición de préstamo. En caso de que el ejemplar no esté disponible, dicho enlace no debe aparecer.

<ejemplar fechaAdquisicion="2007-01-25T00:00:00+01:00" 
          fechaDevolucion="2012-12-12T00:00:00+01:00" 
          fechaPrestamo="2012-12-05T00:00:00+01:00" id="5" 
          localizacion="SALA" numEjemplar="001">
  <link href="http://localhost:8080/jbib-rest/resources/ejemplar/5" 
        rel="self"/>
  <link href=
         "http://localhost:8080/jbib-rest/resources/ejemplar/5/prestamo" 
        rel="ejemplar/prestamo"/>
</ejemplar>

Resumen

Como ayuda, proporcionamos la siguiente figura con la estructura de directorios resultante de esta sesión.

Para la entrega se deberá etiquetar repositorio bitbucket con el tag entrega-proyint-rest.