jueves, 30 de octubre de 2014

JAX-RS + Jersey-Filtros e interceptores

Filtros e interceptores

Filtros e interceptores son objetos que pueden ser utilizados en el procesamiento de solicitudes o respuestas del servidor.

Estos objetos permiten encapsular comportamiento relacionado con el procesamiento de aspectos del protocolo o infraestructura de la aplicación y que no deseamos manejar en el modelo de negocios.

Los filtros e interceptores pueden ser utilizados tanto del lado del cliente como del lado del servidor.

Filtros

Los filtros pueden modificar solicitudes y respuestas al servidor, incluyendo los headers y otros parametros de request/response.

Interceptores

Los interceptores son utilizados para modificar el contenido del mensaje, tanto de entrada como de salida. Por ejemplo comprimir y descomprimir o encriptar y desencriptar mensajes.

Configuración del orden de ejecución de filtros e interceptores

Si tenemos más de un filtro o interceptor, debemos definir el orden en el que deseamos que se ejecuten. En JAX-RS el orden de ejecución se puede fijar por medio de la anotación @Priority y en tiempo de ejecución el orden será determinado por medio del valor asignado, ejecutando primero los números más pequeños.

Si no se configura, los filtros e interceptores aplican a todas las solicitudes y respuestas del servidor, sin embargo se puede aplicar a métodos de recursos seleccionados por medio del registro de una implementación de la interfaz DinamicFeature o por medio de anotaciones. Para más información consultar la sección dynamic binding y name binding de la página oficial de Jersey.

Descomprimir/Comprimir solicitudes y respuestas del servidor

Vamos a crear un interceptor para comprimir y descomprimir los mensajes

Primero veremos como descomprimir el contenido del mensaje, para que cuando se ejecute el recurso deseado, los datos vayan en su formato no comprimido. Vamos a crear la clase CompresionReaderInterceptor con el siguiente código:

@Provider
public class CompresionReaderInterceptor implements ReaderInterceptor {
 @Override
 public Object aroundReadFrom(ReaderInterceptorContext context)
   throws IOException, WebApplicationException {
  String encoding = context.getHeaders().getFirst(CompresionWriterInterceptor.CONTENT_ENCODING_HEADER);
  
  if (!requestHasContentEncodingHeader(encoding)) {
   return context.proceed();
  }
  
  GZIPInputStream gzipInputStream = new GZIPInputStream(context.getInputStream());
  context.setInputStream(gzipInputStream);  
  return context.proceed();
 }

 private boolean requestHasContentEncodingHeader(String encoding) {
  return CompresionWriterInterceptor.CONTENT_ENCODING_HEADER_VALUE
    .equalsIgnoreCase(encoding);
 }
}

Ahora vamos a crear la clase CompresionWriterInterceptor para comprimir el contenido del mensaje una vez que se ha procesado el recurso solicitado.

@Provider
public class CompresionWriterInterceptor implements WriterInterceptor {
 public static final String CONTENT_ENCODING_HEADER_VALUE = "gzip";
 public static final String CONTENT_ENCODING_HEADER = "Content-Encoding";

 @Override
 public void aroundWriteTo(WriterInterceptorContext context)
   throws IOException, WebApplicationException {
  GZIPOutputStream gzipOuputStream = new GZIPOutputStream(
    context.getOutputStream());
  context.getHeaders().putSingle(CONTENT_ENCODING_HEADER,
    CONTENT_ENCODING_HEADER_VALUE);
  context.setOutputStream(gzipOuputStream);
  context.proceed();
  return;
 }
}

La anotación @Provider es la que permite que se detecte la clase como un elemento de interés para el servicio. Esta clase también puede ser registrada en la implementación de Application.

Aplicando filtros por medio de anotaciones

Como se mencionó antes si no se configura, los filtros e interceptores aplican a todos los request y responses del servicio. Vamos a ver como aplicarlo a solo un método de un recurso.

Necesitamos crear la siguiente anotación:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import javax.ws.rs.NameBinding;

/**
 * Permite especificar los recursos a los que se requiere aplicar un algoritmo
 * para comprimir y descomprimir mensajes.
 * 
 * @author Clemente Morales Fernández
 * @since Oct 30, 2014
 *
 */
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Compress {
}

Una vez creada la anotación lo que haremos será aplicarla en el método del recurso que deseamos aplicar compresión en la respuesta y al interceptor encargado de realizar la compresión.

@Compress
public class CompresionWriterInterceptor implements WriterInterceptor {
}

/**
 * Proporciona las operaciones disponibles para el recurso seguro.
 * 
 * @author Clemente Morales Fernández
 *
 */
@Path("seguros")
public interface SegurosService {
 /**
  * Permite obtener los seguros dados de alta por un cliente.
  * @param idCliente Identificador del cliente.
  * @return Lista de seguros del cliente.
  */
 @Path("/{idCliente}")
    @GET
    @Produces("application/json")
 @Compress
    List getSegurosCliente(@PathParam("idCliente") int idCliente);
}

En la siguiente entrada veremos como crear y configurar filtros e interceptores del lado del cliente.

viernes, 3 de octubre de 2014

Consumiendo recurso JSON con Jersey + Jackson + Gradle

Esta semana empecé a trabajar con la herramienta de construcción Gradle y en esta ocasión quiero conmpartir mi experiencia con la herramienta gradle, más la deserialización de la respuesta Json a un modelo de objetos. Para consultar la configuración del cliente para el consumo del recurso REST ir a la entrada anterior.

Instalando gradle

Vamos a empezar instalando gradle. Para esto les comparto la liga que he utilizado para hacerlo en mi entorno de desarrollo. Una vez terminado el proceso verificamos nuestra versión instalada por medio del comando:

gradle -version

Creando la estructura del proyecto

Primero que nada Gradle espera que el código de nuestra aplicación se encuentre en la estructura src/main/java y el código de nuestras pruebas en src/test/java.

Además cualquier archivo colocado bajo src/main/resources será incluido en el archivo JAR como recurso y cualquie archivo colocado sobre src/test/resources será incluido en el classpath utilizado para correr las pruebas unitarias.

Todos los archivos de salida son colocados en el directorio de construción build/libs.

Bueno para empezar vamos a crear la siguiente estructura de directorios:

-- JerseyConsumer
  -- src
    -- main
   -- java
     -- mx
    -- org
      -- mi empresa
   -- resources
    -- test
   -- java
   -- resources

Listo ya tenemos la estructura requerida por gradle para la aplicación.

Creando el archivo de construcción gradle

Necesitamos ahora indicar la configuración de construcción para nuestra aplicación. Para eso vamos a crear los siguientes dos archivos en el directorio JerseyConsumer.

Archivos configuración gradle

Vamos a abrir el archivo build.gradle con nuestro editor favorito y agregamos el siguiente bloque de código:

apply plugin: 'java'

repositories {
 mavenCentral()
}

dependencies {
 compile group:'org.glassfish.jersey.core', name:'jersey-client', version:'2.11'
        compile group:'org.glassfish.jersey.media', name:'jersey-media-json-jackson', version:'2.11'
 compile group:'org.apache.commons', name:'commons-lang3', version:'3.1'
}

task execute(type:JavaExec) {
   main = 'mx.org.miempresa.RestConsumer'
   classpath = sourceSets.main.runtimeClasspath
}

Algunas notas importantes el archivo de configuración:

  • apply plugin. Indica que trabajaremos con el plugin de java para gradle. Viene con la instalación por default de gradle.
  • repositories. Indicamos que utilizaremos el repositorio central de dependencias de maven. Es una de las razones por las que me gusta gradle, que permite utilizar varios repositorios de dependencias.
  • dependencies. Indica las dependencias de la aplicación. En este caso jersey-client (consumo recursos), jackson (deserialización de json response), commons-lang3 (utilerías para el API java.lang)
  • task execute. En gradle podemos utilizar las tareas proporcionadas por el pluging o crear nuevas extendiendo las existentes. En este caso extendemos la tarea JavaExec para pasarle el parámetro main (clase con el método main) y el classpath donde encontrará las dependencias.

Vamos a abrir el archivo gradle.properties con nuestro editor favorito y agregamos la siguiente configuración:

org.gradle.java.home=C:\\Program Files (x86)\\Java\\jdk1.7.0.67

En este archivo indicamos el jdk de java a utilizar para compilar nuestra aplicación.

Consumiendo el recurso y deserializando JSON a objetos:

Vamos a crear la clase RestConsumer en el paquete mx.org.miempresa

package mx.org.miempresa;

import java.util.List;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;

import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.jackson.JacksonFeature;

public class RestConsumer 
{
    public static void main(String[] args) {
        int idCliente = 23;
        
        ClientConfig cc = new ClientConfig().register(new JacksonFeature());
        Client client = ClientBuilder.newClient(cc);
        WebTarget rootTarget = client
                .target("http://cmoralesflap:8080/MiServicio/");
        WebTarget pagosTarget = rootTarget.path("pagos").path("{idCliente}")
                .resolveTemplate("idCliente", idCliente);

        Invocation.Builder builder = pagosTarget.request(MediaType.TEXT_PLAIN);
        builder.accept(MediaType.APPLICATION_JSON);
        
        List<Pago> pagoResponse = 
                builder.get().readEntity(new GenericType<List<Pago>>(){});
        
        for(Pago pago : pagoResponse) {
            System.out.println("Pago " + pago);    
        }
        
        client.close();         
    }
}

Algunas notas importantes el archivo de configuración:

  • List pagoResponse. Ahora la respuesta esperada es una lista de objetos Pago.
  • readEntity(new GenericType<List<Pago>>(){}). Indicamos que esperamos una lista de objetos.

Nota. He modificado el recurso REST para que me devuelva una colección de Pagos. A continuación adjunto los cambios requeridos en el recurso publicado.

@Path("pagos/{idCliente}")
public interface IPagoResource {
    @GET
    @Produces("application/json")
    List getPagoByIdCliente(@PathParam("idCliente") int idCliente);
}

public class PagoResource implements IPagoResource {
    public List<Pago> getPagoByIdCliente(int idCliente) {
        List<Pago> pagos = new ArrayList<Pago>();
        pagos.add(new Pago (23, new BigDecimal("520.56")));
        pagos.add(new Pago (23, new BigDecimal("380.08")));
        return pagos;
    }
}


Ahora creamos la clase Pago en el paquete mx.org.miempresa

package mx.org.miempresa;

import java.math.BigDecimal;
import org.apache.commons.lang3.builder.ToStringBuilder;

public class Pago {
    private int id;
    private int idCliente;
    private BigDecimal cantidad;

    public Pago() {
    }

    public int getIdCliente() {
        return idCliente;
    }

    public void setIdCliente(int idCliente) {
        this.idCliente = idCliente;
    }

    public BigDecimal getCantidad() {
        return cantidad;
    }

    public void setCantidad(BigDecimal cantidad) {
        this.cantidad = cantidad;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
    
    @Override
    public String toString() {
        return new ToStringBuilder(this).append("idCliente", idCliente)
                .append("cantidad", cantidad).toString();
    }
}

Compilando la aplicación:

Para compilar la aplicación ejecutamos el siguiente comando:

gradle build

Ejecutando la aplicación:

Para ejecutar la aplicación ejecutamos el siguiente comando:

gradle execute
Ejecutando la aplicación

Bueno, hasta aquí terminamos el consumo del recurso REST. En la siguiente entrada veremos como crear filtros e interceptores.