Quantcast
Channel: Adictos al trabajo
Viewing all 997 articles
Browse latest View live

Implementación de Emisor y Receptor básicos con RabbitMQ

$
0
0
En este tutorial se va a mostrar como implementar un emisor y un receptor que utilicen el protocolo AMQP (Advanced Message Queuing Protocol).

0. Índice de contenidos.

1. Introducción.

En este tutorial se va a mostrar como implementar un emisor y un receptor que utilicen el protocolo AMQP (Advanced Message Queuing Protocol). Para ello vamos a utilizar el servidor de intercambio RabbitMQ. Se trata de un servidor open source que implementa el protocolo AMQP, escrito en Erlang y construido sobre el framework Open Telecom Platform (OTP). La versiones utilizadas en este tutorial son:
  • JDK 1.8.0_40
  • Servidor RabbitMQ para mac v3.5.3.
  • Cliente RabbitMQ v3.5.3

2. Entorno.

El tutorial está escrito usando el siguiente entorno:
  • Hardware: MacBook Pro 17′ (2.66 GHz Intel Core i7, 8GB DDR3 SDRAM).
  • Sistema Operativo: Mac OS X Lion 10.10.3.
  • NVIDIA GeForce 330M 512Mb.
  • Crucial MX100 SSD 512 Gb.

3. Instalación y arranque del servidor.

Lo primero que tenemos que hacer es descargarnos el servidor desde la página oficial desde este enlace.

A continuación procedemos con la descompresión del contenido del fichero en la ruta que nos convenga, en mi caso:

/Users/jrodriguez/rabbitmq-server

Para el caso que vamos a ver en este tutorial no necesitamos configurar el servidor, así que podemos arrancar el servidor de intercambio dirigiendonos a la carpeta “sbin” de la instalación del servidor y ejecutando el shell script “rabbitmq-server”.

4. Configuración proyecto.

Abrimos nuestro IDE, en mi caso Eclipse Luna, y procedemos con la creación de un nuevo proyecto Maven. Existen multiples tutoriales, en AdictosAlTrabajo, en los que explicamos como crear un proyecto Maven.

Una vez creado el proyecto, y para finalizar la configuración, agregamos dentro del fichero pom.xml la dependencia del cliente de RabbitMQ:

com.rabbitmq
amqp-client
3.5.3

5. Implementación de Emisor y Receptor.

5.1 Emisor

Agregamos una clase al proyecto llamada Emisor con el siguiente contenido:

package com.rabbitmq.basic;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Emisor {

  private final static String QUEUE_NAME = "MAIN_QUEUE";
  
  public static void main(String[] args) throws IOException, TimeoutException {

    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    Connection connection = factory.newConnection();
    Channel channel = connection.createChannel();
    
    channel.queueDeclare(QUEUE_NAME, false, false, false, null);
    String message = "¡Hola!";
    channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
    System.out.println(" [x] Enviar '" + message + "'");

  }

}

Vamos a dividir el código anterior en dos partes para explicar qué hace.

ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();

Por un lado tenemos el establecimiento de la conexión y del canal de comunicaciones. Para ello hacemos uso de una factoría de conexiones, establecemos el Host, generamos una nueva conexión y creamos un nuevo canal de comunicaciones a través de la conexión.

channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "¡Hola!";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" [x] Enviar '" + message + "'");

Por otro lado, establecemos las propiedades del canal y lo utilizamos para publicar un mensaje en el mismo.

5.2 Receptor

Agregamos una clase al proyecto llamada Receptor con el siguiente contenido:

package com.rabbitmq.prueba2;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.ConsumerCancelledException;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.ShutdownSignalException;

public class Receive {

  private final static String QUEUE_NAME = "hello";

  public static void main(String[] args) throws IOException,
      TimeoutException, ShutdownSignalException,
      ConsumerCancelledException, InterruptedException {

    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    Connection connection = factory.newConnection();
    Channel channel = connection.createChannel();

    channel.queueDeclare(QUEUE_NAME, false, false, false, null);
    System.out.println(" [*] A la espera de mensajes. Para salir pulse: CTRL+C");
    QueueingConsumer consumer = new QueueingConsumer(channel);
    channel.basicConsume(QUEUE_NAME, true, consumer);

    while (true) {
      QueueingConsumer.Delivery delivery = consumer.nextDelivery();
      String message = new String(delivery.getBody());
      System.out.println(" [x] Recibido: '" + message + "'");
      doWork(message);
      System.out.println(" [x] Hecho!!! ");
    }
  }

  private static void doWork(String task) throws InterruptedException {
    Thread.sleep(8000);
  }

}

Como en el caso del amos a dividir el código anterior en 3 partes para explicar qué hace.

ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();

Como en el caso anterior, en la primera sección de código tenemos el establecimiento de la conexión y del canal de comunicaciones.

channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] A la espera de mensajes. Para salir pulse: CTRL+C");
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(QUEUE_NAME, true, consumer);

A continuación, configuramos el canal de comunicaciones e instanciamos un consumidor que va a ser el encargado de obtener la información del canal.

while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
System.out.println(" [x] Recibido: '" + message + "'");
doWork(message);
System.out.println(" [x] Hecho!!! ");
}

Para finalizar establecemos un bucle que se encargue de estar a la escucha, constantemente, de mensajes enviados al canal. Hemos introducido un método “doWork” que simplemente emula tareas internas del hilo.

6. Visualización de resultados.

Basta con lanzar cada uno de los ejecutables (el emisor en varias ocasiones), mientras el servidor intermediario se encuentra en ejecución para observar el proceso de comunicación entre los emisores y el receptor.

7. Conclusión.

Hemos visto como con RabbitMQ podemos crear un proceso de comunicación emisor-receptor de una manera rápida y sencilla, lo que demuestra (en parte) la potencia de este servidor de intercambio de mensajes.

Norma ITU/CCIR 601 Y Cuantificación Digital

$
0
0

En este tutorial voy a hablar de el muestreo de una señal digital y para ello voy a mencionar antes la norma ITU/CCIR 601.

Hoy en día cientos de millones de aparatos de televisión digital en todo el mundo funcionan sobre la base de una Recomendación de la UIT, la norma ITU 601, y ha sido el documento técnico más citado y utilizado en la historia de la televisión.

Las siglas del nombre vienen de Sector de Radiocomunicaciones de la UIT (UIT–R) y de su predecesor, el Comité Consultivo Internacional de Radiocomunicaciones (conocido por su acrónimo francés, CCIR). En el otoño de 1981 la Comisión de Estudio 11 del CCIR aprobó un documento en el que se describen los valores parámetro de un formato de vídeo digital, “Parámetros para la codificación en estudio de la televisión digital con un formato de imagen de norma 4:3 y pantalla amplia de 16:9″.

A principios de 1970 se hicieron experimentos donde se transformaban las señales analógicas compuestas existentes PAL, SECAM y NTSC en versiones digitales. En términos técnicos esto se denomina “codificación digital compuesta”.

Se tuvo que elegir una frecuencia de muestreo, es decir, la velocidad a la cual se examina la señal analógica y ésta se convierte en números digitales. Pero no obstante, no fue posible encontrar una única frecuencia de muestreo que sirviera simultáneamente para los sistemas PAL, SECAM y NTSC a causa de las modalidades de desarrollo de dichos sistemas. Había que tener en cuenta una serie factores en la elección de una frecuencia de muestreo a escala mundial: la calidad de la imagen debía ser suficientemente buena y la señal digital debía ser capaz de soportar los diversos procesos de postproducción de televisión (“margen de calidad para el postprocesamiento”), para el cual se necesitaban como mínimo 13 MHz.

Entre todas las frecuencias de muestreo posibles para la señal de luminancia, 13,5 MHz tenía un atributo único: es un múltiplo común de las frecuencias de línea de ambas estructuras de exploración de 525/60 y 625/50, los dos sistemas podían ofrecer el mismo número de muestras por línea activa. Esta fue la frecuencia de muestro que se estableció creando un sistema digital común para la televisión de todo el planeta; 13,5 MHz (señal de luminancia Y) y 6,75 MHz (señales de diferencia de color U, V).

¿Qué es la cuantificación y el muestreo digital?

El muestreo digital es una de las partes de digitalización de las señales donde se van tomando muestras de una señal analógica a una frecuencia para después cuantificarla y codificarla. Dicho de otra manera, es la cantidad de veces que medimos el valor de la señal en un periodo de tiempo (generalmente de 1 segundo).

Todo esto del muestreo está basado en el Teorema del muestreo de Nyquist-Shannon y básicamente lo que nos dice este teorema es que la cantidad de veces que debemos medir una señal para no perder información debe de ser al menos el doble de la frecuencia máxima que alcanza dicha señal. (Por ejemplo, si queremos grabar una conversación telefónica y el ancho de banda de la red telefónica es de 3khz, para no perder información deberemos tomar 6.000 muestras/segundo).

La cuantificación mide el nivel de voltaje de cada una de las muestras. Asigna un margen de valor de una señal analizada a un único nivel de salida; (la muestra toma un valor digital).

La codificación traduce los valores analógicos obtenidos durante la cuantificación al código binario.

Conversión analógica-digital

Convertir una señal analógica a digital facilita su procesamiento; su codificación, compresión, etc, y hacer que la señal digital resultante sea más inmune al ruido y otras interferencias a las que son más sensibles las señales analógicas. Lo convierte en una señal con más calidad.

Conversor AD.svg «Conversor AD» por JmcalderonTrabajo propio. Disponible bajo la licencia Dominio público vía Wikimedia Commons.

¿Por qué digitalizar?

  • Si una señal digital experimenta interferencias o perturbaciones leves, puede reconstruirse y ser amplificada mediante sistemas de regeneraciones de señales.
  • Su procesamiento es más fácil a través de cualquier software de edición o procesamiento de señal.
  • Permite la multigeneración infinita sin pérdidas de calidad.
  • Es posible aplicar técnicas de compresión de datos sin pérdidas o técnicas de compresión con pérdidas basados en la codificación perceptual mucho más eficientes que con señales analógicas.

Pero como todo lo que tiene una serie de ventajas, también encontramos algunos inconvenientes:

  • Se necesita una conversión analógica-digital previa y una decodificación posterior, en el momento de la recepción.
  • i no empleamos un número suficiente de niveles de cuantificación en el proceso de digitalización, la relación señal/ruido resultante se reducirá con relación a la de la señal analógica original que se cuantificó.
  • bajo; (solo deja pasar la parte baja de la frecuencia), sobre la señal a muestrear para evitar el efecto conocido como aliasing, que podría hacer que componentes de frecuencias fuera de la banda de interés quedaran registrados como componentes falsos de frecuencia dentro de la banda de interés.

Dicho todo esto de otra manera para que quede más claro, el efecto de aliasing es un efecto de ‘barrido’ no deseado que aparece cuando la frecuencia de muestreo es demasiado baja para reproducir fielmente los detalles de la imagen, por ejemplo efectos de parpadeo en contornos muy marcados tales como líneas horizontales. También se conoce como efecto Moiré en algunos casos:

Moire pattern of bricks.jpg
«Moire pattern of bricks». Disponible bajo la licencia CC BY-SA 3.0 vía Wikimedia Commons.

Imagen correctamente muestreada de una muralla de ladrillos

Moire pattern of bricks small.jpg
«Moire pattern of bricks small». Disponible bajo la licencia CC BY-SA 3.0 vía Wikimedia Commons.

Aliasing espacial en la forma de Patrón de Moiré

Muestreo de color

Para procesar video en el dominio digital, se necesita un muestreo que forma las líneas horizontales y columnas verticales. La frecuencia de muestreo (sampling rate), tiene que ser un múltiplo de la frecuencia de líneas, de aquí se establecieron las nomenclaturas 4:4:4, 4:2:2, 4:2:0 que hacen referencia a la luminancia y a la crominancia.

El ojo es más sensible a la luminancia que a la crominancia. La información de luminancia siempre la va a respetar en todos los píxeles, por eso el primer número siempre va a ser un 4, porque siempre habrá información de Y. Pero no tiene por qué ser así con la crominancia.

Si se muestrea toda la señal: 4; luminancia en todos los píxeles.
Si muestreamos la mitad: 2, sólo en dos de cada cuatro habrá información de color.
Si muestreamos 1/4: 1, sólo habrá un píxel de cada cuatro con información de color.

Captura de pantalla 2015-06-17 a las 12.39.25


La imagen nos muestra píxeles de un cuadro de video en una matriz 4:4:4. Al analizar la imagen vemos que cada línea contiene píxeles con 4 valores Y, 4 Cb y 4 Cr. Este esquema es el estándar por excelencia del color pero no hay muchos equipos que trabajen así y la mayoría graba a 4:2:2.

Usado para infografías y cine digital.


Captura de pantalla 2015-06-17 a las 12.41.39

4:2:2
Aquí la mitad de la información de los pixeles se ha eliminado. Los equipos y programas que trabajan así pueden coger la información de los píxeles adjuntos y reconstruir la información que falta.

Calidad de estudio.



Captura de pantalla 2015-06-17 a las 12.41.46


Algunos formatos como MPEG usan el formato 4:2:0 que tendría un esquema así. El valor de luminancia existiría en todas las líneas para cada píxel pero de cada línea, solo habría dos píxeles con información Cb y en otra con información Cr.

Instalar y configurar Ubuntu Server y Plesk con VirtualBox

$
0
0

En este tutorial veremos como instalar y configurar de forma rápida el gestor web Plesk 12 en una máquina virtual con Ubuntu Server 14.04 para poder tener acceso a las opciones de hosting de nuestros sitios (por ejemplo, WordPress).


0. Índice de contenidos.

1. Introducción.

Plesk es un grupo de herramientas de gestión de sitios y servidores web fácil de usar mediante un panel de control que permite tener a la vista todos los sitios web que gestiona. Esto es muy útil si se quieren gestionar varios sitios web a la vez (por ejemplo una página web personal, otra de empresa y otra dedicada a hobbies).

La versiones de los programas utilizadas en este tutorial son:

2. Entorno.

El tutorial está escrito usando el siguiente entorno:
  • Hardware: MacBook Pro 15′ (2.4 GHz Intel Core i5, 8GB DDR3 1067 MHz).
  • Sistema Operativo: Mac OS X Yosemite 10.10.3.
  • Gráfica: NVIDIA GeForce 330M 256Mb.
  • Disco: Crucial SSD 480 Gb.

3. Preparación de VirtualBox.

Suponemos que ya tenemos Virtual Box instalada en nuestro equipo. Necesitaremos una imagen ISO de Ubuntu Server. En la página de Odin se encuentra disponible una versión de Ubuntu Server 14.04 con Plesk 12 ya instalado. Nos bajamos la versión que corresponda a nuestra arquitectura (en nuestro caso, la de 64 bits).

Vamos a crear la máquina virtual. En la pantalla de inicio de Virtual Box seleccionamos la opción “Nueva” y saldrá una pantalla de selección de SO (en nuestro ejemplo, Ubuntu de 64 bits) y un nombre.

Al pulsar siguiente nos pide que elijamos la memoria. En este caso nos bastará con 2GB (2048 MB).

El siguiente paso es la elección de un disco duro. Seleccionamos “Crear un disco duro virtual ahora” para más tarde introducir la ISO que hemos descargado.

Al pulsar siguiente nos pide elegir el tipo de archivo de unidad de disco duro. En este paso seleccionamos la opción que viene por defecto (VDI, VirtualBox Disk Image) ya que no vamos a compartir los datos del mismo con otra máquina.

En la siguiente pantalla, indicar que deseamos reservar el tamaño dinámicamente, para que vaya usando disco físico gradualmente.

El siguiente paso es muy importante: elegir el tamaño del disco duro. Hay que tener cuidado, ya que una vez elegido no se podrá cambiar y en caso de necesitar más tendremos que crear de nuevo la máquina. En este punto se ha decidido darle un espacio de 60GB porque montaremos diferentes sites que así lo requieren. Estima en tu caso lo que vas a necesitar.

Una vez presionado el botón “Siguiente”, la máquina virtual estará lista, pero no tendrá nada en el disco duro (será como tener un ordenador vacío). Esto quiere decir que tendremos que instalar un Sistema Operativo como si de un ordenador normal se tratara. Pero ya habíamos descargado una imagen con el SO adecuado. Por tanto lo que queremos es instalar dicha imagen.
Para ello seleccionamos la máquina virtual y pulsamos en el icono de “Preferencias”, y acto seguido en “Almacenamiento”. Aquí veremos que disponemos de un disco duro vacío y una unidad de CD.

Lo que hay que hacer es lo siguiente:

  • Hacer click en el icono .
  • Aparecerá una selección de opciones en la que marcamos “Seleccionar Disco”.
  • Acto seguido, se mostrará una ventana del explorador de archivos para seleccionar la imagen ISO. Pulsamos la que queremos.
Debería quedar algo como la siguiente imagen:

Como se puede observar, aparece la imagen que hemos descargado.

Lo siguiente que se debe hacer es configurar el adaptador de red, ya que por defecto estará conectado a NAT y debemos ponerlo en Adaptador puente para que la máquina sea accesible desde cualquier otro ordenador al estar conectada directamente a la red como un ordenador físico (esto depende de tus necesidades). Para ello seleccionamos la pestaña “Red” y en la configuración del adaptador seleccionamos “Adaptador puente”.

Y ya está. Ya tenemos la máquina virtual preparada para instalar Ubuntu server.

4. Instalación de Ubuntu Server 14.04.

Si ya sabes instalar Ubuntu, puedes pasar al siguiente punto.

Una vez configurada la máquina virtual, la iniciamos haciendo doble click en ella o pulsando en el icono de la flecha verde.

Tras unos segundos aparecerán las ya conocidas opciones de configuración e instalación de Ubuntu (lenguaje, tipo de instalación, particiones…) en modo terminal. Si nunca has instalado Ubuntu Server, he aquí un resumen:

  • Selecciona el idioma de la instalación.
  • Selecciona si quieres instalar o sólo usar el Live CD (es decir, trabajar con el CD únicamente durante una sesión).
  • Suponiendo que hayamos decidido instalar, lo siguiente es seleccionar la ubicación (para la zona horaria y opciones de sistema).
  • Esperar un poco a que se cargue el sistema y la configuración de red.
  • Configurar usuario y contraseña. Introducir un nombre de usuario para el sistema y su contraseña.
  • Configurar las particiones (Ubuntu lo hace automáticamente bastante bien). En nuestro caso vamos a dejar las opciones por defecto.
  • Dejar que el sistema se instale (no suele tardar demasiado) y se reinicie.

Si todo ha ido bien, al final se mostrará una pantalla como la que sigue (login de Ubuntu):

Introducimos usuario y contraseña y dejamos que cargue la sesión. Ya tenemos Ubuntu instalado.

Ahora sería recomendable instalar las actualizaciones del sistema. Para conseguirlo, ejecutamos los comandos:

sudo apt-get update
sudo apt-get upgrade

Que actualizan la lista de paquetes y el sistema, respectivamente.

Lo siguiente es configurar la contraseña del usuario “root”, el superusuario. Esto es debido a que no tiene contraseña por defecto.

Escribimos el comando:

sudo passwd

Y aparecerá el siguiente mensaje:

[sudo] password for [tu usuario]:

Introducimos la contraseña y aparecerá lo siguiente:

Introduzca la nueva contraseña de UNIX:

Escribimos una contraseña para el superusuario (¡apúntala!) y la confirmamos. Tras estos pasos, debería salir el siguiente mensaje:

passwd: password updated successfully

Para usar los privilegios de superusuario basta con introducir el comando:

su

Y luego escribir la contraseña.

¡Y ya está! Ya tenemos Ubuntu actualizado y listo para instalar Plesk.

5. Instalación de Plex 12.

5.1. Pasos previos

Ahora sólo falta instalar Plesk. Pero antes, hay que configurar el nombre de dominio (hostname) y su FQDN (nombre de dominio completo).

Primero creamos un nombre de dominio nuevo. Basta con ejecutar el siguiente comando:

sudo vi /etc/hostname

Esto abrirá el editor vi en la terminal:

Como queremos que nuestro dominio se llame “adictosvirtual”, editamos el fichero y cambiamos la línea

ubuntu

por la línea

adictosvirtual

Esto se consigue pulsando la tecla “i” (comando insertar en vi) y procediendo como un editor normal. Una vez editada la linea, pulsamos ESC para salir de edición y escribimos:

:wq

para guardar y salir. Ejecutamos el comando:

sudo service hostname restart

para cambiar el nombre de dominio y acto seguido comprobamos que el nombre ha cambiado con el comando

hostname

Si lo hemos hecho bien, aparecerá el nuevo nombre de dominio:

Ahora hay que especificar el FQDN del dominio. Pero para ello necesitamos saber la IP del mismo; esto se consigue mediante el comando

ifconfig

cuya salida nos muestra lo siguiente:

De esta salida apuntamos la dirección recuadrada en rojo en la imagen anterior.

Ahora editamos el fichero /etc/hosts con el comando

sudo vi /etc/hosts

Esto mostrará lo siguiente:

Editamos el fichero de forma que quede lo siguiente (pulsando “i” para insertar y ESC para salir de dicho modo):

Como se observa, se ha añadido el hostname creado anteriormente con la IP que hemos anotado. Salimos de edición y guardamos el fichero (:wq). Comprobamos que hemos cambiado el nombre de dominio y su FQDN con los comandos:

hostname
hostname -f


5.2. Instalación

El siguiente paso es (por fin) instalar Plesk. Primero hemos de eliminar la aplicación apparmor, ya que tiene problemas de compatibilidad con lo que queremos instalar. Por lo tanto, ejecutamos

sudo apt-get remove apparmor

y eliminamos la aplicación.

Instalamos Plesk con el siguiente comando:

wget -O - http://autoinstall.plesk.com/one-click-installer | sh

En cuanto lo ejecutemos saldrá la siguiente información:

Esto indica que ya tenemos Plesk instalado (como era de esperar, ya que la propia ISO lo tenía preinstalado y al hacer el upgrade se ha instalado).

6. Configuración del Panel de Control de Plex.

Para acceder a nuestro Panel de control de Plesk, vamos a un navegador web e introducimos la siguiente dirección:

https://192.168.168.110:8443

Lo cual nos llevará al login del panel de control de Plesk (imagen de abajo). Si el navegador nos avisa de que es una conexión no segura, pulsamos “opciones avanzadas” (en Chrome) y “acceder al sitio”.

Introducimos los datos de nuestro superusuario (root y la contraseña que hemos creado al inicio) y se mostrarán los términos y condiciones de Plesk. Bajamos hasta el final (leyéndolos, por supuesto) y aceptamos.

La pantalla que se muestra a continuación son las opciones del servidor:

Aquí debemos introducir el nombre completo del host:

adictosvirtual.adictosaltrabajo.com

Dejamos la IP que apuntamos antes y cambiamos la contraseña de administrador (se puede dejar la misma, pero hay que repetirla).

Ahora se pide que seleccionemos el tipo de hosting y la vista del panel. En nuestro caso, el tipo es personalizado y la vista que elegimos es “Service Provider”, para que muestre todas las opciones en el panel.

Aceptamos y aparece el formulario de información del administrador:

Rellenamos los datos con nuestra información y pulsamos OK. Al hacerlo se nos muestra la vista del panel de control de Plesk:

Éste nos avisa de que no tenemos una licencia; vamos a la página de licencias y pedimos una o usamos una de prueba (para este tutorial usaremos una licencia de versión preliminar).

Usando la opción de preview, pulsamos en “install key” e introducimos la clave:

Nota: la clave es TEMPORAL

Una vez instalada la clave ya podemos empezar a añadir dominios.

7. Añadiendo Dominios en Plex.

Para añadir un nuevo dominio, primero hemos de crear la suscripción asociada. Vamos a “Subscriptions” y pulsamos “Add new Subscription”. Nos llevará a la pantalla de creación, donde introducimos los datos y pulsamos OK.

Ya debería aparecer en “Domains”. Pulsando en “Manage Hosting” accedemos a las opciones del sitio, donde podremos cambiar todo lo relativo a nuestro dominio y por ejemplo utilizar su estructura de ficheros (pulsando en “Files”).

Y ya está, ya tenemos un dominio registrado en Plesk. Ahora es vuestro turno de “jugar” con esta potente herramienta.

8. Conclusiones.

Hemos visto que Plesk es bastante fácil de instalar, y que permite gestionar varios portales web, lo que es un alivio a la hora de tener que controlar nuestros blogs y sitios web.

Comentando el libro: ENCAMBIO: APRENDE A MODIFICAR TU CEREBRO PARA CAMBIAR TU VIDA Y SENTIRTE MEJOR

$
0
0

Vuelvo a recuperar el hábito de leer y publicar resúmenes. Puedes encontrar otros en http://www.adictosaltrabajo.com/category/libros/

Hoy voy a hablar de este libro del que sólo puedo decir que me ha resultado grandioso. Como siempre advierto que comento lo que yo he entendido y aquellas cosas que me han llamado la atención, no porque no haya otras importantes (que tal vez ya conocía). Te diría que es un imprescindible a leer.

Algunas de estas cosas:

  • El 80% del presupuesto de salud de USA se gasta en cinco problemas relacionados con el comportamiento: alcoholismo, tabaquismo, obesidad, estrés y falta de ejercicio físico. Parece que tratar de aprender a cambiar de comportamiento ya justifica leer este libro.
  • Cambiar implica entrar en conflicto y admitir qué comportamientos pasados estaban mal, o que no siempre tienes que querer lo mismo en tu vida. Al cerebro no le gustan los cambios. Si en el trabajo alguien te lleva la contraria tu cerebro puede generar una ráfaga de adrenalina para proteger el status-quo.
  • Cada vez que tienes una experiencia tus neuronas se activan. Las conexiones neuronales al principio son débiles pero, tras la repetición, se producen enlaces fuertes.
  • Tienes millones de neuronas. Cada una de ellas tienen miles de dentritas. Las conexiones entre neuronas a través de las dentritas describen mapas. Cada pensamiento o habilidad tiene un mapa complejo. Por ejemplo, tu mapa “perro” incluye las razas que conoces, cuando te mordió uno, ladridos, etc.
  • Aunque los cerebros son inicialmente iguales se van creando unos mapas completamente distintos. Un ejemplo grandioso para explicar esto es que tú y yo podemos tener exactamente el mismo ordenador pero yo puedo poner reglas a las carpetas para que cuando añada un fichero se indexe, copie, transforme y envíe automáticamente a otro sitio. Por tanto, el valor aportado es distinto con la misma maquinaria.
  • POR TANTO, COMO NUESTROS MAPAS SON DISTINTOS, CUANDO DAS UNA SOLUCION A UN PROBLEMA NO ESTAS CAYENDO EN QUE EL CEREBRO DE OTRO NO TIENE LOS MISMOS MAPAS y por tanto puede no interpretarse ni ejecutarse como lo haría el tuyo. Pensaré en esto cuando peque dando consejos no pedidos.
  • Al cerebro le gusta el orden. Si una idea te gusta, buscas en tus mapas argumentos que las sostengan y conviertes asociaciones en hechos evidentes. Vives guiado por ideologías, creencias o marcos conceptuales que se han creado sub-conscientemente.
  • El procesamiento de mucha información es por aproximación: cuando lees, a partir de las primeras letras predices el resto ¿Acxxxx nx ex estx verxxx? Predecir es “la” función principal del cerebro.
  • No hay una realidad, sino la que quieres ver. Si piensas que el mundo es peligroso verás peligros en todos sitios. ¿O que te pasa cuando un niño da sus primeros pasos a tu lado? Todo lo ves como peligroso.
  • La mayor parte de lo que piensas, sientes y haces no esta bajo tu control consciente. El cerebro trata de ahorrar energía y utilizar mecanismos automáticos.
  • Hay que pensar en el cerebro como en un país. Pasan millones de cosas a tu alrededor, gestionados por grupos locales, pero a ti solo te interesan los “titulares o noticias destacadas”. Si bebes leche a ti te da igual como está la vaca.
  • Hay dos tipos de personas, las que creen que piensan que el cerebro es fijo (el que le tocó por genética) y las que piensan que es algo dinámico. Al primer grupo, si les dices que fallaron un test, no le importa más que el resultado (preguntas correctas e incorrectas) perdiendo atención cuando les das información valiosa para aprender.
  • Haces desaparecer las malas noticias de tu conciencia a través de la negación, idealización, proyección o racionalización (teorías de defensa del ego de Freud).
  • Cuando tienes estrés, el cerebro crea células madre con la idea de repararse cuando pare. Bajo presión el cerebro no puede cambiar porque tiene instinto de cuidarse.
  • Un hábito es el paso de un estímulo a un disparo sin pensar. Si fumas con los amigos tu cerebro se acostumbra. Es difícil quitar un mal hábito, hay que cambiarlo por otro positivo.
  • La experiencia que acumulas hace que a tu cerebro le cueste cambiar. Para conseguirlo TIENES QUE MANTENER A TU CEREBRO APRENDIENDO. INICIARTE EN COSAS EN LAS QUE ERES NOVATO: música, baile, otros deportes, etc.
  • SEIS DE CADA DIEZ PERSONAS SON INFELICES EN EL TRABAJO. TRES DE CADA CUATRO NO ESTAN INVOLUCRADOS. Por tanto, la productividad y creatividad serán bajas porque están relacionadas con las ganas, atención y disfrute. Mucha gente quiere seguridad económica, independencia y libertad pero muy pocos hacen el esfuerzo que requiere adquirir las habilidades para conseguirlo.
  • Hay veces que nos quedamos secuestrados por los estímulos. Imagina que tu jefe te dice hace una hora que no vayas a una reunión y recibes un whatsapp diciendo que por qué no estás. Lo mismo lo relees 10 veces al no dar crédito.
  • CUANDO TU MENTE ESTA PREOCUPADA o amenazada TIENE MAS POSIBILIDADES DE DE GUIAR TUS ELECCIONES. Hay un 50% más de posibilidad de elegir tarta si durante el almuerzo te piden que no olvides un número O_o.
  • Cuando pasas por una panadería y te apetece, por el olor, un pastel, tu cerebro ya te ha pegado un chute de dopamina y ha dado orden a tu cuerpo de bajar los niveles de azúcar, para absorber ese exceso de azúcar que va a entrar. Por tanto, más ganas tendrás. Sólo siendo consciente del proceso y con fuerza de voluntad se puede evitar.
  • El corazón de las personas tiene una variabilidad (cambio de pulsaciones al realizar actividades). Si nos estresamos la variabilidad baja, por el incremento sostenido del nivel de pulsaciones, que genera sensación de ansiedad y enojo, por lo que es difícil mantener la voluntad. Para reforzar la voluntad puede ser útil bajar tu respiración.
  • Es fácil perder control de las emociones si se descansa mal. Tu cuerpo tiene dificultades para absorber glucosa y el cerebro desespera por energía.
  • A lo largo del día se deteriora la voluntad, por eso parece más lógico tratar de ir al gimnasio por la mañana que por la noche. Puedes hacer pequeños esfuerzos para reforzar la voluntad pero no hay que pasarse, porque se gasta esa baza.
  • El cerebro, a medida que encuentra déficit de azúcar recorta los circuitos destinados a la voluntad y autocontrol, que son grandes consumidores de recursos: merece la pena tomar riesgos biológicamente estratégicos.
  • El cambio positivo se produce a partir de la combinación de elementos: expectativas que nos creamos (futuro) y vivimos (pasado y presente), la atención positiva, el poder de vetar (decir no a lo que nuestro cerebro decidió hacer).
  • No es atención positiva apuntar a los hijos a un deporte que no les gusta. Será difícil que destaquen en esa actividad.
  • Expresar gratitud promueve saborear lo positivo de las acciones de la vida. Estimula un comportamiento más moral y menos material y ayuda a construir relaciones más duraderas.
  • Las experiencias tienen un enorme impacto en la capacidad de cambio por lo que es importante concentrarse en: pensamientos precisos (y positivos), buen manejo de recuerdos, conexión entre aromas y humor, construcción de vínculos positivos con uno mismo y los demás. En la pareja hay que proporcionar señales recíprocas de amor: miradas, abrazarse, besarse, mantener contacto visual…
  • Hay que evitar a la gente negativa.
  • Apunto una frase que de Albert Einstein que sospecho que voy a repetir en el futuro: Los problemas significativos a los que nos enfrentamos no pueden ser solucionados con el mismo marco de pensamiento con los que los hemos creado.
  • Si te has dejado cautivar por un profesor o ponente es porque probablemente grandes emociones personales positivas estarían involucradas en el momento.
  • Hay veces que personalizamos agregando un significado personal a eventos inofensivos. Imagínate que tu padre no te habla una mañana. Puedes dar por hecho que está enfadado cuando simplemente puede estar distraído.
  • Hay que aprender a decir que no a esos hábitos que conducen al placer o al alivio instantáneo. Tener deseo no es malo lo importante es la sabiduría para saber cuando seguirlo y cuando no.
  • Diferencia ente sensaciones emocionales y emociones. Si estas triste porque alguien falleció, no es una sensación emocional, porque pasó. Si estás triste porque piensas que no te quieren, eso es una sensación emocional porque puede ser verdad o mentira y llevarte a una reacción equivocada.

Bueno, este libro me parece fascinante y tiene muchísimas cosas más. Espero que te animes a leerlo.

REST y el principio de idempotencia

$
0
0

En este tutorial intentaremos explicar qué es el principio de idempotencia aplicado a verbos HTTP, por qué es importante respetarlo y los beneficios esperados.

0. Índice de contenidos.


1. Introducción

Ya comentamos en anteriores tutoriales la importancia del diseño de nuestras APIs REST para que sigan los principios de la web y no se conviertan en simples interfaces sobre HTTP. En este tutorial volvemos a incidir en las buenas prácticas de diseño de APIs REST, esta vez explicando el principio de idempotencia de los verbos HTTP. ¿Te has preguntado alguna vez en qué nivel se encuentra tu API en la famosa escala de Richardson?

En este tutorial intentaremos explicar qué es el principio de idempotencia aplicado a verbos HTTP, por qué es importante implementar dicho principio y los beneficios esperados.


2. Entorno.

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.2 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS Yosemite 10.10
  • Entorno de desarrollo: NetBeans IDE 8.0.2
  • JDK 1.8
  • Apache Tomcat 8.0.21
  • Maven 3.1.1
  • Spring 4.1.6.RELEASE
  • H2 database 1.3.170

3. ¿Qué es el principio de idempotencia?.

El principio de idempotencia aplicado a REST (realmente es a los verbos HTTP) nos dice que, la ejecución repetida de una petición con los mismos parámetros sobre un mismo recurso tendrá el mismo efecto en el estado de nuestro recurso en el sistema si se ejecuta 1 o N veces. ¿¡¿¡Cooooooooomo?!?!?!!?, vamos a verlo gráficamente para comprenderlo mejor :-)

Imaginemos la ejecución repetida de una petición utilizando un verbo NO idempotente con los mismos datos de entrada sobre un mismo recurso.

Paso 1: Enviamos una petición de creación de un recurso con unos parámetros. Y el estado en el sistema se modifica, concretamente se crea un nuevo pedido en base de datos.

NoIdempotent1

Paso 2: Volvemos a enviar la misma petición de creación al mismo recurso con los mismos parámetros. Observamos que el estado del sistema vuelve a cambiar, se crea otro recurso nuevo.

NoIdempotent2

Paso 3: Nuevamente enviamos la misma petición de creación al mismo recurso con los mismos parámetros. Y se a crear otro recurso nuevo.

NoIdempotent3

Acabamos de ver el comportamiento esperado de aplicar un verbo NO idempotente sobre un recurso, el estado del sistema cambia siempre aunque se apliquen los mismos parámetros al mismo recurso.

Paso 1: Veamos el comportamiento esperado de un verbo idempotente. Enviamos una petición de creación/actualización sobre un recurso con unos determinados parámetros. Y se crea un nuevo recurso en el sistema en caso de no existir o se modifica, en caso de que algo haya cambiado, el existente.

Idempotent1

Paso 2: Repetimos la petición con el mismo verbo idempotente con los mismos parámetros y sobre el mismo recurso. Al ser una acualización el estado del sistema se mantiene igual, no se crea un recurso nuevo y el valor de las propiedades el recurso es el mismo que cuando se terminó la primera petición.

Idempotent2

Paso 3: Repetimos N veces la petición y el estado sigue siendo el mismo.

Paso 4: Si cambiamos algo, por ejemplo el recurso al que atacamos utilizando los mismos parámetros y mismo verbo idempotente si que se producirían cambios en el sistema, ya que la petición es contra un nuevo recurso. En este caso se crearía/actualizaría otro recurso

Idempotent4

Nótese que es MUY IMPORTANTE tener en cuenta que este comportamiento no se produce por “arte de magia” por el simple hecho de utilizar un verbo idempotente. Es necesario implementar la idempotencia en nuestro servicio.

La respuesta del servidor puede cambiar según el número de petición. Por ejemplo, si es una petición de creación/actualización de un recurso, si dicho recurso no existiese, la primera vez que realizamos la petición el servidor nos podría devolver un código 201 (CREATED) y las siguientes veces podría devolver 200 o 204.

Aquí podemos ver un listado de los verbos HTTP más comunmente utilizados donde se indica si son o no idempotentes.

IdempotentVerbs


4. ¿Por qué idempotencia?.

Pueden existir determinadas ocasiones en las que, debido a congestiones de la red, el consumidor de un API no reciba una respuesta del servidor acerca del resultado del procesamiento de la petición (salta un timeout). En ese caso, ¿qué hacemos?, ¿repetimos nuevamente la petición?, ¿continuamos como si no hubiese pasado nada?.

Con HTTP, a diferencia (por ejemplo) de JMS, no tenemos garantía de entrega de nuestras peticiones al servidor, por lo que nada nos asegura que una petición que enviamos, y donde se genera un error por timeout, haya llegado y se haya procesado correctamente.

El caso más típico es cuando queremos dar de alta un nuevo recurso en el sistema. Por ejemplo, una orden de compra que realiza un cliente. Imaginemos que al dar de alta dicha petición en el sistema no obtenemos respuesta del servidor. Supongamos que reintentamos la petición y el servidor nos responde que todo está correcto. Si las dos peticiones se hubiesen hecho de una manera NO idempotente, es probable que la primera petición sí que hubiese llegado al servidor y se hubiese procesado por lo que tendríamos dos órdenes de compra de un mismo cliente. Como el sistema en la segunda petición nos devolvió que todo estaba correcto, nosotros pensamos que se ha creado la orden una única vez, pero realmente existen dos órdenes de compra para el cliente, cuando sólo queríamos dar de alta una.

Si dichos reintentos se hubiesen ejecutado de una manera IDEMPOTENTE podemos realizar tantos reintentos como necesitemos (mismo recurso y mismos parámetros) teniendo la garantía de que cuando el servidor nos responda la primera vez que todo esta correcto, tendremos nuestro recurso creado una única vez.

Por tanto, el gran beneficio que obtenemos de la idempotencia es la capacidad de realizar tantos reintentos como necesitemos teniendo la certeza de que el estado del sistema será el que deseabamos cuando realizamos la primera petición.


5. Los recursos “virtuales”.

Siguiendo con el problema de la creación de recursos en el servidor que se den de alta una única vez por muchos reintentos que hagamos vemos que aparece un gran inconveniente. Si nos fijamos qué verbos HTTP son idempotentes vemos que está PUT pero no POST.

La gran ventaja que obtenemos con POST sobre PUT es que realizamos la petición sobre un recurso padre (ejemplo: una colección) para crear un recurso hijo como podría ser una entidad dentro de dicho recurso. Hablando claro, delegamos la generación del identificador del nuevo recurso que se va a crear en el servidor. Veamos un ejemplo:

URL: http://miservidor:puerto/api/v1/pedidos
Method: POST
Headers:
   Accept: application/json
   Content-Type: application/json
Body:
{
    "voucher" : "358AFD",
    "price" : 33.78,
    "status" : 1
}

Si nos fijamos, la petición la enviamos contra la URL http://miservidor:puerto/api/v1/pedidos que es la colección de la que colgará el recurso que queremos crear. Una posible respuesta podría ser:

URL: http://miservidor:puerto/api/v1/pedidos
Status: 201 CREATED
Headers:
   Location: http://miservidor:puerto/api/v1/pedidos/5743896
   Content-Type: application/json
Body:
{
    "id" : 5743896,
    "voucher" : "358AFD",
    "price" : 33.78,
    "status" : 1
}

Como vemos, en la respuesta el servidor nos devolverá el nuevo identificador del recurso (campo id en la respuesta) y, lo más importante, la URL que identifica de manera única al recurso recién creado. El servidor es el que tiene el conocimiento para crear el identificador de un nuevo recurso.

PUT, sin embargo, suele actuar sobre un recurso concreto y no sobre un recurso padre. Por tanto, si quisiésemos utilizar PUT para crear un nuevo recurso, el cliente debería saber el identificador del mismo, por lo que quedaría en manos del cliente la responsabilidad de crear dicho identificador, cosa MUY POCO recomendable.

Entonces, si queremos utilizar idempotencia para crear recursos concretos, ¿qué hacemos?. Pues utilizar recursos “virtuales”. La técnica de recursos “virtuales” consiste en realizar una primera petición al servidor mediante POST para que cree un identificador válido para un nuevo recurso que se va a crear. Sin embargo, en el servidor no se ha creado ningún recurso, únicamente se genera un identificador válido para un posible nuevo recurso.

Seguidamente, si todo ha ido bien, al contar con una URL que identificará de manera única a un posible nuevo recurso, ya podemos utilizar idempotencia (peticiones PUT) contra ese recurso que todavía no existe.

Sería algo así:

URL: http://miservidor:puerto/api/v1/pedidos
Method: POST
Headers:   
Body:

Si todo ha ido bien el servidor respondería algo como:

URL: http://miservidor:puerto/api/v1/pedidos
Status: 201 CREATED
Headers:
   Location: http://miservidor:puerto/api/v1/pedidos/5743896   
Body:

Con lo que ya tendríamos una URL sobre la que hacer las peticiones PUT (idempotentes) sobre un recurso con un identificador válido. Dicho recurso no existe hasta que hagamos un PUT con los datos del recurso y el servidor nos devuelva una respuesta correcta:

URL: http://miservidor:puerto/api/v1/pedidos/5743896
Method: PUT
Headers:
   Accept: application/json
   Content-Type: application/json
Body:
{
    "voucher" : "358AFD",
    "price" : 33.78,
    "status" : 1
}

Representado gráficamente sería algo así:

VirtualResources

En el caso de que la primera petición fallase (timeout) no pasaría nada. Aunque dicha petición no es idempotente (POST) podemos reenviar al servidor la petición tantas veces como queramos hasta que nos devuelva un identificador válido ya que no se están creando recursos en el servidor, únicamente generamos identificadores.

Seguidamente, ya tenemos una URL de un posible recurso a crear. Finalizamos el proceso con una petición idempotente PUT con la que crearemos/actualizaremos dicho recurso.

Aquí os dejo un EJEMPLO con Java y SpringMVC donde se puede apreciar cómo implementar del lado del servidor un servicio con verbos idempotentes (recurso Users) además de un ejemplo con recursos virtuales (recurso Orders).


6. Referencias.


7. Conclusiones.

En este tutorial hemos visto el principio de idempotencia aplicada a verbos HTTP. A la hora de diseñar nuestras APIs REST sobre HTTP es muy importante tener en cuenta las diferentes características de los verbos como pueden ser la seguridad o la propia idempotencia.

La gran ventaja de la idempotencia es que podemos reintentar una petición contra el servidor tantas veces como necesitemos hasta que tengamos la certeza de que el estado de nuestro recurso en el sistema está justo como nosotros queríamos. Esto adquiere especial importancia en operaciones de creación de recursos donde necesitamos garantizar que ese recurso se creará una única vez en el servidor y donde queremos contar con una política de reintentos en caso de que no obtengamos respuesta del servidor.

Espero que este tutorial os haya sido de ayuda. Un saludo.

Miguel Arlandy

marlandy@autentia.com

Twitter: @m_arlandy

Integración de Elasticsearch con Spring + MySQL

$
0
0

En este tutorial veremos los pasos a seguir para ver cómo podemos integrar Elasticsearch en un proyecto con Spring + MySQL.

0. Índice de contenidos

1. Introducción

Elasticsearch es un servidor de búsqueda basado en Apache Lucene. Provee un motor de búsqueda de texto completo, distribuido y con capacidad de multitenencia con una interfaz web RESTful y con documentos JSON. Elasticsearch está desarrollado en Java y está publicado como código abierto bajo las condiciones de la licencia Apache.

Los motores de búsqueda nos permiten la búsqueda y acceso a información indexada de forma instantánea, que de otra forma supondría una gran penalización en tiempo y en rendimiento. Un ejemplo típico de uso sería la búsqueda de artículos en un blog utilizando como patrón de búsqueda alguna frase significativa que pudiera aparecer con alta probabilidad en artículo que se pretende encontrar.

2. Entorno

El tutorial está escrito utilizando el siguiente entorno:
  • Hardware: MacBook Pro 15′ (2 Ghz Intel Core i7, 8GB DDR3 1333 Mhz)
  • Sistema operativo: Mac OS X Yosemite 10.10.3
  • Software
    • Java JDK 1.8
    • Maven 3
    • MySQL Server 5.6

3. Preparación de la MySQL con datos de prueba.

Para nuestro tutorial, vamos a utilizar la base de datos de pruebas de MySQL Sakila.

Accedemos a la web de descarga de MySQL Sakila a través del siguiente enlace y nos descargamos el ZIP de la base de datos sakila.

Descomprimimos el zip donde deseemos. Se nos habrá creado una carpeta ‘sakila-db’ que contendrá los ficheros ‘sakila-schema.sql’, y ‘sakila-data.sql’.

Abrimos un terminal y nos conectamos al servidor de MySQL con nuestras credenciales.

shell> mysql -u root -p

Ejecutamos el script ‘sakila-schema.sql’.

mysql> SOURCE $DownloadFolder/sakila-db/sakila-schema.sql;

Ejecutamos el script ‘sakila-data.sql’.

mysql> SOURCE $DownloadFolder/sakila-db/sakila-data.sql;

Confirmamos que la base de datos se ha creado y poblado correctamente.

mysql> USE sakila;
Database changed

mysql> SHOW TABLES;
+----------------------------+
| Tables_in_sakila           |
+----------------------------+
| actor                      |
| address                    |
| category                   |
| city                       |
| country                    |
| customer                   |
| customer_list              |
| film                       |
| film_actor                 |
| film_category              |
| film_list                  |
| film_text                  |
| inventory                  |
| language                   |
| nicer_but_slower_film_list |
| payment                    |
| rental                     |
| sales_by_film_category     |
| sales_by_store             |
| staff                      |
| staff_list                 |
| store                      |
+----------------------------+
22 rows in set (0.00 sec)

mysql> SELECT COUNT(*) FROM film;
+----------+
| COUNT(*) |
+----------+
| 1000     |
+----------+
1 row in set (0.02 sec)

mysql> SELECT COUNT(*) FROM film_text;
+----------+
| COUNT(*) |
+----------+
| 1000     |
+----------+
1 row in set (0.00 sec)

4. Instalación y ejecución de Elasticsearch

El único requerimiento necesario para poder utilizar Elasticsearch es una versión reciente de Java, preferiblemente, utilizando su ultima versión oficial liberada disponible en www.java.com.

Accedemos a la página de descarga y nos descargamos el .zip con las versión más actual. Después la descomprimimos en la ubicación que más nos guste (a partir de ahora $ELASTICSEARCH_HOME).

A continuación vamos a instalar el plugin “head” de Elasticsearch, el cual nos proporcionará una interfaz gráfica web del ecosistema Elasticsearch que hayamos arrancado.

shell> $ELASTICSEARCH_HOME/bin/plugin -install mobz/elasticsearch-head

Por último, arrancamos el Elasticsearch y verificamos que está arriba haciendo de nuestro plugin “head”.

shell> $ELASTICSEARCH_HOME/bin/.elasticsearch

Accedemos a http://localhost:9200/_plugin/head/ y vemos que está levantado.

5. Importación de datos a Elasticsearch

Para la importanción de los datos utilizaremos Elasticsearch JDBC Importer, el cual nos permite traernos todos los datos a través de conexiones JDBC a nuestra base de datos. Esto nos da la potencia de qué podremos recolectar datos de cualquier base de datos siempre y cuando tengamos nuestro conector JDBC.

5.1 Para la instalación

Descargamos el .zip con el Elasticsearch JDBC Importer.

wget http://xbib.org/repository/org/xbib/elasticsearch/importer/elasticsearch-jdbc/1.6.0.0/elasticsearch-jdbc-1.6.0.0-dist.zip

Descomprimimos.

unzip elasticsearch-jdbc-1.6.0.0-dist.zip

Nos dirigimos al directorio descomprimido (a partir de ahora $JDBC_IMPORTER_HOME).

cd elasticsearch-jdbc-1.6.0.0

Verificamos que la versión descargada tenga nuestro conector JDBC para MySQL en la carpeta “$JDBC_IMPORTER_HOME/lib”. Si no lo tuviera, se lo añadimos descargándonoslo de la comunidad oficial de MySQL.

Con esto concluirían los pasos para la instalación del Elasticsearch JDBC Importer. Vamos a ver la ejecución.

5.2. Para la ejecución

Antes de ejecutar la importanción, debemos tener definida la query que lanzara el JDBC Importer para extraer los datos.

Para nuestro ejemplo, vamos a indexar la información para que se pueda buscar por título del film y descripción del film. Para ello, debemos construir la siguiente query:

mysql> SELECT film.title as 'filmTitle', film.description as 'filmDescription' FROM film

Con ella, vamos a construir el script de importación.

shell> vim $JDBC_IMPORTER/bin/mysql-sakila-films-importer.sh

y lo editamos para que quede de la siguiente forma:

#!/bin/sh

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
bin=${DIR}/../bin
lib=${DIR}/../lib

echo '{
        "type" : "jdbc",
        "jdbc" : {
                "url" : "jdbc:mysql://localhost:3306/sakila",
                "user" : "root",
                "password" : "",
                "sql" : "SELECT film.film_id as '_id', film.title as 'filmTitle', film.description as 'filmDescription' FROM film",
                "index" : "sakila_index_demo",
                "type" : "film_type"
        }
}'| java \
    -cp "${lib}/*" \
    -Dlog4j.configurationFile=${bin}/log4j2.xml \
    org.xbib.tools.Runner \
    org.xbib.tools.JDBCImporter

Atendiendo especialmente a la salida del comando ‘echo’.

{
        "type" : "jdbc",
        "jdbc" : {
                "url" : "jdbc:mysql://localhost:3306/sakila",
                "user" : "root",
                "password" : "",
                "sql" : "SELECT film.film_id as '_id', film.title as 'filmTitle', film.description as 'filmDescription' FROM film",
                "index" : "sakila_index_demo",
                "type" : "film_type"
 }

  • type: Tipo de conexion.
  • dbc.url: URL de conexión JDBC.
  • jdbc.user: Usuario de la conexión JDBC.
  • jdbc.password: Password de la conexión JDBC.
  • jdbc.sql: Query SQL utilizada para la extracción de datos.
  • jdbc.index: Nombre del índice donde se indexará la información. Puede estar definido previamente en Elasticsearch.
  • jdbc.type: Nombre del mapping que tendrán los documentos indexados a través de la query construida.

Salvamos el documento, y le damos permisos de ejecución.

shell> chmod 755 mysql-sakila-films-importer.sh

Por último ejecutamos el script teniendo en cuenta que el proceso de Elasticsearch ya tiene que estar arrancado previamente.

shell> ./mysql-sakila-films-importer.sh

Accedemos a la web del plugin head, para verificar que se ha creado el índice.

6. Integración con Spring

Para la integración con Spring se utilizará la librería Spring Data Elasticsearch, disponible en el repositorio central de Maven.

<dependency>
	<groupId>org.springframework.data</groupId>
	<artifactId>spring-data-elasticsearch</artifactId>
	<version>1.2.0.RELEASE</version>
</dependency>

6.1 Estructura general del proyecto

6.2 pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.foo.bar</groupId>
	<artifactId>elasticsearch-tutorial-spring</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>elasticsearch-tutorial-spring</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<junit.version>4.11</junit.version>
		<spring.version>4.0.0.RELEASE</spring.version>
	</properties>

	<dependencies>

		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-elasticsearch</artifactId>
			<version>1.2.0.RELEASE</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
			<exclusions>
				<exclusion>
					<groupId>commons-logging</groupId>
					<artifactId>commons-logging</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>${spring.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>1.1.1</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>

6.3 applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
       xsi:schemaLocation="http://www.springframework.org/schema/data/elasticsearch http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">

    <elasticsearch:transport-client id="client" cluster-name="elasticsearch" cluster-nodes="127.0.0.1:9300"/>
    
    <bean name="elasticsearchTemplate" class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
        <constructor-arg name="client" ref="client"/>
    </bean>

    <elasticsearch:repositories base-package="com.autentia.tutorials.elasticsearch.repositories" />

</beans>

6.4 FilmIndexedDoc.java

A destacar, que el nombre de los atributos de clase deben coincidir con los nombres de las propiedades del mapping de los films indexados.

Debemos atender a los alias explicitados en la query de importacion: “filmTitle”, “filmDescription”. Además, el nombre del índice y el nombre del tipo también deben coincidir con el del script de importación (“index” : “sakila_index_demo”, “type” : “film_type”).

package com.autentia.tutorials.elasticsearch.entities;

import org.springframework.data.annotation.*;
import org.springframework.data.elasticsearch.annotations.*;

@Document(indexName = "sakila_index_demo", type = "film_type", indexStoreType = "memory", shards = 1, replicas = 0, refreshInterval = "-1")
public class FilmIndexedDoc {

	@Id
	private String id;

	private String filmTitle;

	private String filmDescription;

	public FilmIndexedDoc() {
	};

	public FilmIndexedDoc(String id, String filmTitle, String filmDescription) {
		super();
		this.id = id;
		this.filmTitle = filmTitle;
		this.filmDescription = filmDescription;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getFilmTitle() {
		return filmTitle;
	}

	public void setFilmTitle(String filmTitle) {
		this.filmTitle = filmTitle;
	}

	public String getFilmDescription() {
		return filmDescription;
	}

	public void setFilmDescription(String filmDescription) {
		this.filmDescription = filmDescription;
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("FilmIndexedDoc [id=");
		builder.append(id);
		builder.append(", filmTitle=");
		builder.append(filmTitle);
		builder.append(", filmDescription=");
		builder.append(filmDescription);
		builder.append("]");
		return builder.toString();
	}

}

6.5 FilmIndexedDocRepository.java

Definimos una interfaz que extienda de la interfaz ‘ElasticsearchRepository’ indicándole el objeto y el tipo del campo ‘_id’. Esta interfaz contendrá las firmas de los métodos que Spring Data Elasticsearch utilizará para construir las queries de forma transparente utilizando la notación CamelCase para los atributos ‘filmTitle’ y ‘filmDescription’.

package com.autentia.tutorials.elasticsearch.repositories;

import java.util.*;

import org.springframework.data.elasticsearch.repository.*;

import com.autentia.tutorials.elasticsearch.entities.*;

public interface FilmIndexedDocRepository extends ElasticsearchRepository<FilmIndexedDoc, String> {

	List<FilmIndexedDoc> findByFilmTitle(String filmTitle);

	List<FilmIndexedDoc> findByFilmDescription(String filmDescription);

}

6.6. FilmIndexedDocRepositoryTest.java

Por último, definimos un test de integración para ver que todo funciona correctamente.

package com.autentia.tutorials.elasticsearch;

import java.util.*;

import javax.annotation.*;

import org.junit.*;
import org.junit.runner.*;
import org.springframework.test.context.*;
import org.springframework.test.context.junit4.*;

import com.autentia.tutorials.elasticsearch.entities.*;
import com.autentia.tutorials.elasticsearch.repositories.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:/applicationContext-test.xml")
public class FilmIndexDocRepositoryTest {

	@Resource
	private FilmIndexedDocRepository filmIndexedDocRepository;

	@Test
	public void shouldReturnListOfFilmsByTitle() {
		List<FilmIndexedDoc> films = filmIndexedDocRepository.findByFilmTitle("casper");
		Assert.assertTrue(films.size() > 0);
	}

	@Test
	public void shouldReturnListOfFilmsByDescription() {
		List<FilmIndexedDoc> films = filmIndexedDocRepository.findByFilmDescription("robot and a boy");
		Assert.assertTrue(films.size() > 0);
	}

}

7. Conclusiones

En este tutorial hemos visto la integración de Elasticsearch con Spring + MySQL. Para ello, nos hemos servido de la herramienta Elasticsearch Importer JDBC con la que hemos podido importar los datos de manera sencilla utilizando una query customizada a nuestras necesidades, y con la posibilidad de poder importar datos de cualquier fabricante de base de datos, siempre y cuando tengamos su conector JDBC. Para la integración con Spring, hemos utilizado la librería Spring Data Elasticsearch, la cual nos proporciona una serie de clases e interfaces templates para poder dedicar nuestro tiempo únicamente a los procesos de negocio de búsqueda en Elasticsearch.

Espero que este tutorial os haya servido de ayuda. Un saludo.

Daniel Rodríguez

Twitter: @DaniRguezHdez

APIs REST documentadas y probadas con API Blueprint y Dredd

$
0
0

En este tutorial intentaremos explicar cómo documentar nuestras APIs REST con la ayuda de API Blueprint y cómo probar que el servicio de backend cumple con el contrato de manera automática gracias a Dredd.

0. Índice de contenidos.


1. Introducción

Tener una buena documentación actualizada de nuestras APIs es fundamental para facilitar la integración por parte de nuestros potenciales consumidores. En servicios web basados en SOAP tenemos la opción de WSDL para documentar nuestros contratos técnicos, pero en REST no parece que se haya impuesto ningún estilo, al menos de momento…

En este tutorial intentaremos explicar cómo documentar nuestras APIs REST con la ayuda de API Blueprint y cómo probar que el servicio de backend cumple con el contrato de manera automática gracias a Dredd.

Dredd


2. Entorno.

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina 15′ (2.2 Ghz Intel Core I7, 16GB DDR3).
  • Sistema Operativo: Mac OS Yosemite 10.10
  • Entorno de desarrollo: NetBeans IDE 8.0.2
  • Node Package Manager (npm) v2.10.0
  • Dredd v0.6.0
  • JDK 1.7 +
  • Apache Maven 3.1.1

3. La importancia de documentar las APIs.

Nuestra API es el componente que define la forma en la que los clientes deben interactuar con el servicio, las capacidades que éste expone y los tipos de datos que maneja.

Tener bien documentada la API de un servicio es fundamental, sobre todo si la vamos a exponer al exterior para que sea consumida por una gran variedad de consumidores. Por supuesto, cuando se expone de manera interna (ej. Intranet corporativa) también es muy importante una buena documentación de este contrato técnico.

Un API bien documentada nos asegura que los futuros consumidores podrán interactuar con nuestro servicio de backend con relativa facilidad. La documentación del API debe ser clara, concisa y, sobre todo, sencilla de entender por humanos. Si nuestra documentación del API es excesivamente extensa o compleja, cabe la posibilidad de que al equipo de desarrollo del servicio le dé pereza actualizar la documentación y no se corresponda con el servicio de backend.

Desde el punto de vista del diseño de servicios es fundamental seguir el enfoque Contract First con el que diseñamos primero el contrato de nuestro servicio (nuestra API) y posteriormente implementamos la lógica de backend que cumple con dicho contrato. Con esto respetamos fundamentalmente dos principios de diseño como son el de bajo acoplamiento (entre contrato e implementación) y el de abstracción del servicio.

Si contamos con servicios web (los clásicos que suelen intercambiar mensajes SOAP) no tenemos demasiado problema a la hora de documentar nuestra API ya que contamos con el lenguaje WSDL. Pero, ¿qué ocurre en el caso de contar con servicios REST?.

Con REST tenemos múltiples alternativas para documentar nuestra API aunque es cierto que parece que no hay ninguna que se imponga claramente, desde WSDL 2.0 o WADL, pasando un documento corporativo, una web (html) o incluso alguna herramienta tipo Swagger, etc…


4. Documentando el API con API Blueprint.

API Blueprint es un lenguaje basado en Markdown, muy fácil de entender por humanos y con el que podemos documentar de una manera muy sencilla nuestras APIs.

Aunque existen diferentes variantes, podríamos decir que un documento de un API escrito con API Blueprint se divide, fundamentalmente, en dos secciones:

  • API: sección relativa a la documentación general del API. Se representa con #
  • Recursos: sección relativa a la documentación de cada uno de los recursos que conforman el API. Se representa con ##. Se divide en diferentes sub-secciones:
    • Modelo: describimos los datos que conforman el recurso o entidad además de las posibles cabeceras de la petición.
    • Accion: Verbos HTTP que acepta el recurso. A su vez se divide en diferentes secciones donde las más importantes son las peticiones y respuestas.

Aquí podéis encontrar la documentación completa del lenguaje API Blueprint

En un primer momento puede parecer algo complejo pero se tarda muy poco en pillarle el truco. Es realmente sencillo :)

Este sería un pequeño ejemplo de documentación de un recurso “Order” representado como un objeto con atributos: id, price, vouncher y status. Dicho recurso acepta peticiones GET y ante una petición utilizando dicho verbo y negociación de tipo de contenido a JSON, se espera una respuesta con código 200 y una representación JSON del objeto “Order”.

## Order [/api/orders/{id}]

+ Parameters
    + id : 999992_bd84fe278c6152c821a80ee6038effe6 (string)

+ Attributes (object)
    + id: bd84fe278c6152c821a80ee6038effe6 (string)
    + price: 2.77 (number)
    + vouncher: ACR0347Z (string) 
    + status: 1 (number) - Order status. It must be 1 (CREATED), 2 (FINISHED) or 3 (CANCELED)


### Get existing order [GET]
+ Request
    
    + Headers
        
            Accept: application/json

+ Response 200 (application/json;charset=UTF-8) 
    
    + Attributes(Order)

Y visualmente, representado por un intérprete de Markdown, sería algo como:

ApiBlueprint


4.1. ¿Por qué usar API Blueprint?.

Llegados a este punto tampoco parece que tengamos ningún motivo especial para utilizar API Blueprint. Lo que, bajo mi punto de vista, hace realmente interesante a este lenguaje basado en Markdown es el conjunto de herramientas que surgen alrededor de API Blueprint. En concreto destacaremos dos:

  • Drakov: una herramienta para levantar un servidor con un “servicio de Mocks” basado en la documentación de nuestra API. Una excelente opción para tener un servicio corriendo aceptando peticiones y devolviendo respuestas únicamente con nuestra documentación. Todavía tiene que madurar un poco pero ya empieza a tener muy buena pinta.
  • Dredd: Herramienta para testear nuestro servicio de backend en base a la documentación del API. Esta opción es excelente para comprobar que nuestra documentación está constantemente actualizada y que el servicio de backend se comporta en base a lo pactado en el contrato (documento). Hablaremos de Dredd en el siguiente punto.
Drakov Ejemplo de servicio Mock creado a partir de la documentación con Drakov.

5. Probando el servicio a través de la documentación del API con Dredd.

Veamos cómo podemos probar nuestro servicio de backend en base a la documentación de su API.

Lo primero que haremos será instalar Dredd en nuestro equipo. Podemos hacerlo de una forma muy sencilla haciendo uso del gestor de paquetes de node (npm). Para ello, asegurándonos de tener los permisos necesarios para una instalación global del paquete, ejecutamos el siguiente comando en nuestro terminal.

npm install -g dredd

Una vez tenemos Dredd instalado globalmente en nuestro sistema, arrancamos nuestro servicio. Nosotros probaremos con el ejemplo del anterior tutorial.

Para ponerlo en funcionamiento, nos descargamos el código fuente y ejecutamos estos dos comandos:

mvn clean install
mvn tomcat7:run

Si todo salió correctamente, ya deberíamos tener nuestro servicio corriendo en http://localhost:8080/rest-idempotency.

Por último lanzamos Dredd, para que ejecute los tests de integración que comprobarán que nuestra documentación está actualizada con respecto al servicio.

Lanzamos el siguiente comando:

dredd $DOCUMENTO_API $URL_BASE_SERVICIO

Donde:

  • $DOCUMENTO_API: es la ruta al documento de nuestra API, puede ser una URL o una ruta del sistema de ficheros del equipo local. En nuestro caso utilizaremos la URL http://localhost:8080/rest-idempotency/API_DOCUMENTATION.md
  • $URL_BASE_SERVICIO: URL de donde parte nuestro servicio, a partir de dicha URL “vivirán” los recursos del API.

Lanzamos el comando:

dredd http://localhost:8080/rest-idempotency/API_DOCUMENTATION.md http://localhost:8080/rest-idempotency

Y vemos que todo funciona correctamente :)

DreddOutput


5.1. ¿Cómo funciona?.

Lo que hace Dredd es evaluar las acciones que se pueden realizar sobre los recursos (verbos HTTP) y, en base a las peticiones/repuestas definidas para cada acción (con API Blueprint se pueden definir tantas peticiones/respuesta como se quieran por acción) realizará dichas peticiones del mismo modo en que se reflejan en el documento. Podríamos decir que una acción sobre un recurso es un escenario y cada petición/respuesta es un caso de uso.

Dredd se basa en otra herramienta llamada Gavel para realizar las validaciones de las respuestas en base a las peticiones que realiza al API. Gavel cuenta con varios grupos de validadores para evaluar las cabeceras de petición/respuesta, códigos de retorno y el cuerpo de la petición (por desgracia, a fecha de publicación de este tutorial, solo entiende texto plano y JSON). Aquí se puede ver un ejemplo de cómo Gavel valida un objeto JSON.


6. Referencias.


7. Conclusiones.

En este tutorial hemos visto la importancia de contar con un API REST bien documentada y actualizada para facilitar la vida a nuestros consumidores tanto si lo hacen de manera externa (Internet) o interna (Intranet).

API Blueprint es una buena opción para documentar de una manera muy sencilla nuestras APIs pero lo que realmente lo hace especial es el conjunto de herramientas que giran a su alrededor. De entre ellas podríamos destacar Dredd una excelente opción para generar tests de integración de manera automática en base a nuestra documentación y facilitar nuestro proceso de documentation-driven development.

Cualquier duda en la sección de comentarios.

Espero que este tutorial os haya sido de ayuda. Un saludo.

Miguel Arlandy

marlandy@autentia.com

Twitter: @m_arlandy

Apache Maven War overlay con dependencias locales

$
0
0

En el caso que necesitemos empaquetar los recursos locales en el target war de nuestra aplicación web, podemos hacer uso del plugin empaquetados de war que ofrece Apache Maven.

0. Índice de contenidos

1. Introducción

Algunas veces nos podemos encontrar con una situación (que aun no siendo la ideal) necesitemos hacer un overlay de recursos o dependencias que no podamos disponer de ellos en un repositorio remoto (Nexus, por ejemplo), o que por decisiones de diseño (aunque este tipo de diseños habría que evitarlos) las dependencias se encuentren como recursos locales del proyecto web.

En el caso que necesitemos empaquetar los recursos locales en el target war de nuestra aplicación web, podemos hacer uso del plugin empaquetados de war que ofrece Apache Maven: maven-war-plugin. Habrá que configurar dicho plugin para obtener el recurso local como dependencia a la hora de realizar el overlay. Lo veremos en los siguientes apartados.

2. Entorno

  • Macbook pro core i7 con 16gb RAM
  • SO: Yosemite
  • IDE: IntelliJ IDEA 14
  • Gestor de proyectos: Apache Maven 3.3.3
  • Java: JDK 1.8

3. Problema inicial

Pongamos una situación de ejemplo:

Supongamos que tenemos una aplicación web gestionada con Maven:

screen1

Esta aplicación web estará integrada como recurso local en otra aplicación web en una carpeta lib/ dentro del proyecto:

screen2

¿Cómo podríamos realizar el overlay de dicho WAR, si no dispongo de un repositorio de artefactos (como viene siendo el diseño más común de gestión de dependencias), por lo que las dependencias de Maven no se me van a resolver correctamente? Lo veremos a continuación 😉

4. Solución war overlay local

La solución será muy sencilla: Haremos uso del plugin de empaquetado de wars de Maven y jugaremos con la gestión de dependencias con recursos locales que te ofrece el core de Maven.

Empecemos por lo segundo. Sabemos que las dependencias en Apache Maven pueden tener varios niveles de alcance o ‘scope': scopes dependencias Maven. Estos niveles de alcance permitirán indicar a Maven cómo tiene que gestionar esas dependencias, si es requerida a nivel de compilación, de runtime, de test, o lo que nos importa en este tutorial, si la dependencia se provee de forma local (system). Haremos uso de este nivel de alcance para indicar que disponemos de un recurso local que se usará como dependencia en nuestra aplicación web.

Ahora nos falta la parte de configuración del maven-war-plugin. Sabemos que el plugin hace uso de las dependencias que tengamos configuradas en el proyecto (a nivel de <dependencies>). Con esto no tendremos problemas ya que previamente hemos configurado nuestra dependencia del WAR local. Ahora hará falta incluir dicha dependencia en la parte <overlays> del plugin:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.6</version>
    <configuration>
        <overlays>
            <overlay>
                <artifactId>exampleWar</artifactId>
                <groupId>com.autentia.tutoriales</groupId>
            </overlay>
        </overlays>
    </configuration>
</plugin>

Con esto, el plugin obtendrá la dependencia correctamente, y realizará el overlay con el war actual. La estructura de directorios que se genera dentro del war resultante será la siguiente:

screen3

Como vemos, podemos identificar recurso.txt y MyClass.class como recurso del war exampleWar-1.0-SNAPSHOT.war, y recurso2.txt como recurso de la aplicación web con la que estamos trabajando, por lo que el overlay se ha ejecutado correctamente.

5. Conclusiones

Como hemos visto en este tutorial, se puede hacer uso del plugin de war overlay de forma muy sencilla para realizarlo sobre dependencias que se encuentren en nuestro filesytem, o en una ruta interna del proyecto. Lo mismo se podría hacer si tenemos por ejemplo un repositorio SVN, o si accediéramos al recurso vía FTP, Samba, etc. (configurando los repositorios adecuadamente).

Se puede consultar el proyecto en github aquí

6. Referencias


Takipi, identifica los errores producidos en tus aplicaciones web Java

$
0
0

En este tutorial veremos una herramienta que nos ayudará a detectar problemas en el código de nuestras aplicaciones en producción que corren en la JVM y nos evitará tener que revisar manualmente los logs.

0. Índice de contenidos.

1. Introducción

En este tutorial veremos cómo instalar y dar los primeros pasos con la herramienta Takipi, la cuál nos permite analizar las causas que han producido errores (excepciones, errores HTTP, errores de log) de nuestras aplicaciones web Java. Para ello, detecta señales a nivel de JVM y realiza análisis de código en la nube.

Toda la información surgida de la monitorización es cifrada haciendo uso de una clave secreta de 256 bits que sigue el estándar de cifrado AES proporcionada en el momento de su instalación y almacenada en los servidores de Takipi.

Takipi proporciona una interfaz web muy sencilla localizada en https://app.takipi.com/ que nos muestra tanto el código donde se produce el error como el valor de las variables en el momento de dicho error, permitiéndonos navegar por la pila de llamadas que lo han producido.

La ventaja de Takipi es que es él quién se pelea con los ficheros de log haciendo uso de un agente ligero que se ejecuta con nuestro servicio y nos lo muestra de una manera más “amigable”. Además, es posible trabajar de manera colaborativa añadiendo compañeros y creando issues que pueden ser discutidos y resueltos entre todos. Por otro lado, podemos publicar cualquier combinación de errores y eventos en devops como Jira, Slack y New Relic.

Finalmente, hay que decir que Takipi no es una herramienta gratuita. Podemos acceder a una versión de pruebas durante un período de 14 días en el que se nos proporciona acceso a un servidor y se nos permite monitorizar 10 errores por mes en una JVM. Finalizado ese período debemos pagar 69 euros al mes por servidor, en el que se nos permite monitorizar todas las JVMs que queramos, sin limitación de análisis de errores y con un mayor número de funcionalidades.

2. Entorno

Para llevar a cabo este tutorial se ha usado el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15’ (2 GHz Intel Core i7, 8 GB 1333 MHz DDR3)
  • Sistema Operativo: Mac OS X Yosemite 10.10.3.
  • Takipi (Julio 2015).
  • Una aplicación web corriendo en un servidor Apache Tomcat 8.0.23, dentro de Eclipse Mars Release (4.5.0).

3. Instalación

La instalación de Takipi es muy sencilla y se lleva a cabo en pocos minutos. Desde la página principal de Takipi https://www.takipi.com/, ingresamos nuestra dirección de correo electrónico en el siguiente cuadro de diálogo y le damos a Start using Takipi:

El siguiente paso será rellenar un formulario como el que vemos a continuación. En nuestro caso, seleccionamos en “I’m planning to use Takipi on Java”, aunque se pueden seleccionar otros tipos de lenguaje basados en Java como Scala.

Una vez rellenado el formulario, pasamos a la instalación de la herramienta. Seleccionamos la plataforma en la que la vamos a instalar y seguimos el método de instalación que nos proporcionan. En nuestro caso, seleccionamos la pestaña OS X y descargamos el instalador.

En este paso se proporciona una cosa muy importante: la clave secreta utilizada para el cifrado del que hablábamos en la introducción. Será necesario copiar esta clave en el portapapeles ya que luego será requerida por el instalador.

Esta clave se solicita siempre que se quiera realizar cambios avanzados en la configuración. Tras instalar Takipi podremos encontrar dicha clave en /Library/Takipi/work/secret.key (en Mac OSX).

Seguimos los pasos del instalador de Takipi hasta llegar a la siguiente ventana:

Será el momento de volver a nuestro navegador y conectar Takipi con nuestra JVM. En nuestro caso, haremos correr la aplicación web en un Tomcat dentro de Eclipse, por lo que elegiremos este IDE. Sin embargo, podemos elegir directamente Tomcat entre otras opciones, y nos dirán los pasos que debemos seguir.

Antes de ejecutar la aplicación en Tomcat, debemos añadir a los argumentos de entrada la opción -agentlib:TakipiAgent. Para ello, en Eclipse hacemos click con el botón derecho en el proyecto que queremos probar, elegimos Run As y seleccionamos Run Configurations. Nos vamos a la pestaña Arguments, y añadimos está opción en el cuadro VM arguments.

Selecccionamos Run, y nos volvemos a app.takipi.com en nuestro navegador. Testeamos la conexión y si obtenemos algo como lo que vemos en la siguiente imagen, querrá decir que Takipi será capaz de monitorizar nuestra aplicación.

Seleccionamos “Next” y Takipi se encontrará activo. Nos saldrá una ventana más que nos sugiere añadir compañeros de trabajo para trabajar conjuntamente en la resolución de problemas. Como ahora no nos interesa seleccionamos “Not Now” y la instalación habrá finalizado.

4. Primeros pasos

Cuando terminamos la instalación de Takipi, en https://app.takipi.com se nos abrirá la siguiente interfaz:

img8

Aquí es donde recibiremos, a modo de buzón de entrada, las excepciones o errores producidos en nuestra aplicación. Además, Takipi enviará estos errores a la cuenta de correo utilizada en el login.

Para probar Takipi hemos forzado algunos errores distintos en nuestra aplicación. Por ejemplo, hemos desconectado el servidor Postgresql en el que se encuentra nuestra base de datos, o hemos producido una excepción java.lang.NullPointerException de manera aleatoria.

Vamos a mostrar el error en la capa de persistencia, para eso lo seleccionamos en nuestra carpeta de entrada y Takipi nos llevará hasta la pantalla de análisis de error.

img9

Resumiré a grandes rasgos los diferentes elementos de esta pantalla, aún así Takipi ofrece tutoriales de inicio rápido muy visuales en los que explica su uso.

En la parte superior de la columna izquierda vemos la información del error, como la fecha y hora en la que se produjo. Si el error se ha reproducido en más ocasiones, se puede ver en que otros momentos se produjo. En esta parte se encuentran otro tipo de acciones, como la de compartir el error, publicarlo en otras herramientas, añadirle una etiqueta para luego combinarlo con otros errores, etc.

Justo debajo de este cuadro vemos la pila de llamadas entre métodos que han dado lugar a este error. Podemos navegar por ella y seleccionar un método concreto para visualizar el fragmento de código en el que se produce el error.

La zona principal de esta pantalla muestra el código y el valor de la variables en el momento en el que se produjo el error. De esta manera podemos detectar el problema de una forma muy sencilla y visual.

El valor de las variables lo podemos consultar tanto en la tabla localizada en el lado derecho de nuestro código o incluso llevando el cursor sobre ellas en el código.

En el caso concreto de nuestro ejemplo, vemos el error localizado en la línea de código señalizada con una especie de llama de fuego rojo.

Vemos que se produce a la hora de obtener una lista de localidades almacenadas en la base de datos. Como ya hemos dicho, el servidor postgresql está parado y por eso se está produciendo la excepción.

En el ejemplo en el que provocamos una excepción java.lang.NullPointerException en una variable, podemos ver claramente el momento en el que lanzamos dicha excepción.

Finalmente, Takipi nos muestra en esta ventana principal la pila de llamadas identificando las llamadas por la etiqueta Called By.

5. Conclusiones

Parece claro que frente a tener que recorrer nuestro archivos de logs para localizar errores y excepciones y poder solucionarlas, es mejor tener una aplicación visual como Takipi, que nos avisa cuando se producen errores en nuestros servicios y nos permite localizar el lugar exacto del código en el que se produce y una imagen de las variables en ese momento de la ejecución.

Tener esta información en la nube y hacer uso de un agente ligero elimina la posibilidad de sobrecargar nuestros recursos con una aplicación de estas características.

Por tanto, por su sencillez tanto en instalación como uso, y lo práctico de sus funcionalidades, recomiendo Takipi a todo aquel que quiera monitorizar los errores en sus Servicios Web Java de una manera más amigable a la habitual.

El “pero” lo encontramos lógicamente en que si queremos acceder a todas las funcionalidades más allá de la versión de prueba gratuita de 14 días tendremos que rascarnos el bolsillo, pero seguro que un servidor de producción nos sale rentable.

Esto ha sido todo, espero que os haya gustado.

Saludos.

Guetón Padrón Sánchez
gpadron@autentia.com

6. Referencias

https://www.takipi.com/ http://docs.takipi.com/

Visualización de rendimiento del Garbage Collector

$
0
0

En este tutorial se presentan una serie de ejemplos que demuestran el comportamiento del recolector de basura o Garbage Collector (GC) de Java en determinados escenarios.

0. Índice de contenidos

1. Introducción

En este tutorial se presentan una serie de ejemplos que demuestran el comportamiento del recolector de basura o Garbage Collector (GC) de Java en determinados escenarios que fuerzan al GC a limpiar el Heap de ejecución.

Además de los trozos de código sobre los que se ha trabajado, se invluyen capturas de pantalla de las salidas gráficas de las herramientas especializadas utilizadas durante el tutorial, como VisualVM con el plug-in VisualGC.

2. Entorno de desarrollo

  • MacBook Pro Intel Core 2 Duo 8GB RAM
  • SO: Yosemite
  • IDE: Eclipse Mars 4.5.0
  • Java JDK 1.7

Al IDE Eclipse se le ha instalado el plugin de VisualVM que permite la representación en tiempo real de espacio de memoria de la JVM, uso de procesador, carga del GC, etc. Conjunto a VisualVM, se ha añadido el plugin VisualGC que añade una serie de visualizaciones del recolector: espacio utilizado en Eden, en Survivor y en PermGen, entre otros.

Para más información sobre VisualVM, consúltese la página oficial.

3. Llamada explícita al GC

La forma más simple de forzar al colector a limpiar memoria es desreferenciando (haciendo apuntar a null) un objeto creado. Se ha sobreescrito el método Object#finalize para mostrar por consola que, efectivamente, se limpia cuando no hay referencias al objeto.

Es conveniente indicar que la llamada a Runtime#gc no garantiza al 100% que el GC haga una pasada de limpieza; es sólo una sugerencia del usuario a la JVM.

package es.autentia.gc_samples;

public class GCExplicito {

    public static void main(String[] args) {
        A a = new A("blanco");
        
        a = null;

        Runtime.getRuntime().gc();
    }

}

class A {
    private String color;

    public A(String color) {
        this.color = color;
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println(color + " eliminado");
    }
}

La salida que obtendremos por consola nos indicará que el objeto ha sido reciclado:

blanco eliminado

4. Referencias inalcanzables

Otro de los casos en los que el GC realiza una limpia del Heap es cuando existe una referencia circular entre objetos y no son alcanzables desde fuera.

En el siguiente caso se declaran tres objetos apuntándose entre ellos (mediante un campo de clase):

package es.autentia.gc_samples;

public class GCAmbito {

    GCAmbito t;
    int i;
    
    public GCAmbito(int i) {
        this.i = i;
    }
    
    public static void main(String[] args) {

        GCAmbito t1 = new GCAmbito(1);
        GCAmbito t2 = new GCAmbito(2);
        GCAmbito t3 = new GCAmbito(3);
        // Ningun objeto se debe reciclar
        
        t1.t = t2;
        t2.t = t3;
        t3.t = t1;
        // Ningun objeto se debe reciclar
        
        t1 = null;
        System.out.println("t1 = null");
        Runtime.getRuntime().gc();
        // No se debe reciclar: t3.t apunta aun a t1
        
        t2 = null;
        System.out.println("t2 = null");
        Runtime.getRuntime().gc();
        // No se debe reciclar: t3.t.t apunta aun a t2
        
        t3 = null;
        System.out.println("t3 = null");
        Runtime.getRuntime().gc();
        // Deben reciclarse los tres objetos: cada uno de ellos
        // apunta a otro de manera circular, sin ser referenciados
        // desde fuera
        
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("Finalizado objeto " + i);
    }
}

Como se desprende de los comentarios en código, cada uno de los objetos son desreferenciados en orden, pero no son liberados por el GC hasta que la referencia circular es inalcanzable (t3 es desreferenciado).

La salida por consola nos demuestra éste comportamiento (nótese que el orden de reciclaje no es determinista y depende de muchos factores como la carga actual de la JVM):

t1 = null
t2 = null
t3 = null
Finalizado objeto 1
Finalizado objeto 3
Finalizado objeto 2

5. Estructura del Heap de Java – Hotspot Heap

Estructura Hotspot VM

Conviene recordar cómo se produce el envejecimiento de los objetos en Java mediante la denominada “Weak Generational Hypothesis”. Para más información sobre el funcionamiento y los tipos de GC de Java conviene echarle un vistazo al tutorial sobre Garbage Collector de Jose Luis Rodríguez.

En el tipo de GC por defecto de la JVM (Serial GC), la política de limpieza y envejecimiento sigue estos pasos:

  • Cualquier objeto nuevo es colocado en Eden.
  • Cuando el Eden se llena, se realiza un reciclaje menor (minor garbage collection).
  • Los objetos con referencias se mueven al primer espacio de supervivientes (S0). El resto serán eliminados en la siguiente limpieza de Eden.
  • En el siguiente reciclaje menor, Eden sufre el mismo proceso antes descrito. Los objetos no referenciados son eliminados de Eden; sin embargo, los objetos viejos tanto de Eden como de S0 son movidos al segundo espacio de supervivientes (S1). Una vez terminada la copia, tanto Eden como S0 son limpiados.
  • En el siguiente reciclaje menor, los espacios de supervivientes (S0 y S1) se intercambian los roles y vuelven a repetir el proceso.
  • Cuando algún objeto de cualquier de los espacios de supervivientes alcanza un determinado tiempo de vida, es promocionado a la Old Generation.
  • Eventualmente, se producirá un reciclaje amplio (major garbage collection) que limpiará y compactará este espacio de la Old Generation.

Con este proceso en mente, el siguiente ejemplo ilustra cómo se produce este llenado y consecuente vaciado de ambas generaciones del Heap:

package es.autentia.gc_samples;

public class GCDesreferencia {

    public static void main(String[] args) {
        B b = new B("blanco");

        for (int i = 0; i < 10000000; i++) {
            if (i % 2 == 1) {
                b = new B("rojo");
            } else {
                b = new B("verde");
            }
            b = null;
        }
    }

}

class B {
    private String color;

    public B(String color) {
        this.color = color;
    }

    @Override
    public void finalize() {
        System.out.println(this.color + " eliminado");
    }
}

Entre los muchos flags que podemos añadir a la ejecución del programa para visualizar el trabajo del GC, los básicos son los siguientes:

  • -XX:+PrintGCDetails: Obtenemos mensajes cada vez que actúa el GC.
  • -Xloggc:<fichero.log>: Redirige el resultado de los mensajes del GC a un fichero externo en vez de por salida estándar.

Si nos fijamos en el log generado, podemos diferenciar dos partes:

[...]

0.822: [GC [PSYoungGen: 38912K->5104K(72704K)] 66872K->63644K(159744K), 0.1651680 secs] [Times: user=0.20 sys=0.03, real=0.16 secs]
0.988: [Full GC [PSYoungGen: 5104K->0K(72704K)] [ParOldGen: 58540K->63275K(151040K)] 63644K->63275K(223744K) [PSPermGen: 2631K->2631K(21504K)], 1.4727490 secs] [Times: user=2.58 sys=0.01, real=1.47 secs]
3.172: [GC [PSYoungGen: 67584K->5120K(72704K)] 130859K->119483K(223744K), 0.2449040 secs] [Times: user=0.29 sys=0.05, real=0.24 secs]
3.418: [Full GC [PSYoungGen: 5120K->0K(72704K)] [ParOldGen: 114363K->116723K(265728K)] 119483K->116723K(338432K) [PSPermGen: 2631K->2631K(21504K)], 1.3292800 secs] [Times: user=2.42 sys=0.01, real=1.33 secs]
5.192: [GC [PSYoungGen: 67584K->5120K(91136K)] 184307K->176627K(356864K), 0.2886140 secs] [Times: user=0.31 sys=0.04, real=0.28 secs]

[...]

La primera parte detalla el estado de ocupación del Heap de forma periódica cada vez que actúa el GC, indicando los tres espacios en KB: la Young, la Old y la Permanent Generation, así como los tiempos en los que se han tomado las medidas.

[...]

Heap
 PSYoungGen      total 238592K, used 194302K [0x00000007d5500000, 0x00000007f3e00000, 0x0000000800000000)
  eden space 97280K, 54% used [0x00000007d5500000,0x00000007d88bf808,0x00000007db400000)
  from space 141312K, 100% used [0x00000007e6400000,0x00000007eee00000,0x00000007eee00000)
  to   space 180224K, 0% used [0x00000007db400000,0x00000007db400000,0x00000007e6400000)
 ParOldGen       total 450560K, used 352260K [0x0000000780000000, 0x000000079b800000, 0x00000007d5500000)
  object space 450560K, 78% used [0x0000000780000000,0x0000000795801030,0x000000079b800000)
 PSPermGen       total 21504K, used 2637K [0x000000077ae00000, 0x000000077c300000, 0x0000000780000000)
  object space 21504K, 12% used [0x000000077ae00000,0x000000077b093790,0x000000077c300000)

Al final del log se detalla el nivel de ocupación del Heap de manera estratificada al finalizar la ejecución. En la Young Generation o PSYoungGen, from space se refiere a S0 y to space a S1.

6. Tipos de GC y su rendimiento comparado

Mediante las herramientas VisualVM con el plugin VisualGC, podemos realizar una pequeña comparación de rendimiento y comportamiento de los diferentes tipos de GC al ejecutar el ejemplo anterior.

6.1. GC Serie

GC Serie

Este primer ejemplo usa el recolector en serie, que sigue el proceso descrito en el apartado del Hotspot de Java. En la zona resaltada de las gráficas se puede apreciar cómo cada vez que alguna de las zonas de supervivientes son vaciadas, o bien pasa a la siguiente zona, o, si se cumplen los criterios de envejecimiento, se copian a la Old Gen.

6.2. GC en Paralelo con 2 hilos

GC Paralelo

En el caso del recolector en paralelo, aunque las pasadas del GC sigan siendo Stop-the-world, al aprovechar el uso de varias CPUs, los minor collections pueden ser mucho más frecuentes, resultando en tamaños de Eden y Survivor (Young Generation) sensiblemente mayores que el GC en serie.

6.3. GC con Concurrent Mark Sweep (GCCMS)

GC CMS

Debido a la naturaleza del algoritmo CMS en el que los eventos Stop-the-world de los major collection de la Old Generation son mucho más frecuentes, pero sensiblemente más rápidos, se obtienen este tipo de resultados. El tamaño en el Heap de esta partición es mucho mayor (1,8GB en comparación a los 1,3GB de los otros métodos), pero el tiempo empleado en realizar limpieza es exponencialmente más pequeño (316ms vs ~5s).

6.4. Garbage First GC (GCG1)

GC G1

Ya que Garbage First GC (G1) mantiene siempre una serie de áreas del Heap libres y controladas, las pausas para reciclaje serán menores y se evitará por completo el llenado de cualquiera de las generaciones. Este fenómeno se hace evidente en las gráficas sacadas por VisualVM, en el que vemos que ni el espacio de Survivor (con G1 solo existe un espacio de este tipo) ni el de la Old Generation son limpiados en ningún momento.

7. Conclusión

Herramientas como los flags de ejecución de la JVM, como -verbosegc correspondientes al recolector de basura en conjunto con plugins de visualización de datos, como VisualVM + VisualGC, permiten recabar información sobre el rendimiento del GC en nuestras aplicaciones.

La elección del mejor tipo de GC para un determinado problema es una tarea compleja que debe ser sopesada tanto en términos de uso de memoria, como de frecuencia de dicho uso.

Inyección de dependencias a través de CDI en Tomcat

$
0
0

En pocos años CDI se ha convertido en un aspecto clave de la plataforma Java EE y su importancia sigue creciendo mientras que otras especificaciones delegan parte de sus responsabilidades en ella (como es el caso de JTA con @Transactional ó JBV y las validaciones a nivel de métodos).

0. Índice de contenidos.

1. Introducción

Con el fin de mantener un código poco acoplado, límpio y reusable, uno de los aspectos que deben considerarse básicos a la hora de crear una aplicación es el de mantener una librería o framework a través del cual, de una manera rápida y sencilla, realizar inyecciones de dependencias.

A lo largo de los años el framework Spring ha venido dando solución a esta necesidad, puesto que con tan solo un fichero de configuración (application-context) y varias anotaciones, los objetos necesarios pueden ser inyectados donde sean requeridos.

Desde Red Hat no son ajenos a esta necesidad y, con la JSR-299 en la mano (Context and Dependency Injection for Java EE platform), decidieron realizar su propia implementación.

La especificación de CDI (Context And Dependency Injection) aparece por primera vez en Java EE 6 (CDI 1.0) y es revisada (mínimamente, CDI 1.1) en Java EE 7.

Esta especificación nos ofrece, a grandes rasgos, la posibilidad de introducir componentes en el ciclo de vida de una aplicación Java que mantenga ámbitos bien acotados y la capacidad de inyectar dependencias en el código.

En pocos años CDI se ha convertido en un aspecto clave de la plataforma Java EE y su importancia sigue creciendo mientras que otras especificaciones delegan parte de sus responsabilidades en ella (como es el caso de JTA con @Transactional ó JBV y las validaciones a nivel de métodos).

Tal es su calado dentro de la plataforma Java EE que actualmente se esta realizando una revisión importante (CDI 2.0) con el objetivo de incorporarla en Java EE 8 (JSR-365 e incluso un early-draft).

Con el objetivo ver como funciona la inyección de dependencias a través de CDI, proponemos crear una aplicación web JSF en la que, de una manera sencilla, veremos como integrar CDI en un servidor web Apache Tomcat 7.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: MacBook Pro 17′ (2.66 GHz Intel Core i7, 8GB DDR3 SDRAM).
  • Sistema Operativo: Mac OS X Lion 10.10.3.
  • NVIDIA GeForce GT 330M 512Mb.
  • Crucial MX100 SSD 512 Gb.
  • Software:
    • Eclipse Mars
    • Java JDK 1.8
    • Maven 3
    • Apache Tomcat 8
    • JBoss Weld 2.2.13

3. Dependencias

En este caso vamos a hacer uso del servidor web Apache Tomcat 8 y la implementación referencia para CDI: “Weld”, un proyecto Jboss de Red Hat y distribuida bajo licencia ASL 2.0.

A continuación las dependencias de maven:


  	
		javax.enterprise
		cdi-api
		1.2
	
  	
		org.jboss.weld.servlet
		weld-servlet
		2.2.13.Final
	
	
		org.glassfish.web
		el-impl
		2.2
	
	
		javax.faces
		jsf-api
		2.1
	
	
		org.glassfish
		javax.faces
		2.2.11
	
	
		javax.ejb
		javax.ejb-api
		3.2
	
  

4. Configuración

Son tres los ficheros de configuración establecidos para el correcto despliegue de la aplicación, a continuación se detalla su contenido.

context.xml

Situado en la ruta /main/resources/META-INF/, es el lugar en el que definiremos como resource el BeanManager, que ofrece las operaciones necesarias para tratar con el contexto.



	

web.xml

El descriptor de despliegue en el que se define como desplegar la aplicación web. Debe establecere en la carpeta WEB-INF.


	Archetype Created Web Application
	
		index.xhtml
	
	
	   BeanManager
	   javax.enterprise.inject.spi.BeanManager
	 
	 
	   javax.faces.PROJECT_STAGE
	   Development
	 
	
		Faces Servlet
		javax.faces.webapp.FacesServlet
		1
	
	
		Faces Servlet
		*.xhtml
	

beans.xml

Se trata del fichero donde se definirán los beans para las inyecciones (en nuestro caso quedará vacío puesto que manejaremos las inyecciones a través de anotaciones). Debe establecere en la carpeta WEB-INF.


5. Uso

Para ver el funcionamiento de las inyecciones mediante CDI vamos a crear un proyecto muy básico en el que se definiran una clase “servicio” que nos devolverá un mensaje, un bean en la que se inyectará la clase servicio y una página JSF en la que se hará uso del bean.

En primer lugar tenemos una interfaz simple que declara el servicio que vamos a ofrecer:

public interface Greating {
	
	String getSalute();
	
}


A continuación la clase servicio que implementa el interfaz:

import javax.enterprise.context.Dependent;
import javax.inject.Named;

@Named
@Dependent
public class GreatingService implements Greating{
	
	@Override
	public String getSalute(){
		return "Hello World!";
	}
}

Como se puede ver, dos son las anotaciones que hemos utilizado en este caso, @Named, que sirve para asociar un nombre al bean (en este caso GreatingService), y @Dependent, que establece el ciclo de vida y el contexto copiandolos del Bean donde se inyecte.

Como se puede ver la única lógica que mantiene el servicio es la generación de un String con el típico “Hello World!!”


A continuación tenemos el bean alque se llamará desde la página web:

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;

@Named
@RequestScoped
public class MessageServerBean {

	@Inject
	Greating greatingService;
	
	public String getMessage(){
		return greatingService.getSalute();
	}
}

En este caso se utiliza también la anotación para asociar el nombre con el bean y a demás aparecen dos nuevas anotaciones, @RequestScoped que establece el ciclo de vida del bean acotado a la petición y finalmente la anotación @Inject que identifica el punto en el que se inyectará el bean.

Los beans en CDI viven en ámbitos bien definidos, y se crean y se destruyen bajo demanda por el contenedor. En este sentido CDI ofrece una serie anotaciones predefinidas para establecer dichos ámbitos:

  • @RequestScoped: El bean tiene un ciclo de vida relativo a la petición.
  • @SessionScoped: El bean tiene un ciclo de vida relativo a la sesión.
  • @ApplicationScoped: El bean tiene un ciclo de vida relativo a la aplicación.
  • @ConversationScoped: El bean tiene un ciclo de vida relativo a una conversación.

Finalmente tenémos la aplicación web:




	
		Hello!
	
	
	    Hello from Facelets
	    
Message is: #{messageServerBean.message}
Message Server Bean is: #{messageServerBean}


Tras levantar el servidor Tomcat y desplegar la aplicación se puede visitar la web, donde se debe visualizar algo similar a lo que se ve en la imagen a continuación:

Resultado Tomcat

6. Decoradores

Un decorador es una clase Java que tiene la anotación javax.decorator.Decorator y que se corresponde con un elemento “decorators” en el fichero bean.xml

Este tipo de anotación nos permite completar o añadir más lógica a un bean ya existente dentro de nuestra aplicación.

A modo de ejemplo vamos a realizar una pequeña modificación sobre el código de nuestra aplicación y así ver como se puede utilizar

Vamos a comenzar creando nuestro decorador:

import javax.decorator.Decorator;
import javax.decorator.Delegate;
import javax.inject.Inject;

@Decorator
public abstract class GreatingDecorator implements Greating {

	@Inject
	@Delegate
	@Any
	Greating greating;
	
	@Override
	public String getSalute() {
		return greating.getSalute() + " How are you???";
	}

}

En este caso, la anotación @Decorator se encarga de espoecificar que la clase es un Decorador, mientras que la anotación @Delegate establece el punto en el que se debe inyectar el bean a decorar. @Any sirve para establecer el punto de inyección pero sin establecer el nombre del bean inyectado.


A continuación nos dirigimos al fichero beans.xml y dejamos constancia de este decorador:


      
      	cdi.webapp.GreatingDecorator
      

Tras levantar el servidor Tomcat y desplegar de nuevo la aplicación se puede visitar la web, donde se debe visualizar algo similar a lo que se ve en la imagen a continuación:

Resultado Tomcat 2

7. Conclusiones

CDI es la opción de presente y de futuro que ofrece la plataforma Java EE en cuanto a la definición de contextos y manejo de inyección de dependencias.

Como hemos visto, su configuración y uso en entornos Tomcat es rápida y sencilla.
A través de pocas líneas de código se puede disfrutar de todas las ventajas que ofrece, como el establecimiento de ámbitos o el manejo de las inyecciones de dependencias.

En futuros tutoriales trataremos aspectos más avanzados como pueden Alternativas, Eventos ó Interceptadores.

Introducción a las directivas en AngularJS

$
0
0

Las directivas en AngularJS permite la manipulación y reutilización de código HTML, en este tutorial, vamos a ver una introducción a las mismas

0. Índice de contenidos.

1. Introducción

Siguiendo la propia documentación de AngularJS, las directivas son marcas en los elementos del árbol DOM, en los nodos del HTML, que indican al compilador de Angular que debe asignar cierto comportamiento a dichos elementos o transformarlos según corresponda.

Podríamos decir que las directivas nos permiten añadir comportamiento dinámico al árbol DOM haciendo uso de las nativas del propio AngularJS o extender la funcionalidad hasta donde necesitemos creando las nuestras propias. De hecho, la recomendación, es que sea en las directivas en el único sitio donde manipulemos el árbol DOM, para que entre dentro del ciclo de vida de compilación, binding y renderización del HTML.

Las directivas son la técnica que nos va a permitir crear nuestros propios componentes visuales encapsulando las posibles complejidades en la implementación, normalizando y parametrizándolos según nuestras necesidades.

En este tutorial vamos a ver qué tipos de directivas existen y unos ejemplos básicos de directivas tanto nativas como propias a modo de introducción para que podamos hacernos una idea de para qué sirven y hasta donde podemos llegar con ellas.

2. Entorno.

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.3 GHz Intel Core i7, 16GB DDR3).
  • Sistema Operativo: Mac OS Mavericks 10.9.4
  • AngularJS 1.4.0.
  • Plunker

3. Tipos.

La primera tipología de directivas ya la hemos comentado: hay directivas nativas del propio Angular y cualquiera puede crear sus propias directivas o hacer uso de directivas de terceros.

En el siguiente código podemos encontrar las siguientes directivas:

<div class="container" ng-app="myApp" ng-controller="MainCtrl"> 
    <div class="btn-group">
        <label class="btn btn-primary" ng-model="radioModel" btn-radio="'Auto'">Auto</label>
        <label class="btn btn-primary" ng-model="radioModel" btn-radio="'Manual'">Manual</label>
    </div>
</div>
  • ng-app (ngApp): es la directiva nativa de Angular que arranca nuestra aplicación estableciendo el elemento raíz de la misma.
  • ng-controller (ngController): es la directiva nativa que asocia a una vista el controlador que mantiene la vinculación del modelo y gestiona los eventos de control de la misma.
  • ng-model (ngModel): es la directiva nativa que vincula una propiedad en concreto del modelo declarado en el controlador a un componente de la vista; normalmente a un componente de formulario para obtener información.
  • btn-radio (btnRadio):es una directiva de angular-bootstrap que transforma una etiqueta en un componente de tipo radio button de bootstrap

Lo normal es que las directivas que comienzan por ng* sean de Angular y cada modulo tenga su propio prefijo, pero no tiene porque, de hecho las directivas de bootstrap no lo tienen.

La segunda tipología que podemos encontrar en las directivas es su ámbito de aplicación a los nodos del árbol DOM, de modo tal que se pueden asociar:

  • mediante la declaración de un atributo ‘A’ en cualquier elemento del DOM
    <div customize-it></div>
  • mediante la declaración de un clase css ‘C’ en cualquier atributo de tipo class de un elemento del DOM
    <div class="customize-it"></div>
  • mediante la asignación a un comentario ‘M’ en cualquier bloque de código comentado
    <!-- directive: customize-it -->
  • mediante la declaración de un elemento ‘E’ del propio árbol DOM
    <customize-it></customize-it>
  • también se puede combinar la declaración de un elemento con la asignación de un atributo ‘EA’ o añadirle también la declaración en un comentario ‘EAC’

Lo normal es disponer y declarar directivas a nivel de elemento y atributo.


4. Directivas nativas.

Como ya vimos en los primeros pasos con AngularSJS disponemos de una serie de directivas nativas que nos permiten vincular el modelo de AngularJS a la vista, exponiéndolo mediante un controlador.

Se puede acceder a la documentación de las directivas de Angular para comprobar que existen las suficientes directivas como para soportar sobradamente las necesidades de cualquier aplicación SPA y llama la atención que incluso los componentes estándar de formulario de HTML5 están recubiertos con una directiva, que permite básicamente asociar una propiedad del modelo al componente de formulario.

Me remito en este punto a la documentación y al ejemplo planteado en el punto anterior, para centrarnos en el siguiente.

5. Directivas propias.

A la hora de crear directivas, la recomendación es que se nombren con camelCase y para hacer referencia a las mismas se separan con guiones; aunque se podrían utilizar otras técnicas al usar guiones evitamos errores si usamos herramientas que validen nuestro HTML.

El siguiente código podemos ver cómo declarar una directiva en un módulo ya existente:

(function() {

  angular.module('tnt.ui.components')

  .directive('userInfo', [function() {
    return {
      restrict: 'E',
      template:'Nombre: {{user.name}}, email: <a href="mailto:{{user.email}}">{{user.email}}</a>'
    };
  }]);

}());

Para hacer uso de la misma bastaría con el siguiente código:

<div ng-app="tnt.ui.components" ng-controller="DemoDirectivesCtrl">
    <user-info></user-info>
</div>

La directiva da por hecho que el ámbito del controlador existe una propiedad “user” con la información a renderizar del usuario:

(function() {

  angular.module('tnt.ui.components').controller('DemoDirectivesCtrl', 
  function($scope){
  	$scope.user = {
    name: 'Jose',
    email: 'jmsanchez@autentia.com'
  };
}());

A continuación veremos todos los parámetros disponibles para la declaración de una directiva y comprobaremos que lo visto no es la manera más recomendable si queremos que nuestra directiva sea realmente reutilizable.

Aquí os dejo un plunker con el código de esta primera directiva: http://plnkr.co/WxsORtycwMlrGyFhgFju.

4.1 API.

A la hora de declarar una directiva podemos asignar las siguientes propiedades:
  • restrict: para delimitar el ámbito de declaración de la directiva a un atributo, elemento, estilo o comentario. Es la tipología de directivas que vimos anteriormente y se especifica con un literal ‘A’, ‘E’, M, ‘C’ o ‘EA’.
  • template: especifica el código HTML que se compilará en el ámbito de la directiva aplicando las propiedades definidas también en el ámbito de la misma
  • templateUrl: la recomendación es que cuando el código de la plantilla se hace demasiado grande se externalice en un fichero html aparte al que podría apuntar la propiedad templateUrl que es como un ngInclude,
  • scope: todas las directivas tienen asociado un ámbito que por defecto es el de su padre, lo normal es que sea un controlador. Mediante este atributo que puede adoptar los siguientes valores se puede modificar:
    • false: es el comportamiento por defecto, hereda el ámbito y cualquier modificación tanto dentro como fuera de la directiva de una propiedad del modelo se verá reflejada en ambos sentidos,
    • true: se crea un nuevo ámbito haciendo una copia del ámbito del padre de modo tal que, al ser un nuevo ámbito las modificaciones en el modelo que se realicen dentro de la directiva no se verán reflejadas hacia el exterior, sin embargi las modificaciones en el modelo del ámbito del padre si tendrán reflejo en el ámbito de la directiva,
    • {}: se creará un ámbito nuevo independiente del padre; es el ámbito recomendado para que realmente una directiva pueda ser reutiliazada y no dependa ni herede todo el ámbito de su padre. Si bien, dentro de la declaración de ámbito se pueden pasar valores especificando una serie de prefijos que van a permitir no solo pasar propiedades del modelo del ámbito del padre sino vincularlas en uno o ambos sentidos. Estas propiedades se declaran como atributos del elemento HTML de la directiva. Así:
      • @: vinculación en un solo sentido, al crearse el ámbito de la directiva el valor de esta propiedad se asigna al nuevo ámbito de tal forma que las modificaciones dentro de la directiva no afectan al ámbito del padre pero sí a la contra. El valor de la propiedad debe ser evaluada {{}} en la declaración del atributo.
      • =: vinculación en ambos sentidos, espera que el valor de la propiedad sea una referencia al modelo, no una expresión evaluable como en el caso anterior. La vinculación se hace en ambos sentidos de tal forma que las modificaciones tanto fuera como dentro de la directiva del modelo afectan a ambos.
      • &: vinculación de métodos que permite invocar desde la directiva a métodos declarados en el ámbito del padre

      Por último, todos estos prefijos pueden ir acompañados de una redefinición del nombre de la propiedad en el ámbito.

      Sobre el ejemplo inicial haremos las siguientes modificaciones en la declaración de la directiva:

      (function() {
      
        angular.module('tnt.ui.components')
      
        .directive('userInfo', [function() {
          return {
            restrict: 'E',
            scope:{
              name: '@',
              email: '@',
              rate: '=',
              click: '&'
            },
            template:'
      ' }; }]); angular.module('tnt.ui.components').controller('DemoDirectivesCtrl', function($scope){ $scope.users = [{ name: 'Jose', email: 'jmsanchez@autentia.com', rate: '1' }, { name: 'Gustavo', email: 'gmartin@autentia.com', rate: '2' } ]; $scope.vote = function(user){ console.log(user); }; }); }());

      Adaptamos el código HTML a los parámetros que ahora recibe la directiva y comprobamos que ahora sí, la directiva es totalmente reutilizable:

      <div ng-app="tnt.ui.components" ng-controller="DemoDirectivesCtrl">
          <user-info ng-repeat="user in users" name="{{user.name}}" email="{{user.email}}" rate="user.rate" click="vote(user)"></user-info>
      </div>
  • link: define una función con el siguiente contrato function link(scope, element, attrs) { … } recibiendo el ámbito de la directiva, el elemento del árbol DOM al que está vinculado la directiva, envuelto por un objeto de la implementación de jQuery de Angular ‘jqLite’ y un objeto con los pares de nombre y valor de los atributos de la directiva declarados en el elemento HTML. A través del parámetro element y pudiendo acceder tanto al ámbito y, como consecuencia, el valor de las propieaddes como a los atributos que recibe, estén o no declarados en el ámbito se puede manipular el nodo HTML y todos sus hijos antes de renderizarse.
  • transclude: adoptando el valor a true permite crear directivas que encapsulan otras directivas o envuelven porciones de código con una envoltura permitiendo insertar el código envuelto en la plantila con la directiva <ng-transclude>
  • require: permite declarar como abligatorios parámetros en la directiva que hacen referencia a otras directivas.
  • controller: permite declarar un controlador a nivel de la directiva. La diferencia entre la función de link es que el código del controlador se ejecuta antes de la compilación y el del link después. No se debería incluir manipulación de árbol DOM en un controlador que debería reservarse para la inicialización o manipulación de las propiedades del ámbito de la directiva.

Aquí os dejo otro plunker con el ejemplo antes expuesto http://plnkr.co/edit/rR3BiWGOqh3IquWVrnDK

5. Referencias.

6. Conclusiones.

Las directivas son una parte muy interesante de Angular que nos permiten la reutilización de código.

Si los quisiéramos comparar con otras tecnologías de presentación serían como los componentes por composición de JSF2 o las plantillas de JSTL o velocity.

Idependientemente de la tecnología debemos intentar aplicar una serie de patrones que nos faciliten la reutilización de código para no caer en el DRY, pero siempre teniendo en cuenta los principios KISS y YAGNI; por eso la recomendación es crear directivas o componentes reutilizables a medida que vamos desarrollando y estandarizar el desarrollo de forma gradual, elevando a nivel de arquitectura aquello que se vaya necesitando.

Un saludo.

Jose

Plantillado de índices y tipado de documentos por configuración externa en Elasticsearch

$
0
0

En este tutorial veremos las alternativas de configuración de Elasticsearch a través del plantillado de índices y el tipado de documentos de forma externa.

0. Índice de contenidos

1. Introducción

Elasticsearch es un servidor de búsqueda basado en Apache Lucene. Provee un motor de búsqueda de texto completo, distribuido y con capacidad de multitenencia con una interfaz web RESTful y con documentos JSON. Elasticsearch está desarrollado en Java y está publicado como código abierto bajo las condiciones de la licencia Apache.

Los motores de búsqueda nos permiten la búsqueda y acceso a información indexada de forma instantánea, que de otra forma supondría una gran penalización en tiempo y en rendimiento. Un ejemplo típico de uso sería la búsqueda de artículos en un blog utilizando como patrón de búsqueda alguna frase significativa que pudiera aparecer con alta probabilidad en artículo que se pretende encontrar.

2. Entorno

El tutorial está escrito utilizando el siguiente entorno:
  • Hardware: MacBook Pro 15′ (2 Ghz Intel Core i7, 8GB DDR3 1333 Mhz)
  • Sistema operativo: Mac OS X Yosemite 10.10.3
  • Software
    • Java JDK 1.8
    • Maven 3

3. Instalación y ejecución de Elasticsearch

Podemos encontrar la información de instalación y ejecución de Elasticsearch cubierta en este otro tutorial: Integración de Elasticsearch con Spring + MySQL

4. Preparación del proyecto Maven con Spring Data Elasticsearch

Con el fin de poder realizar las pruebas necesarias de plantillado y tipado externo de este tutorial, vamos a preparar un proyecto Maven que utilizaremos más adelante para realizar nuestros tests de integración que pruebe la configuración que estamos indicándole al motor de Elasticsearch.

4.1 Estructura general del proyecto

proyecto-maven-1

4.2 Fichero pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
			xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
			<modelVersion>4.0.0</modelVersion>
			<groupId>com.autentia.tutoriales</groupId>
			<artifactId>elasticsearch-config-tutorial</artifactId>
			<version>0.0.1-SNAPSHOT</version>
			<name>Elasticsearch Config Tutorial</name>

			<dependencies>
				<!-- Spring Data Elasticsearch -->
				<dependency>
					<groupId>org.springframework.data</groupId>
					<artifactId>spring-data-elasticsearch</artifactId>
					<version>1.2.0.RELEASE</version>
				</dependency>

				<!-- Spring -->
				<dependency>
					<groupId>org.springframework</groupId>
					<artifactId>spring-test</artifactId>
					<version>4.1.7.RELEASE</version>
					<scope>test</scope>
				</dependency>

				<dependency>
					<groupId>org.springframework</groupId>
					<artifactId>spring-context</artifactId>
					<version>4.1.7.RELEASE</version>
				</dependency>

				<!-- Tests -->
				<dependency>
					<groupId>junit</groupId>
					<artifactId>junit</artifactId>
					<version>4.12</version>
				</dependency>

				<dependency>
					<groupId>org.hamcrest</groupId>
					<artifactId>hamcrest-all</artifactId>
					<version>1.3</version>
					<scope>test</scope>
				</dependency>

				<dependency>
					<groupId>org.mockito</groupId>
					<artifactId>mockito-all</artifactId>
					<version>1.10.19</version>
					<scope>test</scope>
				</dependency>
		
			</dependencies>
		</project>

4.3 Fichero elasticsearch-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
       xsi:schemaLocation="http://www.springframework.org/schema/data/elasticsearch http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">

    <elasticsearch:transport-client id="client" cluster-name="elasticsearch" cluster-nodes="127.0.0.1:9300"/>
    
    <bean name="elasticsearchTemplate" class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
        <constructor-arg name="client" ref="client"/>
    </bean>

4.4 IndexedDocument.java

package com.autentia.tutoriales.elasticsearch;

import org.springframework.data.annotation.*;
import org.springframework.data.elasticsearch.annotations.*;

import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.annotation.JsonCreator.Mode;

@Document(indexName = "indice_prueba", type = "documento_indexado_type")
public class IndexedDocument {

	@Id
	private final String id;

	private final String title;

	private final String description;

	private final String notStored;

	@JsonCreator(mode = Mode.PROPERTIES)
	public IndexedDocument(@JsonProperty("id")
	final String id, @JsonProperty("title")
	final String title, @JsonProperty("description")
	final String description, @JsonProperty("notStored")
	final String notStored) {
		super();
		this.id = id;
		this.title = title;
		this.description = description;
		this.notStored = notStored;
	}

	public String getId() {
		return id;
	}

	public String getTitle() {
		return title;
	}

	public String getDescription() {
		return description;
	}

	public String getNotStored() {
		return notStored;
	}

}

4.5 Fichero IndexedDocumentTest_IT

package com.autentia.tutoriales.elasticsearch.test;

import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;

import java.util.*;

import org.elasticsearch.index.query.*;
import org.junit.*;
import org.junit.runner.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.data.elasticsearch.core.*;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.test.context.*;
import org.springframework.test.context.junit4.*;

import com.autentia.tutoriales.elasticsearch.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:/elasticsearch-context.xml")
public class IndexedDocumentTest_IT {

	@Autowired
	private ElasticsearchTemplate elasticsearchTemplate;

	@Before
	public void setUp() {
		elasticsearchTemplate.deleteIndex(IndexedDocument.class);
	}

}

5. Plantillado de índices

Una vez disponemos de nuestro proyecto para codificar los tests, pasamos a configurar el Elasticsearch.

Podemos aplicar un sistema de plantillado de índices, el cual nos permitirá establecer parámetros de configuración de los futuros índices que se creen en el motor de búsqueda.

Para ello, deberemos acceder a la carpeta de configuración de Elasticsearch $ELASTICSEARCH_HOME/config, y crear una nueva carpeta llamada ‘templates’.

La carpeta ‘templates’ es la carpeta por defecto donde nuestro Elasticsearch buscará las posibles plantillas que puede aplicar a nuestros índices en el momento de su creación. Las plantillas de índices deben ser creadas en formato JSON.

En nuestro tutorial, vamos a crear una plantilla de índice que aplique a cualquier índice que vaya a ser creado nuevo, y lo que va a hacer es desactivar el campo “_source” de los documentos indexados de dicho índice.

Para ello, sobre $ELASTICSEARCH_HOME/config/templates creamos, por ejemplo, el fichero ‘default_template.json’ (El nombre no es determinante).

templates-1

En dicho fichero, vamos a almacenar el siguiente contenido:

{
        "template" :"*",
        "mappings":{
                "_default_":{
                        "_source":{
                                "enabled" : false
                        }
                }
        }
}

Con esta configuración, le estamos indicando al Elasticsearch que cualquier índice que cumpla con el nombre “cualquiera” (el asterisco es el caractér wildcard de Elasticsearch), y que además no importa en qué tipado se esté creando el documento (mappings->_default_), se deberá establecer el campo “_source” a “enabled:false”. Lo que quiere decir, que para cualquier documento, sin importar el índice o su tipado, se desactivará el campo “_source”.

5.1 Test de Integración del campo “_source” desactivado

Para poder verificar que el motor de Elasticsearch utiliza nuestra configuración de plantillado de índices, vamos a realizar un pequeño test de integración en el que indexemos un documento e inmediatamente después lo busquemos en Elasticsearch, y comprobemos que, efectivamente, el documento que recuperamos nos viene de vuelta con todos los atributos a null. Es decir, que como no hemos definido (todavía) ningún atributo a almacenar explícitamente, y hemos eliminado el campo “_source”, Elasticsearch indexará pero NO almacenará ningún campo.

Para nuestro test, vamos a verificar unicamente que el campo “notStored” viene a null. Recordad que debemos de tener corriendo el servidor de Elasticsearch en la IP y puerto que hemos indicado en el ‘elasticsearch-context.xml’.

@Test
		public void givenIndexedDocumentWithNoSourceWhenRetriveNotStoredFieldThenReturnedNull() {
			// given
			final IndexQuery indexQuery = new IndexQuery();
			indexQuery.setObject(new IndexedDocument("1", "Documento indexado",
					"Esto es una prueba de indexación de contenido", "Este contenido no se deberia guardar."));
			elasticsearchTemplate.index(indexQuery);
			elasticsearchTemplate.refresh(IndexedDocument.class, true);

			// when
			final QueryBuilder queryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.matchAllQuery());
			final SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(queryBuilder).withFields("notStored")
					.build();
			final List indexedDocuments = elasticsearchTemplate.queryForList(searchQuery,
					IndexedDocument.class);

			// then
			assertThat(indexedDocuments.size(), is(equalTo(1)));
			assertThat(indexedDocuments.get(0).getNotStored(), is(nullValue()));
		}

Lanzamos el test y verificamos que todo ha ido correctamente.

test-01

Además de ello, podemos ir a la página de administración del motor Elasticsearch y verificar la configuración del índice de forma empírica. Aquí podemos observar que el campo “_source” está desactivado para el índice que estamos testeando. (Para poder acceder a está página de administración, es necesario haber instalado previamente el plugin HEAD de Elasticsearch).

plugin-head-1

6. Tipado de documentos de un índice

Una vez visto como podemos configurar un índice de manera global, vamos a ver como configurar el tipado de un documento a través de ficheros JSON.

Para poder configurar el tipado de un documento indexado mediante configuración JSON, deberemos acceder a la carpeta ‘config’ del $ELASTICSEARCH_HOME, y allí crear la carpeta ‘mappings’. Debería quedar así: $ELASTICSEARCH_HOME/config/mappings.

En esta carpeta debemos crear todos los índices que queramos configurar en forma de carpeta, y posteriormente en el interior de las carpetas recien creadas, deberemos crear nuestros ficheros JSON con el mismo nombre del tipo del documento que vayamos a indexar.

Para nuestro ejemplo, tenemos que tener encuenta que nuestro índice se llama “indice_prueba”, y que nuestro tipo de documento se llama “documento_indexado_type”. Por lo tanto, dentro de la carpeta ‘mappings’, creamos la carpeta ‘indice_prueba’, y en el interior de ésta, creamos el fichero ‘documento_indexado_type.json’.

mappings-1

En el interior del fichero ‘documento_indexado_type.json’, escribimos el tipado de los documentos indexados que se basen en dicho tipo. Para nuestro ejemplo, vamos a indicar que se almacenen todos los contenidos indexados menos precisamente nuestro atributo ‘notStored’.

{
   "documento_indexado_type":{  
      "properties":{  
         "description":{  
            "type":"string",
            "store":true
         },
         "id":{  
            "type":"string",
            "store":true
         },
         "notStored":{  
            "type":"string"
         },
         "title":{  
            "type":"string",
            "store":true
         }
      }
   }
}

6.1 Test de integración de los campos ‘store':true

Del mimso modo que la vez anterior, vamos a codificar un test de integración para verificar que el motor de Elasticsearch ya almacena los campos que hemos marcado como ‘store:true’. Para ello, indexaremos un documento y posteriormente realizaremos una búsqueda sobre él recuperando aquellos campos que deberían haber sido almacenados para ver si nos devuelve el contenido correcto.

@Test
		public void givenIndexedDocumentWithNoSourceWhenRetrieveStoredTitleAndStoredDescriptionThenReturnTheTitleAndDescription() {
			// given
			final IndexQuery indexQuery = new IndexQuery();
			indexQuery.setObject(new IndexedDocument("1", "Documento indexado",
					"Esto es una prueba de indexación de contenido", "Este contenido no se deberia guardar."));
			elasticsearchTemplate.index(indexQuery);
			elasticsearchTemplate.refresh(IndexedDocument.class, true);

			// when
			final QueryBuilder queryBuilder = QueryBuilders.boolQuery().must(QueryBuilders.matchAllQuery());
			final SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(queryBuilder)
					.withFields("title", "description").build();
			final List indexedDocuments = elasticsearchTemplate.queryForList(searchQuery,
					IndexedDocument.class);

			// then
			assertThat(indexedDocuments.size(), is(equalTo(1)));
			assertThat(indexedDocuments.get(0).getTitle(), is(equalTo("Documento indexado")));
			assertThat(indexedDocuments.get(0).getDescription(),
					is(equalTo("Esto es una prueba de indexación de contenido")));
		}

Lanzamos los tests y verificamos que todo ha ido correctamente.

test-02

Por último, verificamos visualmente que el tipado del contenido ha cambiado en la pantalla de administración del motor Elasticsearch.

plugin-head-2

7. Limitaciones en configuración programática con Spring Data Elasticsearch

Como hemos podido comprobar, hemos configurado índices y tipados a través de ficheros de configuración externos almacenados directamente en el directorio de instalación del Elasticsearch.

Pero, ¿qué pasa si queremos configurarlo en un entorno de desarrollo y queremos almacenar los ficheros de configuración dentro de un JAR para poder lanzar test de integración de manera que los ficheros estén autocontenidos en la configuración del propio test? Pues lo cierto es que existe una limitación a la hora de indicarle la ruta de los ficheros de configuración que queramos que el motor de búsqueda utilice para tal fin, ya que debemos indicarle la ubicación absoluta del mismo.

En el caso de que estemos realizando tests de integración y los ficheros se encuentren dentro de la misma librería, no habría ningún problema.

Pero este escenario es poco realista, y normalmente nuestras aplicaciones se despliegan en algún servidor de aplicaciones o contenedor de servlets empaquetadas como .war, y dentro estarían contenidas todas las librerías .jar. En este caso, al estar los ficheros de configuración dentro del .jar, no se le puede indicar una ruta absoluta al Elasticsearch en el test, puesto que la ruta absoluta física no existe.

Por esta razón, si quisieramos disponer de un entorno de desarrollo para poder lanzar test de integración separados del propio entorno de integración, deberíamos disponer de una instancia de Elasticsearch a utilizar en desarrollo y configurar mediante ficheros externos (como hemos visto en este tutorial) las correspondientes configuraciones que queramos aplicar.

8. Referencias

8. Conclusiones

En este tutorial hemos visto como poder realizar plantillas de índices y tipado de documentos a través de ficheros de configuración externos almacenados en la propia instalación del Elasticsearch.

Espero que este tutorial os haya servido de ayuda. Un saludo.

Daniel Rodríguez

daniel.rodriguez@autentia.com

Twitter: @DaniRguezHdez

GitHub: dani-r-h

DBeaver

$
0
0

En este tutorial vamos a describir las operaciones básicas que podemos realizar sobre DBeaver y dos opciones que me han resultado muy útiles.

0. Índice de contenidos

1. Introducción

DBeaver es un gestor universal de BBDD multiplataforma, que ofrece soporte a las bases de datos más conocidas del mercado (MySQL, Oracle, DB2, SQL Server, PostgreSQL, etc ..) , así como algunas NoSQL (MongoDB, Cassandra).

Como características más destacada ofrece:

  • Navegar por la estructura de BBDD
  • Editar tablas, columnas, keys, etc.
  • Permite ejecutar scripts SQL.
  • Modificar columnas resultantes de una consulta.
  • Resaltado de SQL (específico para cada tipo de motor)
  • Autocompletado en el editor.
  • Soporte para ver y editar archivos BLOB.
  • Exportar tablas o resultados de querys.
  • Generación de entidad-relación.
  • Comparación de esquemas.

Dispone de 2 versiones Community y Enterprise, las dos son gratuitas (licencia GPL 2), la única diferencia es que la Enterprise Edition incluye el soporte a base de datos NoSQL.

2. Entorno

El tutorial está escrito utilizando el siguiente entorno:
  • Hardware: MacBook Pro 15′ (2.3 Ghz Intel Core i7, 16GB DDR3 SDRAM)
  • Sistema operativo: Mac OS X Mavericks 10.9.5.
  • Software: DBeaver 3.4.2

3. Instalación

Previamente a la instalación debemos tener en cuenta que DBeaver está hecha con java, por lo que deberíamos tener instalada la jre 1.6 o superior.

Una vez solventado este punto accedemos a su web http://dbeaver.jkiss.org/download/

image00

Seleccionamos las descargas para Mac OS, (versión Enterprise Edition), descargamos el zip, lo descomprimimos en la ubicación deseada y tendremos los siguientes ficheros:

image01

Doble click en dbeaver.app

image02

4. Crear Conexión MySQL

En este apartado vamos a ver cómo se crea una conexión a MySQL (para otros motores sería algo similar)

Para crear una nueva conexión File > New

image03

Seleccionamos la opción ‘Connection’ y pulsamos Next

image04

De todas las opciones, seleccionamos MySQL, y pulsamos Next

image05

Introducimos los datos de conexión y pulsamos Next

image06

Introducimos un nombre para la conexión, damos a Finish y ya tendríamos la conexión creada

image07

Desplegamos nuestra conexión y podremos ver todos los objetos de la BBDD

image08

5. Operaciones Básicas

Vamos a describir las opciones básicas que presenta la herramienta (explorar tablas, filtrado, editor SQL)

Desplegamos el esquema sakila > Tables para ver todas las tablas del esquema

image09

Seleccionamos la tabla customer haciendo doble click

image10

En la primera pestaña ‘Properties’ vemos toda la información de la tabla (columnas, constraint, references, etc..).

En la pestaña ‘Data’ dispondremos de un grid con el contenido

image11

En esta vista podremos añadir filtros para refinar la consulta

image12

La pestaña ‘Diagram’ nos permite ver gráficamente las relaciones que mantiene la tabla (las FK hacía otras entidades)

image13

Podríamos navegar a través del modelo haciendo doble click en la entidad seleccionada, por ejemplo si deseamos conocer las relaciones de store, realizamos doble click y nos mostraría sus relaciones

image14

Para crear un script con nuestras sentencias SQL abriríamos un ‘Editor SQL’ pulsamos el icono image15

En este editor podremos escribir nuestra sentencias SQL cómodamente, aprovechando las opciones de autocompletado de las que dispone.

image16

6. Creación de un modelo E-R

Una de las opciones destacadas que nos ofrece esta herramienta, es la posibilidad de crear diagramas de E-R de una manera muy sencilla, para ello pulsamos File – New

image17

Seleccionamos la opción ER Diagram y pulsamos Next

image18

Introducimos el nombre del diagrama y pulsamos Finish con lo que se crea un diagrama vacío

image19

Seleccionamos la tablas film, film_actor, actor, film_category, category, inventory y las arrastramos al diagrama.

image20

De esta manera somos capaces de generar diagramas de E-R parciales de nuestro modelo.

7. Comparación de esquemas

Otra de las opciones que me ha resultado más útil para mi trabajo actual, es la posibilidad de comparar esquemas, sobre todo para poder ver las diferencias que tienen diferentes versiones de nuestro modelo, o las diferencias entre BBDD de diferentes entornos (desarrollo, test, pre, etc..)

En nuestra BBDD disponemos de 2 esquema sakila diferentes

image21

Los seleccionamos, botón derecho > Compare

image22

Pulsamos Next

image23

Y al pulsar ‘Compare’ se abrirá el navegador con el resultado de la comparación

image24

En este informe podemos ver que en el esquema sakila_v2, la tabla actor no tiene la columna last_update y en la tabla film_actor no tiene la foreign key fk_film_actor_film.

8. Referencias

http://dbeaver.jkiss.org/

9. Conclusiones

Bueno, bonito, y barato.

Se trata de una herramiento muy útil que podemos añadir a nuestra mochila de desarrolladores, que si bien es cierto, tiene algún que otro bug, el equipo que la mantiene libera muy a menudo versiones solucionandolos y añadiendo nuevas funcionalidades.

Un saludo.

Jorge

jpacheco@autentia.com

Ley de Demeter

$
0
0

La ley Demeter es una buena práctica para la programación orientada a objetos enfocada a reducir el acoplamiento entre clases.

Introducción

La ley Demeter es una buena práctica para la programación orientada a objetos enfocada a reducir el acoplamiento entre clases.

Probablemente te suene la siguiente manera de recuperar información de un objeto:

Universidad universidad;

[...]

System.out.println(universidad.getDepartamento(“Departamento de Informática”).getAsignatura(“Ingeniería del Software”).getProfesorCoordinador());

Como se puede ver, en este caso un objeto Universidad no tiene acceso de primer nivel al profesor coordinador de una asignatura. Esto implica que hay que hacer una llamada de tercer nivel a un objeto Asignatura para recuperar el profesor coordinador deseado. De esta manera, al objeto de tipo Universidad se le está dando información de la estructura del objeto de tipo Asignatura, al cual no tiene acceso directo, y además, se está confiando en cómo ese tercero proporciona la información.

¿Qué es lo que dice la Ley Demeter?

El fundamento de la Ley Demeter es el siguiente:

    Para todas las clases, C, y para todos los métodos, M, contenidos en C, todos los objetos a los cuáles M envía un mensaje deben de ser instancias de clases asociadas de la siguiente manera:
    • Las clases que definen a los argumentos de M, incluyendo a C
    • Las clases que definen a los atributos de C
    Se considera que los objetos creados por M, o por métodos a los que se llama desde M, y las variables globales son argumentos de M.

¿Cuál es el propósito de la Ley Demeter?

El principal objetivo de la Ley Demeter es reducir el acoplamiento entre clases, traduciéndose en una mejora en el mantenimiento del código. Lo hace disminuyendo el nivel de dependencia que tiene una clase con respecto a la estructura interna de otra clase que desconoce porque no es una “conocida” suya.

En el ejemplo anterior, si por alguna razón se modifica el comportamiento del método getProfesorCoordinador() o su interfaz, o incluso si se prescinde de la clase Asignatura, se hace necesario recodificar la pila de llamadas. En el ejemplo no sería costoso pero ¿que pasaría si sobre un proyecto real hay que hacer este tipo de recodificación repetidas veces, y no solo para un caso, sino para varios?

Al aplicar la Ley Demeter, pasaremos de recuperar el nombre del profesor coordinador de una asignatura de esta manera

Universidad universidad;

[...]

String profesorCoordinador = System.out.println(universidad.getDepartamento(“Departamento de Informática”).getAsignatura(“Ingeniería del Software”).getProfesorCoordinador());

a esta manera:

Universidad universidad;

[...]

System.out.println(universidad.getProfesorCoordinador(“Departamento de Informática”, “Ingeniería del Software”));

Las ventajas es que ante cualquier cambio, para obtener el profesor coordinador de una asignatura en este caso, disminuye el impacto en el mantenimiento y aumenta la adaptabilidad del código.

Veamos cómo hacerlo posible:

MainUniversidad.java

package com.autentia.tutoriales.demeterlaw;

public class MainUniversidad {

	public static void main(String[] args) {

	Asignatura asignatura;
	Departamento departamento;
	Universidad universidad;

	asignatura = new Asignatura();
	asignatura.setNombre("Ingeniería del Software");
	asignatura.setProfesorCoordinador("Autentio Pérez García");

	departamento = new Departamento();
	departamento.setNombre("Departamento de Informática");
	departamento.setAsignatura(asignatura.getNombre(), asignatura);	

	universidad = new Universidad("Universidad Autentia San Fernando");
	universidad.setDepartamento(departamento.getNombre(), departamento);

	System.out.println(universidad.getProfesorCoordinador("Departamento de Informática", "Ingeniería del Software"));

	}

}

Universidad.java

package com.autentia.tutoriales.demeterlaw;

import java.util.HashMap;
import java.util.Map;

public class Universidad {

	Map departamentos;
	String nombre;

	public Universidad(String nombre){
		this.departamentos = new HashMap();
		this.nombre = nombre;
	}


	public void setDepartamento(String nombreDepartamento, Departamento departamento){
		this.departamentos.put(nombreDepartamento, departamento);
	}

	public String getProfesorCoordinador(String nombreDepartamento, String nombreAsignatura){

		String profesorCoordinador = "";
		Departamento departamento = null;

		departamento = this.departamentos.get(nombreDepartamento);

		if(departamento != null){
		profesorCoordinador = departamento.getProfesorCoordinador(nombreAsignatura);
		}

		return profesorCoordinador;

	}

}

Departamento.java

package com.autentia.tutoriales.demeterlaw;

import java.util.HashMap;
import java.util.Map;

public class Departamento {

	Map asignaturas;
	String nombre;

	public Departamento(){
		asignaturas = new HashMap();
	}

	public String getNombre(){
		return this.nombre;
	}

	public String getProfesorCoordinador(String nombreAsignatura){

		String profesorCoordinador = "";
			profesorCoordinador = this.asignaturas.get(nombreAsignatura).getProfesorCoordinador();

		return profesorCoordinador;
	}

	public void setNombre(String nombre){
		this.nombre = nombre;
	}

	public void setAsignatura(String nombreAsignatura, Asignatura asignatura){
		this.asignaturas.put(nombreAsignatura, asignatura);
	}

}

Asignatura.java

package com.autentia.tutoriales.demeterlaw;

public class Asignatura {

	String nombre;
	String profesorCoordinador;

	public String getNombre(){
		return this.nombre;
	}

	public String getProfesorCoordinador(){
		return this.profesorCoordinador;
	}

	public void setNombre(String nombre){
		this.nombre = nombre;
	}

	public void setProfesorCoordinador(String profesorCoordinador){
		this.profesorCoordinador = profesorCoordinador;
	}

}

En la implementación se ve como si se realiza algún cambio en la clase Asignatura, la única clase que se vería afectada por ese cambio sería la clase Departamento. Los cambios a realizar si se se modifica el método getProfesorCoordinador() de la clase Asignatura estarían localizados en un único punto, que es el método getProfesorCoordinador(String asignatura).

Conclusiones

Aplicando la Ley Demeter hemos pasado de tener que modificar una pila de llamadas, realizada probablemente en varios lugares de la implementación, a tener que realizar la modificación en un único lugar, reduciendo el impacto de mantenimiento del código. La Ley Demeter tiene desventajas, como es el tiempo que hay que dedicar a realizar métodos de envoltura y la disminución del rendimiento.

De todas maneras, como se dice en el Clean Code: “talk to friends, not to strangers”.


Tests de integración con Spring Boot + Spring Security en servicios REST

$
0
0

En este tutorial vamos a ver cómo hacer Tests de integración con Spring Boot y Spring Security en servicios REST

0. Índice de contenidos

1. Introducción

Combinando herramientas como spring-boot, spring-security y spring-mvc pueden ofrecernos soluciones muy sencillas de desarrollar y que cubren muchas de las requisitos comunes de hoy día, como puede ser, aplicar seguridad a la capa de servicios REST de nuestra aplicación (aunque hoy día se está externalizando la seguridad de servicios en herramientas de apoyo, como un API Manager. Más info aquí).

Este tutorial tiene dos objetivos principales:

  • Veremos cómo implementar seguridad en servicios REST a través de configuración Spring basado en Java con Spring Security. La aplicación que contendrá dicho servicio estará desarrollada con spring-boot. Para más información sobre spring-boot, puedes consultar este tutorial
  • Desarrollaremos tests de integración con spring-boot y TestRestTemplate sobre nuestro servicio, y veremos cómo aplicar la seguridad básica en las peticiones de forma sencilla.

2. Entorno

  • Macbook pro core i7 con 16gb RAM
  • SO: Yosemite
  • IDE: IntelliJ IDEA 14
  • Gestión de dependencias: Apache Maven 3.3.3
  • Java: JDK 1.8
  • Spring: 4.1.6.RELEASE
  • Spring-boot: 1.2.5.RELEASE
  • Spring-security: 3.2.2.RELEASE

3. Aplicación spring-boot con Spring security

Lo primero que vamos a hacer es configurar nuestra aplicación con spring-boot, y luego le aplicaremos una capa de seguridad básica con autenticación usuario/contraseña. Se muestra a continuación el código y luego daremos una explicación de cada elemento. Nuestra aplicación estará configurada en una clase Application.java con el siguiente contenido:

@SpringBootApplication
@EnableAutoConfiguration
@EnableWebSecurity
@ComponentScan("com.tutoriales.springbootit")
public class Application extends WebSecurityConfigurerAdapter {

    @Resource
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("user").password("pass").roles("USER");
    }

    public static void main(String[] args) {

        SpringApplication.run(Application.class, args);
    }
}

Expliquemos el código por línea:

  • Línea 1. @SpringBootApplication: Esta anotación indica que se trata de una aplicación basada en spring-boot
  • Línea 2. @EnableAutoConfiguration: Anotación de spring-boot que incluye beans recomendados dependiendo de las dependencias que tengas en tu proyecto. Es decir, que si añades una dependencia tomcat-embedded.jar, te creará una instancia de TomcatEmbeddedServletContainerFactory, porque es posible que la uses. Es parte de la “magia” que hace spring-boot por tí
  • Línea 3. @EnableWebSecurity: Anotación de spring-security que indica a la configuración de Spring que se van a redefinir métodos de las clases WebSecurityConfigurer o de WebSecurityConfigurerAdapter (que es la que usaremos en este tutorial)
  • Línea 4. @ComponentScan(..): Anotación de spring para la búsqueda de componentes por paquetes.
  • Línea 5. Nuesta aplicación extiende de WebSecurityConfigurerAdapter para redefinir los métodos de seguridad necesarios. En este caso sólo implementaremos seguridad básica.
  • Línea 8. Se redefine este método para aplicar autenticación básica a nuestra aplicación. Servirá para todas las llamadas HTTP que se realicen sobre el contexto de la misma
  • Línea 12. Este es el main que será ejecutado desde el test de integración

Con esto ya podemos levantar una aplicación bajo un contenedor de servlet (por defecto Tomcat 8), y con seguridad básica usuario/password implementada. Las posibilidades de configuración de seguridad a nivel de aplicación, mediante el uso de WebSecurityConfigurerAdapter son muy amplias (de hecho puedes configurar la mayoría de aspectos que te proporciona spring-security).

No hemos necesitado hacer uso de spring-security 4 para realizar esta solución. No obstante, spring-security 4 nos ofrece bastantes funcionalidades nuevas muy interesantes. Os dejo el enlace a su documentación (v4.0.1.RELEASE) aquí.

4. Definición del servicio REST

Para esta solución hemos implementado un servicio REST muy sencillo, con un sólo método GET, que obtiene información del bean Persona. Dicho bean lo podemos ver a continuación:

public class Person implements Serializable {

private Long id;

private String name;

private String surname;

private String phoneNumber;

//Métodos getter/setter, equals y hashCode

}

Y el controlador Spring se muestra a continuación:

@RestController
public class PersonRestController {

    private static final Logger LOG = LoggerFactory.getLogger(PersonRestController.class);

    private List<Person> personList = new ArrayList<Person>();

    {
        personList.add(new Person(1L, "Pepe", "Ruiz", "666-555-444"));
        personList.add(new Person(2L, "Paco", "Rodríguez", "633-222-444"));
    }

    @RequestMapping(value = "/person/{id}", method = RequestMethod.GET)
    @ResponseStatus(value = HttpStatus.OK)
    public Person obtainPersonDetails(@AuthenticationPrincipal Principal principal, @PathVariable("id") long id) throws PersonException {

        LOG.info(new StringBuilder("User: ").append(principal.getName()).append(" requesting for person: ").append(id).toString());

        for(Person person : personList) {
            if(person.getId().equals(id)) {
                return person;
            }
        }

        throw new PersonException("Person has not been found!");
    }

    @ExceptionHandler(PersonException.class)
    @ResponseStatus(value = HttpStatus.NO_CONTENT)
    public void personExceptionHandler(final Exception exception) {
        LOG.warn("REST Service exception: " + exception.getMessage());
    }
}

Como podéis ver, se trata de un servicio muy sencillo que almacena una lista de dos personas, simulando información que viene de, por ejemplo, una base de datos.

En el servicio cabe destacar lo siguiente:

  • Línea 1. @RestController: Anotación que combina @Controller con @ResponseBody en los métodos del servicio.
  • Línea 15.@AuthenticationPrincipal: Anotación de spring-security que indica que el parámetro de tipo Principal se inyectará con la información del usuario que hace la petición contra el servicio
  • Línea 30. Manejador de excepción PersonException en caso de lanzar una petición con una persona que no existe

El servicio devolverá un 200(OK) si se encuentra la persona (y devolverá la misma en formato JSON en el body de la respuesta), o un 404(Not Found) si no se encuentra.

5. Test de integración

Además de un test unitario (que no muestro en el tutorial, y que os lo podéis descargar del repositorio Git (ver Configuración y código fuente)) se ha desarrollado un test de integración. Gracias a la integración con spring-boot, disponemos de una serie de clases y anotaciones de utilidad que nos servirán para desarrollar tests de integración de forma sencilla. En concreto, cuando estamos desarrollando este tipo de tests con spring-boot, se realizan las siguientes acciones:

  1. Se levanta el contenedor de servlets (por defecto Tomcat 8)
  2. Se levanta el contexto de Spring
  3. Se ejecutan los tests de la clase de Test (ojo! ¡y de todos los tests de integración del proyecto! Se comparte el contenedor que se ha levantado 😉
  4. Se le manda señal de shutdown al contenedor de servlets

Nuestro test de integración al servicio REST queda como sigue:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebIntegrationTest(randomPort = true)
public class PersonRestControllerIT {

    @Value("${local.server.port}")
    private int port;

    private RestTemplate restTemplate = new TestRestTemplate("user", "pass");

    @Test
    public void shouldReturnPersonDetails() {

        final ResponseEntity response = restTemplate.getForEntity(getBaseUrl() + "/person/{id}", Person.class, 1L);
        final Person person = response.getBody();

        assertThat(person.getId(), is(1L));
        assertThat(person.getName(), is("Pepe"));
        assertThat(person.getSurname(), is("Ruiz"));
        assertThat(person.getPhoneNumber(), is("666-555-444"));
        assertThat(response.getStatusCode(), is(HttpStatus.OK));
    }

    @Test
    public void shouldNotFindPersonDetailsWhenIdNotFound() {

        final ResponseEntity response = restTemplate.getForEntity(getBaseUrl() + "/person/{id}", Person.class, 3L);
        final Person person = response.getBody();

        assertThat(person, nullValue());
        assertThat(response.getStatusCode(), is(HttpStatus.NOT_FOUND));
    }

    private String getBaseUrl() {
        return new StringBuilder("http://localhost:").append(port).toString();
    }
}

Vamos a explicarlo línea a línea:

  • Línea 2. @SpringApplicationConfiguration(..): Seguramente nos recuerde a @ContextConfiguration(..), para tests de integración con Spring: enlace. Indica la configuración de la aplicación spring-boot; Le indicaremos la clase de la aplicación que contiene el main de spring-boot.
  • Línea 3. @WebIntegrationTest(randomPort = true): Esta es una de las partes más interesantes del test de integración. Esta anotación de spring-boot indica que se trata de un test de integración sobre una aplicación real. Implementa un bootstrapper (WebAppIntegrationTestContextBootstrapper para ser más exactos) que permite, entre otras cosas, capturar el puerto donde se ha levantado el contenedor de servlets, de forma que lo podamos caputar en el test (mediante un @Value(..), por ejemplo) y lo podamos usar para hacer las pruebas contra el servicio. A la hora de levantar el contenedor de servlets se le puede indicar si queremos que se levante en algún puerto específico, o en un puerto (libre) aleatorio.
  • Línea 6. Aquí se inyecta el valor de la propiedad local.server.port, que indica el puerto donde se ha levantado la aplicación spring-boot.
  • Línea 9. TestRestTemplate: Clase de spring-boot que extiende del clásico RestTemplate, y que implementa funcionalidad de autenticación básica contra servicios securizados. Usarlo es tan sencillo como añadir el usuario y la contraseña como parámetros del constructor, de forma que irán en las cabeceras de cada una de las peticiones que se realicen contra el servicio que consume.

Con esto tenemos un test de integración atacando al servicio que acabamos de implementar. Éste test estará dentro del ciclo de vida de Maven gracias al plugin maven-failsafe-plugin. Si ejecutamos mvn integration-test, en la consola se muestra lo siguiente:

09:34:47.875 [main] DEBUG o.s.t.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.tutoriales.springbootit.controller.PersonRestControllerIT]
09:34:47.875 [main] DEBUG o.s.t.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.tutoriales.springbootit.controller.PersonRestControllerIT]
09:34:47.876 [main] DEBUG o.s.t.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.tutoriales.springbootit.controller.PersonRestControllerIT]
09:34:47.876 [main] DEBUG o.s.t.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.tutoriales.springbootit.controller.PersonRestControllerIT]
09:34:47.878 [main] DEBUG o.s.t.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.tutoriales.springbootit.controller.PersonRestControllerIT]
09:34:47.878 [main] DEBUG o.s.t.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.tutoriales.springbootit.controller.PersonRestControllerIT]
09:34:48.062 [main] DEBUG o.s.t.c.s.DependencyInjectionTestExecutionListener - Performing dependency injection for test context [[DefaultTestContext@15b204a1 testClass = PersonRestControllerIT, testInstance = com.tutoriales.springbootit.controller.PersonRestControllerIT@77167fb7, testMethod = [null], testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@2b9627bc testClass = PersonRestControllerIT, locations = '{}', classes = '{class com.tutoriales.springbootit.app.Application}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{server.port:0, org.springframework.boot.test.IntegrationTest=true}', resourceBasePath = '', contextLoader = 'org.springframework.boot.test.SpringApplicationContextLoader', parent = [null]]]].
09:34:48.108 [main] DEBUG o.s.core.env.StandardEnvironment - Adding [systemProperties] PropertySource with lowest search precedence
09:34:48.109 [main] DEBUG o.s.core.env.StandardEnvironment - Adding [systemEnvironment] PropertySource with lowest search precedence
09:34:48.109 [main] DEBUG o.s.core.env.StandardEnvironment - Initialized StandardEnvironment with PropertySources [systemProperties,systemEnvironment]
09:34:48.110 [main] DEBUG o.s.core.env.StandardEnvironment - Adding [integrationTest] PropertySource with search precedence immediately lower than [systemEnvironment]

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.5.RELEASE)

2015-07-15 09:34:48.284  INFO 13647 --- [           main] c.t.s.controller.PersonRestControllerIT  : Starting PersonRestControllerIT on mbp-jreyes with PID 13647 (/Users/jreyes/workspacesIJ/tutoriales/spring-boot-it/target/test-classes started by jreyes in /Users/jreyes/workspacesIJ/tutoriales/spring-boot-it)
2015-07-15 09:34:48.320  INFO 13647 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@71c3b41: startup date [Wed Jul 15 09:34:48 CEST 2015]; root of context hierarchy
2015-07-15 09:34:48.653  INFO 13647 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'beanNameViewResolver': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
2015-07-15 09:34:49.105  INFO 13647 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 0 (http)
2015-07-15 09:34:49.293  INFO 13647 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2015-07-15 09:34:49.294  INFO 13647 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.0.23
2015-07-15 09:34:49.375  INFO 13647 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2015-07-15 09:34:49.375  INFO 13647 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1057 ms
2015-07-15 09:34:49.761  INFO 13647 --- [ost-startStop-1] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@108d210b, org.springframework.security.web.context.SecurityContextPersistenceFilter@1873ae24, org.springframework.security.web.header.HeaderWriterFilter@31636d95, org.springframework.security.web.csrf.CsrfFilter@66a50ea6, org.springframework.security.web.authentication.logout.LogoutFilter@4b69f81, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@3b9a440c, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@306a8956, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@5c0fa2db, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@fe8e14, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@4bfc72a, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@5010ac52, org.springframework.security.web.session.SessionManagementFilter@5d4c5ab2, org.springframework.security.web.access.ExceptionTranslationFilter@38eb00b9, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@34c78f7]
2015-07-15 09:34:49.804  INFO 13647 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2015-07-15 09:34:49.809  INFO 13647 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'characterEncodingFilter' to: [/*]
2015-07-15 09:34:49.809  INFO 13647 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2015-07-15 09:34:49.809  INFO 13647 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'springSecurityFilterChain' to: [/*]
2015-07-15 09:34:50.014  INFO 13647 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@71c3b41: startup date [Wed Jul 15 09:34:48 CEST 2015]; root of context hierarchy
2015-07-15 09:34:50.061  INFO 13647 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/person/{id}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public com.tutoriales.springbootit.vo.Person com.tutoriales.springbootit.controller.PersonRestController.obtainPersonDetails(java.security.Principal,long) throws com.tutoriales.springbootit.exception.PersonException
2015-07-15 09:34:50.062  INFO 13647 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2015-07-15 09:34:50.063  INFO 13647 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[text/html],custom=[]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest)
2015-07-15 09:34:50.084  INFO 13647 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-07-15 09:34:50.084  INFO 13647 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-07-15 09:34:50.116  INFO 13647 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-07-15 09:34:50.221  INFO 13647 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 51372 (http)
2015-07-15 09:34:50.223  INFO 13647 --- [           main] c.t.s.controller.PersonRestControllerIT  : Started PersonRestControllerIT in 2.109 seconds (JVM running for 2.719)
2015-07-15 09:34:50.353  INFO 13647 --- [o-auto-1-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2015-07-15 09:34:50.353  INFO 13647 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2015-07-15 09:34:50.366  INFO 13647 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 13 ms
2015-07-15 09:34:50.411  INFO 13647 --- [o-auto-1-exec-1] c.t.s.controller.PersonRestController    : User: user requesting for person: 1
2015-07-15 09:34:50.472  INFO 13647 --- [o-auto-1-exec-2] c.t.s.controller.PersonRestController    : User: user requesting for person: 3
2015-07-15 09:34:50.475  WARN 13647 --- [o-auto-1-exec-2] c.t.s.controller.PersonRestController    : REST Service exception: Person has not been found!
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.792 sec
2015-07-15 09:34:50.481  INFO 13647 --- [       Thread-1] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@71c3b41: startup date [Wed Jul 15 09:34:48 CEST 2015]; root of context hierarchy

6. Configuración y código fuente

El fichero de configuración de Maven tendrá esta estructura:

<project xmlns="http://maven.apache.org/POM/4.0.0"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

    <groupId>com.autentia.tutoriales</groupId>
    <artifactId>spring-boot-IT</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.5.RELEASE</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <spring.version>4.1.6.RELEASE</spring.version>
        <spring.security.version>3.2.2.RELEASE</spring.security.version>
    </properties>

    <!-- BUILD -->

    <build>
        <plugins>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <!-- DEPENDENCIES -->

    <dependencies>

        <!-- SPRING -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${spring.security.version}</version>
        </dependency>
        <!-- Test -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.10.19</version>
            <scope>test</scope>
        </dependency>

        <!-- Other dependencies -->
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${spring.security.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${spring.security.version}</version>
        </dependency>


    </dependencies>


    <!-- REPOSITORIES -->

    <repositories>
        <repository>
            <id>spring-releases</id>
            <url>https://repo.spring.io/libs-release</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-releases</id>
            <url>https://repo.spring.io/libs-release</url>
        </pluginRepository>
    </pluginRepositories>

</project>

El código del proyecto lo podéis encontrar aquí

7. Conclusiones

Con el desarrollo que se expone en este tutorial se pueden realizar soluciones de integración de componentes independientes dentro de una aplicación empresarial, como puede ser la definición de una capa de servicios REST. Las posibilidades que nos ofrece spring-boot para la definición de este tipo de componentes nos ahorra mucho tiempo de desarrollo, y las clases de utilidad que nos ofrece este módulo de Spring nos permiten encapsular funcionalidad que de otra forma nos llevaría a escribir muchas líneas de código. Además, nos permiten definir tests independientes del entorno, independientes del servidor de aplicaciones donde esté corriendo la aplicación (ya que levanta uno propio embebido), o configuración según entorno.

Una consideración que hay que tener en cuenta es que estos tests no soportan transaccionalidad (mediante @Transactional), aunque sí si le inyectamos directamente el transactionManager al test.

Por último, existe un módulo específico de testing de spring-boot: spring-boot-starter-test. Dicho módulo provee de algunas herramientas útiles para testing; por ejemplo, te incluye las librerías mockito, hamcrest, spring-test, junit, y algunas clases de apoyo para testing. En este tutorial se ha optado por incluir las librerías de forma independiente, pero esta es otra opción igual de válida. Échale un vistazo al apartado de referencias para saber más de este módulo de spring-boot.

8. Referencias

  • Spring security project: enlace
  • Enable web security in Spring projects: enlace
  • Spring boot test: enlace
  • Spring MVC Rest services: enlace

Notificaciones vía web sockets con Spring Messaging en una aplicación AngularJS

$
0
0

Existen muchas técnicas para mantener una conexión bidireccional entre cliente y servidor, en este tutorial vamos a ver como hacerlo con el soporte de Spring en una aplicación AngularJS

0. Índice de contenidos.


1. Introducción

Ya hemos visto como podemos implementar una conexión bidireccional entre cliente y servidor estableciendo un canal de comunicación en tiempo real con el soporte de Web Sockets y:

En esta entrada haremos uso de la librería spring-websocket desarrollada bajo el proyecto spring-messaging que es una abstracción sobre el soporte nativo de web sockets del servidor sobre el cuál arranca nuestra aplicación, de modo tal que podemos seguir desplegando sobre un Apache Tomcat pero en vez de configurar y registrar de forma nativa la conectividad lo haremos con el soporte de Spring beneficiándonos de su configurabilidad y el soporte de inyección de dependencias en nuestros servicios.

Los web sockets son una capa ligera de comunicación sobre TCP que permite el uso de otros protocolos a bajo nivel para encapsular los mensajes; en este tutorial usaremos STOMP desde una aplicación AngularJS para conectarnos/desconectarnos al servidor y enviar y recibir mensajes.

2. Entorno.

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.3 GHz Intel Core i7, 16GB DDR3).
  • Sistema Operativo: Mac OS Mavericks 10.9.4
  • Spring 4.1.6.RELEASE
  • Apache Tomcat 8.0.20

3. Configuración.

Como siempre, asumiendo que nuestro proyecto tiene el soporte de maven, lo primero es configurar las dependencias en el pom.xml:

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-webmvc</artifactId>
	<version>4.1.6.RELEASE</version>
</dependency>

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-web</artifactId>
	<version>4.1.6.RELEASE</version>
</dependency>

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-messaging</artifactId>
	<version>4.1.6.RELEASE</version>
</dependency>

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-websocket</artifactId>
	<version>4.1.6.RELEASE</version>
</dependency>

A continuación, podríamos configurar por anotaciones el soporte de web sockets, pero soy un viejuno y prefiero la configuración vía xml:

<beans 	xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns:context="http://www.springframework.org/schema/context"
		xmlns:p="http://www.springframework.org/schema/p"
     	xmlns:mvc="http://www.springframework.org/schema/mvc"
	    xmlns:websocket="http://www.springframework.org/schema/websocket"
     	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 
     	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd 
     	http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd ">

	<websocket:message-broker application-destination-prefix="/app">

		<websocket:stomp-endpoint path="/chat" >
		            <websocket:sockjs/>
		</websocket:stomp-endpoint>

		<websocket:simple-broker prefix="/topic" />

	</websocket:message-broker>
	
</beans>

Con lo anterior estamos habilitando:

  • websocket:message-broker: el soporte de mensajería generíco de spring-messaging para el prefijo de “app”; para remitir un mensaje desde el cliente a un endPoint “chat”, se hará a través de “/app/chat”
  • websocket:stomp-endpoint: registra un endPoint “chat” habilitando SockJS
  • websocket:simple-broker: un sencillo broker de mensajería basado en memoria que registra un “topic”, con ese prefijo, para el transporte de los mensajes a los clientes.

SockJS es una librería Javascript para navegadores que permite el uso de WebSockets orientado a objetos. SockJS proporciona un api, cross-browser, cross-domain que permite una comunicación entre el navegador y el servidor de modo tal que si el navegador soporta WebSockets hace uso de la implementación del mismo y, sino lo soporta, lo emula de forma nativa.

Por último, en el descriptor de despliegue web.xml tendríamos que declarar el front controller de spring MVC con una configuración similar a la siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">


	<servlet>
		<servlet-name>rest</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	
	<servlet-mapping>
		<servlet-name>rest</servlet-name>
		<url-pattern>/rest/*</url-pattern>
	</servlet-mapping>
      

	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
	</welcome-file-list>


</web-app>

4. Creación de un manejador de mensajes.

Con el soporte de spring mvc podemos registrar un controlador de la siguiente forma

@Controller
public class MessagesController {
		@MessageMapping("/sendMessage")
		@SendTo("/topic/messages")
		public Message receive(Message message) throws Exception {
			Thread.sleep(3000); // simulated delay
			return message;
		}
}

Message es un POJO que encapsula como propiedades el texto del mensaje .

public class Message {

	private String text;
	
    public Message() {
    }

    public Message(String text) {
	   this.text = text;
    }

    public String getText() {
        return text;
    }
    
}

El método que mapea la URL “sendMessage”, recibe un mensaje y remite el mismo a la cola de mensajería “topic/messages” a la que se pueden suscribir los clientes al conectarse al socket.

Con todo lo anterior tendríamos la siguiente configuración:

  • “topic/messages” para que los clientes se puedan suscribir y recibir los mensajes de saludo del servidor,
  • “app/chat” para que los clientes puedan enviar sus mensajes
  • el prefijo de los recursos anteriores será el contexto de la aplicación + la url-pattern de la declaración del servlet.

5. Soporte de STOMP en AngularJS.

En este punto tenemos dos opciones:

  • buscar un módulo mantenido por un tercero que nos de soporte de STOMP en el cliente, como muchas otras librerías que usamos,
  • implementar nuestro propio servicio de conexión, suscripción y envio de mensajes.

Vamos a optar por la segunda opción puesto que en pruebas de concepto anteriores hemos podido comprobar que el soporte del modulo “angular-stomp” prevé la conectividad por ws:/ pero no soporta la conexión por http:// y además queremos tener más control y así comprobamos como crear nuestro propio servicio en AngularJS.

Lo primero es añadir las dependencias de las librerías que nos van a proporcionar el soporte de web sockets y el protocolo de comunicaciones; si para la parte servidora/java declarábamos las dependencias en el pom.xml de maven, nuestro proyecto de cliente/javascript está gestionado por bower con lo que debemos añadir las siguientes dependencias a nuestro bower.json

"dependencies": {
	"sockjs": "0.3.4",
	"stomp-websocket": "2.3.4",
}

Con ello ya podemos hacer uso de la librería de socksJS para implementar nuestro servicio dentro de un módulo de prueba “pushApp”:

angular
    .module('pushApp', []).
    factory('stompService',['$rootScope', function($rootScope) {
    	
    	var client = {};
    	
        var service = function(url){
        	
            var socket = new SockJS(url);
            client = Stomp.over(socket);
            
            return {
            	subscribe: subscribe,
            	send: send,
            	connect: connect,
            	disconnect: disconnect
            }
    	};
        
		return service;
		
		function subscribe(queue, callback) {
            client.subscribe(queue, function() {
            	var args = arguments;
            	$rootScope.$apply(function() {
                    callback(args[0]);
                })
            })
		}
		
		function send(queue, headers, data){
			client.send(queue, headers, data);
		}

		function connect(user, password, on_connect, on_error, vhost) {
            client.connect(user, password,
            function(frame) {
             $rootScope.$apply(function() {
                 on_connect.apply(client, frame);
             })
            },
            function(frame) {
                $rootScope.$apply(function() {
                    on_error.apply(client, frame);
                })
            }, vhost);
			
		}

		function disconnect(callback){
            client.disconnect(function() {
                $rootScope.$apply(function() {
                    callback.apply(args);
                })
            })
		}
    }]
  );

En esta implementación inicial nos hemos basado en la librería que comentabamos pero modificando el prototipado y el modo en el que se crea el cliente para permitir la conexión vía http. Al no basarse en promesas propaga los cambios al ámbito $rootScope con la función $apply.

Para hacer uso de la librería bastaría con inyectarla en un controlador:

angular.
    module('pushApp').
    controller('stompCtrl', ['$scope', 'stompService', stompCtrl]);
    
    function stompCtrl($scope, stompService){
      $scope.messages = [];
      $scope.text = "";
      $scope.chat = stompService('/tnt-labs/rest/chat');

      $scope.chat.connect("guest", "guest", function(){
          $scope.chat.subscribe("/topic/messages", function(message) {
              var body = JSON.parse(message.body)
              $scope.messages.push(body.text);
          });
      });
      
      $scope.sendName = function(){
          $scope.client.send("/app/sendMessage", {}, JSON.stringify({ 'text': $scope.text }));
      }
    }

Y desde el html podríamos tener un código como el siguiente para enviar mensajes y recibirlos, tanto los nuestros como los de otros clientes.

<div ng-app="pushApp" >
	<div ng-controller="stompCtrl">
	    <div >
    	    <h1>Messages</h1>
        	<p ng-repeat="message in messages">{{message}}</p>
    	</div>
    	<div ng-disabled="$scope.client != undefined">
	        <label>Mensaje</label><input type="text" ng-model="text"/>
	        <button id="sendName" ng-click="sendName();">Send</button>
    	</div>
	</div>
</div>

A nivel de interfaz deberíamos ver algo como lo siguiente:


6. Referencias.


7. Conclusiones.

Hemos visto una aproximación adicional a las que ya proponiamos con web sockets haciendo uso además de un servicio en Angular.

Vale que no aún no tenemos un chat al uso, solo enviamos y recibimos mensajes a todos los usuarios conectados, pero no se puede tener todo en una primera aproximación.

Stay tuned!

Un saludo.

Jose

Docker for dummies

$
0
0

Vamos a comenzar una serie de tutoriales dedicados a una de las tecnologías que más se han puesto de moda en el último año : ‘Docker’.

0. Índice de contenidos


1. Introducción

Vamos a comenzar una serie de tutoriales dedicados a una de las tecnologías que más se han puesto de moda en el último año : ‘Docker’.

En este primero de la serie, vamos a describir que es Docker, su instalación, primeros pasos, y en tutoriales sucesivos describiremos una serie de casos de uso en los que docker nos ha facilitado la vida.


2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: MacBook Pro 15' (2.3 GHz Intel Core i7, 16GB DDR3 SDRAM)
  • Sistema Operativo: Mac OS X Mavericks 10.9.5.
  • Software: Docker 1.7

3. Que es Docker??

Docker es una herramienta open-source que nos permite realizar una ‘virtualización ligera’, con la que poder empaquetar entornos y aplicaciones que posteriormente podremos desplegar en cualquier sistema que disponga de esta tecnología.

Para ello Docker extiende LXC (LinuX Containers), que es un sistema de virtualización que permite crear múltiples sistemas totalmente aislados entre sí, sobre la misma máquina o sistema anfitrión.


4. Docker vs Máquinas Virtuales

La gran diferencia es que una máquina virtual necesita contener todo el sistema operativo mientras que un contenedor Docker aprovecha el sistema operativo sobre el cual se ejecuta, comparte el kernel del sistema operativo anfitrión e incluso parte de sus bibliotecas.

En la siguiente imagen podemos ver esta diferencia entre el enfoque de las máquinas virtuales y el utilizado por Docker:

Diagram source: Docker Inc.

Respecto al almacenamiento en disco, una máquina virtual puede ocupar varios gigas ya que tiene que contener el sistema operativo completo, sin embargo los contenedores Docker sólo contienen aquello que las diferencia del sistema operativo en las que se ejecutan, por ejemplo un Ubuntu con Apache ocuparía unos 180 Mb.

Desde el punto de vista del consumo de procesador y de memoria RAM, los contenedores Docker hacen un uso mucho más eficiente del sistema anfitrión, pues comparten con él, el núcleo del sistema operativo y parte de sus bibliotecas, con lo que únicamente usarán la memoria y la capacidad de cómputo que estrictamente necesiten.

Otras ventajas destacables del uso de Docker serían:

  • Las instancias se arrancan en pocos segundos.
  • Es fácil de automatizar e implantar en entornos de integración continua.
  • Existen multitud de imágenes que pueden descargarse y modificarse libremente.

Como inconvenientes podemos destacar:

  • Sólo puede usarse de forma nativa en entornos Unix aunque se puede virtualizar gracias a boot2docker tanto en OSX como en Windows.
  • Las imágenes sólo pueden estar basadas en versiones de Linux modernas (kernel 3.8 mínimo).
  • Como es relativamente nuevo, puede haber errores de código entre versiones.

5. Imágenes y Contenedores

Los términos que hay que manejar con Docker son principalmente 2, las imágenes y contenedores.

Las imágenes en Docker se podrían ver como un componente estático, pues no son más que un sistema operativo base, con un conjunto de aplicaciones empaquetadas, mientras que un contenedor es la instanciación o ejecución de una imagen, pudiendo ejecutar varios contenedores a partir de una misma imagen.

Haciendo una analogía con la POO una imagen es una clase y un contenedor es la instanciación de una clase, es decir un objeto.


6. Instalación

El motor de Docker utiliza las características específicas del kernel de Linux, asi que, si no disponemos de alguna distribución de Linux, tendremos que utilizar Boot2docker que es una mini-máquina-virtual de Linux para poder utilizar esta tecnología en Mac o Windows.

Como prerrequisito a la instalación de Boo2Docker necesitaremos tener instalado VirtualBox, una vez resolvamos este aspecto, nos descargamos boot2docker desde su web

Nos descargamos el archivo Boot2Docker-1.7.1.pkg y lo ejecutamos

Pulsamos 'Continuar'

Pulsamos 'Instalar'

Una vez finalizada la instalación ejecutamos por consola:

boot2docker init

Este comando nos creará la máquina virtual boot2docker que nos permitirá usar Docker desde Mac, posteriormente la arrancamos:

boot2docker start

Deberemos exportar las variables de entorno que nos indican para poder usar docker, este paso lo deberemos realizar cada vez que arranquemos boot2docker, para evitarlo podemos añadir esta variables de entorno a nuestro .profile

Para verificar que docker se ha instalado correctamente ejecutamos desde la terminal:

docker version


7. Primeros pasos

Una vez tengamos instalado correctamente docker vamos a empezar a trastear con él, para ello vamos a Docker Hub, que es un repositorio de imágenes pre-configuradas listas para usar (en otras palabras un github de imágenes).

Docker Hub dispone de las imágenes oficiales de postgresql, redis, mysql, ubuntu, rabbitmq, sonarqube, mongodb … además de una multitud de imágenes que los usuarios van creando y subiendo al repositorio.

En este ejemplo vamos a usar la imagen oficial de sonarqube , para crear un contenedor con Sonar instalado

En el repositorio de sonarqube encontraremos información relativa a la imagen y de cómo se usa, más o menos lo que vamos a describir a continuación.

El primer paso sería descargarnos la imagen

docker pull sonarqube

Podemos listar las imágenes que tenemos en nuestro equipo

docker images

Una vez tenemos la imagen, ya estamos en disposición de instanciar un contenedor a partir de ella.

docker run -d --name sonarqube -p 9000:9000 sonarqube

Este comando lo que realiza es levantar un contenedor con los siguientes parámetros:

  • -d : Levanta el contenedor en segundo plano
  • –name : Nombre asociado al contenedor
  • -p: Mapeamos el puerto 9000 de nuestro equipo con el 9000 del contenedor

Podemos comprobar los contenedores que están levantados

docker ps

Para acceder al sonar instalado en el contenedor nos bastaría con abrir un al navegador con la URL http:/localhost:9000, pero al usar un Mac (o Windows) y necesitar de un software intermedio, boot2docker, necesitaremos conocer la ip de la máquina virtual que crea boot2docker

boot2docker ip

Ahora ya podemos acceder a nuestro Sonar

Fácil no ?? Y si necesitamos tener otro Sonar ?? Pues nada, instanciamos de nuevo la imagen para crear otro contenedor

docker run -d --name sonarqube2 -p 7000:9000 sonarqube

Sólo necesitamos cambiar el nombre y el mapeo de puertos, volvemos a comprobar los contenedores activos … y ahí tenemos nuestros dos contenedores

Podemos acceder al nuevo sonar

Podemos parar un contenedor:

docker stop sonarqube

Vemos como sólo quedaría activo sonarqube2, podemos listar los contenedores independientemente de su estado con:

docker ps -a

Para borrar un contenedor deberíamos ejecutar el siguiente comando

docker rm sonarqube

Hay que tener en cuenta que al borrar un contenedor perderíamos los cambios que hubiésemos realizado en él. Una de las opciones que tendríamos si queremos que los cambios que realicemos al contenedor sean permanentes, sería la de generar una imagen a partir del contenedor, para ello haríamos lo siguiente:

docker commit -m "<comentario>" -a "<autor>" <id_contenedor> <nombre_imagen>:<etiqueta_imagen>

Con este comando lo que conseguimos es generar una imagen jpacheco/sonar construida a partir del contenedor sonarqube2, si volvemos a listar las imágenes:


8. Dockerfile

En este último apartado vamos a ver como crear una nueva imagen a través de un fichero llamado Dockerfile.

Un fichero Dockerfile es simplemente un fichero de texto que nos permite definir las instrucciones a seguir por Docker para construir una imagen, en otras palabras es como una receta para crear nuestras imágenes, que servirán de forma posterior para correr nuestros contenedores.

En este ejemplo vamos a crear una imagen con un SO Ubuntu y le vamos a instalar el servidor web Apache, para ello contamos con un Dockerfile:

FROM ubuntu
MAINTAINER Jorge Pacheco
ENV http_proxy http://user:pass@proxy/
ENV https_proxy http://user:pass@proxy/
RUN apt-get update
RUN apt-get install apache2 -y
RUN echo "<h1>Apache with Docker</h1>" > /var/www/html/index.html
EXPOSE 80
ENTRYPOINT apache2ctl -D FOREGROUND

Vamos a describir los comandos del Dockerfile:

FROM : Indica la imagen que tomamos como base, en este caso la imagen oficial de ubuntu

MAINTAINER: Especifica el autor de la imagen.

ENV: Definimos una variables de entorno en la imagen base.

  • http_proxy http://user:pass@proxy/ — Definimos la variable http_proxy
  • https_proxy http://user:pass@proxy/ — Definimos la variable https_proxy

RUN: Ejecuta una sentencia sobre la imagen base

  • apt-get update : actualiza los repositorios de ubuntu
  • apt-get install apache2 -y : Instala el apache
  • echo “<h1>Apache with Docker</h1>” > /var/www/html/index.html : crea un fichero index.html

EXPOSE: Exponemos el puerto 80 del contenedor para que pueda ser mapeado por la máquina anfitrión.

ENTRYPOINT: Indicamos que se ejecute apache2ctl -D FOREGROUND cada vez que arranquemos el contenedor.

Una vez tengamos definido el fichero Dockerfile, vamos a construir la imagen:

docker build -t jpacheco/apache .

Le estamos indicando a docker que construya una imagen con el nombre jpacheco/apache a partir del DockerFile que se encuentra en la misma ruta donde ejecutamos el comando

Listamos las imágenes disponibles:

Ya estamos en disposición de arrancar un contenedor a partir de la imagen que hemos creado:

docker run --name apache1 -d -p 90:80 jpacheco/apache

Arrancamos un contenedor, que llamaremos apache1, mapeando el puerto 80 del contenedor con el 90 de nuestra máquina, a partir de la imagen jpacheco/apache. Para comprobar que todo ha ido bien abrimos un navegador con la url http://192.168.59.103:90/


9. Conclusiones

Apenas hemos visto la potencia que nos brinda Docker, en posteriores tutoriales iremos viendo casos de uso y adentrándonos en la multitud de posibilidades que nos ofrece: simulación de entornos productivos, desarrollo de microservicios, empaquetamiento de infraestructuras, etc..

Aviso: Crea adicción y acabaremos intentando 'dockerizar' todo … 😉

Un saludo.


10. Referencias

Soporte de Web Sockets en Apache Web Server (httpd) sobre un contenedor Docker

$
0
0

En este tutorial vamos a probar el soporte de web sockets en un Apache Web Server “dockerizado”.

0. Índice de contenidos.


1. Introducción

En tutoriales anteriores hemos visto como podemos implementar una conexión bidireccional entre cliente y servidor estableciendo un canal de comunicación en tiempo real con el soporte de Web Sockets y:

En todos esos ejemplos el servidor estaba directamente expuesto al cliente pero ¿y si el servidor está fuera de la DMZ y tenemos un proxy inverso delante?, y ¿si necesitamos montar nuestra aplicación en cluster activo/activo y queremos dar soporte de balanceo a las peticiones vía Web Sockets?

En este tutorial vamos a configurar httpd (Apache Web Server) para dar soporte y hacer pruebas de redirección de peticiones ws (Web Sockets) con balanceo de carga, inicialmente en un entorno no productivo.

Remarco que las pruebas las vamos a ejecutar en un entorno no productivo porque además vamos a hacerlas en un contenedor docker de httpd, que nos va a servir “para cacharrear” sin tener ninguna intención inicial de guardar o externalizar esa configuración para su uso en otros entornos, aunque como hemos visto recientemente en el tutorial de introducción a docker no sería muy complejo hacerlo.

2. Entorno.

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.3 GHz Intel Core i7, 16GB DDR3).
  • Sistema Operativo: Mac OS Mavericks 10.9.4
  • Boot2docker 1.6.2
  • Apache Web Server httpd 2.4.12
  • Apache Tomcat 8.0.20

3. Httpd con Docker.

Asumiendo que tenemos docker o boot2docker instalado, en función del SO en el que estemos trabajando, la instalación de la imagen oficial de httpd pasa por la ejecución del siguiente comando:

docker pull httpd

Con ello nos bajaremos la última versión publicada de la imagen que, a día de hoy, incorpora un httpd 2.4.12.

Podemos comprobar la descarga de la imagen ejecutando el comando de listado de imágenes:

bash-3.2$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
httpd               latest              de94ed779434        4 weeks ago         161.8 MB

El siguiente paso es arrancar un contenedor basado en esa imagen, proporcionando un nombre descriptivo y, como es posible que tengamos un apache en local corriendo, mapeando el puerto local 9091 contra el puerto 80 del contenedor

bash-3.2$ docker run -d -p 9091:80  --name httpd-ws httpd
12820527d2e7dc758ab6b00638ceb18d29ff166ef6aa60a6709f9bc216f618a9

Usando boot2docker tenemos que lanzar el siguiente comando para comprobar la ip en la que se ha levantado el contenedor.

MacBook-Pro-de-jmsanchez:~ jmsanchez$ boot2docker ip
192.168.59.103

Ahora podemos acceder a través del navegador para comprobar que se encuentra levantado:

httpd-websockets-docker-00

Con el siguiente comando podemos listar los contenedores creados basados en las imágenes y comprobar el estado de los mismos

bash-3.2$ docker ps -a
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS                  PORTS                  NAMES
12820527d2e7        httpd:latest        "httpd-foreground"   2 days ago          Up 53 minutes           0.0.0.0:9091->80/tcp   httpd-ws            
0f792a9c397a        httpd:latest        "httpd-foreground"   10 days ago         Exited (0) 7 days ago                          loving_swartz

Parece que lo tenemos listo para empezar “a cacharrear” pero en este punto, tenemos un primer problema, la imagen oficial de Apache Web Server al arrancar levanta el proceso httpd, si accedemos al contenedor y paramos el proceso httpd también se para el contenedor. La recomendación en este punto es construir nuestra propia imagen basada en la de httpd proporcionando una configuración externa, bien basándonos en un Dockerfile o mapeando un directorio local contra el directorio de configuración de httpd en el contenedor; pero no vamos a invertir inicialmente tiempo en esto, nuestro objetivo es disponer de un apache 2.4 para hacer pruebas de configuración no preparar un entorno de producción.

A continuación paramos el contenedor:

bash-3.2$ docker stop 12820527d2e7
12820527d2e7

Y lo borramos:

bash-3.2$ docker rm 12820527d2e7
12820527d2e7

Al comando inicial con el que creamos el contenedor basándonos en la imagen vamos a añadirle los siguientes parámetros

  • -it: para acceder por consola a la shell del contenedor,
  • -p: por cambiar el puerto inicial que proponíamos al 81,
  • el comando bash al final para que no se levante el proceso establecido por defecto para la imagen, sino que simplemente permita el acceso a la consola,
docker run -it --name httpd-ws -p 81:80 httpd bash

Una vez ejecutado, podemos lenvantar manualmente el Apache Web Server:

root@7329fc1203d5:/usr/local/apache2/bin# httpd -k start

Si, por cualquier motivo paramos el contenedor siempre podemos volver a levantarlo manualmente

bash-3.2$ docker start httpd-ws

Y podemos “engancharnos” a la consola de bash del contendor con el siguiente comando (dos veces INTRO):

bash-3.2$ docker attach httpd-ws

Ya podemos parar y arrancar tanto el contendor como el httpd que nos proporciona internamente el mismo, si bien, nos encontramos con un segundo problema, no tenemos un editor que nos permita modificar fichero alguno desde el contenedor; las imágenes están pensadas para consumir pocos recursos y ocupar poco espacio con lo que tienen lo mínimo imprescindible para que después las amplies; ya digo que lo normal es externalizar la configuración de los procesos, en este caso httpd, en ficheros externos al contenedor para que levantar uno o varios dockers por entorno sea homogéneo, pero para esta prueba no vamos a hacer eso, vamos a instalar un editor en el contenedor.

La imagen se ha generado en base una debian con lo que vamos a ejecutar los siguientes comandos:

apt-get update
...

apt-get install vim
...

Hay que tener en cuenta que todo lo que hemos hecho se perderá si borramos el contenedor y, a partir de aquí, podemos ejecutar el siguiente comando:

root@7329fc1203d5:/usr/local/apache2/htdocs# vi index.html

para hacer una prueba rápida de edición de la página de indice de Apache.

<html><body><h1>It works! inside Docker!!!</h1></body></html>

Si levantamos el proceso httpd:

root@7329fc1203d5:/usr/local/apache2/bin# httpd -k start

y accedemos a través del navegador al puerto configurado veremos los cambios

httpd-websockets-docker-01

Aparte de cacharrear en este punto ya hemos aprendido o consoliado conceptos sobre docker.


4. Web sockets httpd proxy.

Vamos a configurar el soporte de web sockets en Apache Web Server y lo primero que debemos hacer es habilitar los módulos correspondientes en la configuración de httpd para que los soporte. Accediendo al fichero de configuración

root@7329fc1203d5:/usr/local/apache2# vi conf/httpd.conf

Habilitaremos los siguientes módulos:

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_connect_module modules/mod_proxy_connect.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule proxy_express_module modules/mod_proxy_express.so
LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so

Y, para probar, en cualquier punto del mismo fichero, puede ser después de la declaración del primer <Directory /> podemos añadir la siguiente configuración

ProxyPass        /tnt-labs/rest/notifications/info  http://172.20.10.3:8080/tnt-labs/rest/notifications/info
ProxyPassReverse /tnt-labs/rest/notifications/info  http://172.20.10.3:8080/tnt-labs/rest/notifications/info

ProxyPass        /tnt-labs/rest/notifications  ws://172.20.10.3:8080/tnt-labs/rest/notifications
ProxyPassReverse /tnt-labs/rest/notifications  ws://172.20.10.3:8080/tnt-labs/rest/notifications

ProxyPass        /tnt-labs http://172.20.10.3:8080/tnt-labs
ProxyPassReverse /tnt-labs http://172.20.10.3:8080/tnt-labs

La IP 172.20.10.3 es la de mi máquina local que está visible para el contenedor.

El orden en el que declaramos las URLs de mapeo es importante puesto que debemos declararlas de mayor a menor granularidad y, por regla general las que mapean el protocolo ws serán más granulares y, como consecuencia, las primeras en declararse.

Las pruebas las estamos haciendo con el proyecto montado con el soporte de spring-messaging, que expone un primer servicio /notifications/info por http que devuelve la información del Web Socket, como la URL es más granular debe declararse el primero.

Si ahora reiniciamos el proceso, podríamos comprobar que ya no es necesario acceder a la aplicación por el puerto del Apache Tomcat, puesto que está correctamente mapeado por el Apache Web Server.

root@7329fc1203d5:/usr/local/apache2# bin/httpd -k restart

Si no funcionase correctamente, una manera de comprobar que los módulos que están cargados es ejecutando el siguiente comando:

root@7329fc1203d5:/usr/local/apache2# bin/apachectl -t -D DUMP_MODULES | grep proxy
 proxy_module (shared)
 proxy_connect_module (shared)
 proxy_http_module (shared)
 proxy_wstunnel_module (shared)
 proxy_ajp_module (shared)
 proxy_balancer_module (shared)
 proxy_express_module (shared)

5. Web sockets en cluster con htttp.

Ahora vamos a suponer que tenemos la aplicación levantada en dos tomcats, en la misma máquina con distintos puertos.

Lo primero que vamos a hacer es habilitar el monitor del balanceador, podriamos no hacerlo pero buena gana de ponernos una venda en los ojos

<Location /balancer-manager>
SetHandler balancer-manager

Order Deny,Allow
Allow from all
</Location>

ProxyRequests Off
ProxyPass /balancer-manager !

A continuación vamos a configurar tres balanceadores con sus correspondientes URLs de mapeo que coinciden tanto en orden como en contenido con lo configurado anteriormente, solo que ahora redirigen a los balanceadores:

<Proxy balancer://tnt-cluster-rest-info>
BalancerMember http://172.20.10.3:8080/tnt-labs/rest/notifications/info
BalancerMember http://172.20.10.3:8081/tnt-labs/rest/notifications/info
</Proxy>

<Proxy balancer://tnt-cluster-http>
BalancerMember http://172.20.10.3:8080/tnt-labs
BalancerMember http://172.20.10.3:8081/tnt-labs
</Proxy>

<Proxy balancer://tnt-cluster-ws>
BalancerMember ws://172.20.10.3:8080/tnt-labs/rest/notifications
BalancerMember ws://172.20.10.3:8081/tnt-labs/rest/notifications
</Proxy>


ProxyPass        /tnt-labs/rest/notifications/info  balancer://tnt-cluster-rest-info
ProxyPassReverse /tnt-labs/rest/notifications/info  balancer://tnt-cluster-rest-info

ProxyPass        /tnt-labs/rest/notifications  balancer://tnt-cluster-ws
ProxyPassReverse /tnt-labs/rest/notifications  balancer://tnt-cluster-ws

ProxyPass        /tnt-labs balancer://tnt-cluster-http
ProxyPassReverse /tnt-labs balancer://tnt-cluster-http

Si realizamos varias peticiones desde distintos clientes podemos comprobar, accediendo al monitor del balanceador en la URL http://192.168.59.103:81/balancer-manager, como las peticiones se reparten entre los distintos tomcats.

httpd-websockets-docker-02


6. Referencias.


7. Conclusiones.

Debemos tener en cuenta que, con lo visto, un cliente a través del Apache Web Server está conectado a un Web Socket de un Apache Tomcat, la carga está repartida entre los distintos servidores pero simplemente con la configuración anterior las notificaciones desde los servidores a los clientes tendrían que ejecutarse desde todos los nodos del balanceador, no hemos configurado una replica de la sesión de web sockets entre los distintos nodos del cluster. ¿Será el contenido de otra entrada en el blog?

Sin necesidad de mantener una réplica de sessiones y disponiendo un repositorio de información común, como podría ser:

  • una base de datos, cada nodo podría “monitorizar” una tabla de notificaciones para remitirlas a todos sus clientes conectados, o
  • una cola de mensajería como se plantea en esta entrada de activemq, de tal modo que nodo podría suscribirse a un topic de notificaciones y despachar el mensaje recibido de la cola, a sus clientes conectados.

Un saludo.

Jose

Filtros Personalizados en AngularJS

$
0
0

En este tutorial vamos a aprender a ir más allá de los filtros básicos que nos ofrece el framework AngularJS y vamos a crear nuestro propio filtro. Este se puede considerar una segunda parte del que realicé hace varios meses sobre una introducción básica a los filtros de AngularJS.

Para crear un filtro en AngularJS, al igual que creamos controladores poniendo el nombre del módulo seguido de controller. ahora pondremos el nombre del módulo seguido de filter y el nombre del filtro de esta forma.

app.filter(‘nombreDelFiltro’, function(){});

Vamos a comenzar con un filtro que nos filtre una lista de países por un continente concreto.

Teniendo una lista de países de esta forma:

$scope.paises = [
        {nombre: 'Francia', continente : 'Europa'},
        {nombre: 'China', continente : 'Asia'},
        {nombre: 'Canada', continente : 'America'},
        {nombre: 'Marruecos', continente: 'Africa'},
        {nombre: 'Suecia', continente: 'Europa'},
        {nombre: 'Chile', continente: 'America'}
    ];

Podemos crear el filtro que deseamos de la siguiente forma:

app.filter(‘filtroPais’, function() {
        return function(input) {
            var salida = [];
            angular.forEach(input, function(pais) {
                if (pais.continente === 'Europa') {
                salida.push(pais)
                }
            })
            return salida;
        }
    });

Algunos detalles importantes que se pueden observar en el filtro son:

  • El nombre del módulo es app
  • El nombre del filtro es continente
  • El filtro tiene un parámetro obligatorio de entrada llamado input
El funcionamiento es sencillo. Los elementos de la lista que cumplan la propiedad pais.continente === ‘Europa’ se añadirán a una lista que se devolverá.

En nuestra web, es sencilla su utilización.

  • {{pais.name}}

Con esto podríamos observar Francia y Suecia.

Vamos a mejorar la reusabilidad del filtro un poco mas. Este filtro no tiene mucha utilidad, ya que siempre filtra por Europa, por lo que vamos a parametrizar el continente del que deseamos obtener los países.

Los filtros requieren de un parámetro en la función obligatorio, pero también permiten n parámetros adicionales que son opcionales. Vamos a añadirle el parámetro continente a nuestro filtro.

app.filter(‘filtroPais’, function() {
        return function(input, continente) {
            var salida = [];
            angular.forEach(input, function(pais) {
                if (pais.continente === continente) {
                salida.push(pais)
                }
            })
            return salida;
        }
    });

Y con este fragmento de HTML podemos escribir el continente por el que deseemos filtrar.



    
  • {{pais.name}}

A partir de aquí ya podéis seguir sólos. Como véis los filtros son una herramienta muy potente que se suelen infrautilizar.

Un saludo,
Samuel Martín

Viewing all 997 articles
Browse latest View live