Json

Proyecto en Eclipse con Jersey(JAX-RS 2.0)+Tomcat+JSON

En los últimos años se pretende llevar más allá la separación entre datos y representación: se está pasando de un modelo en el que la separación se hace en el servidor (con desarrollos MVC) a transferir la responsabilidad de la vista al cliente, con las nuevas “single-page web application” basadas en HTML5.

De este modo se pretende que la comunicación que se lleva a cabo entre el cliente y el servidor sea la mínima posible, siendo datos la información que se transfiere. Los más usados últimamente son los datos en formato JSON, que se adaptan perfectamente a los clientes hechos en JavaScript. Un paradigma de comunicación puede ser por lo tanto un servidor RESTful, tal y como vimos en el ejemplo de AngularJS y CouchDB (que implementa de forma nativa un servidor Web con comunicación RESTful para dar acceso a los datos).

Hay en el mercado diferentes opciones para crear un servidor RESTful con diferentes tecnologías. Al fin y al cabo se trata de implementar un servidor en el puerto 80 (normalmente) que responde al protocolo HTTP y a sus diferentes comandos (GET, POST, HEAD…). Podríamos implementar nosotros mismos uno cualquiera en cualquier lenguaje. La comunicación se realiza a través de diferentes protcolos, como XML, JSON, Texto plano… No pienses que RESTful se emplea para desarrollar aplicaciones Web: también es un buen método para desarrollar la interoperabilidad entre diferentes sistemas.

Dentro de la oferta de Java, existen diferentes opciones, como por ejemplo usar un MVC como Struts2 y añadirle un plug-in para RESTful. Este plugin se encargará de traducir las peticiones HTTP y sus cabeceras a las diferentes acciones que hayamos desarrollado. También existe la opción de usar JAX-RS, que desde Java6 forma parte del estándar J2EE.

JAX-RS es un framework que nos permite desarrollar de una manera sencilla y fiable servicios web de tipo RESTful. Su implementación de referencia es el framework Jersey. Así pues, en este artículo vamos a ver cómo podemos montar en Eclipse un proyecto de base que funcione sobre Tomcat y además la comunicación se lleve a cabo a través de objetos JSON.

Antes de comenzar, hay que destacar que podemos apoyarnos en Maven para todo el proceso. Recomiendo el uso del plugin m2e que ya tratamos en otro artículo del blog (https://mysticalpotato.wordpress.com/2014/01/21/maven-en-eclipse-m2e/).

1. Crear Proyecto Web dinámico con Eclipse y Tomcat 7

Esto no debería ser problema. Como siempre, ir a File->New->Dynamic Web Project

newDynamicProject

Si además en el último paso indicamos que queremos crear el fichero web.xml mucho mejor (aunque posteriormente vamos a sobreescribirlo).

2. Incluir las bibliotecas de Jersey y Jackson

Ahora dos alternativas, dependiendo de si usamos o no Maven:

Si NO usamos Maven

a. Ir a https://jersey.java.net/download.html y descargar el fichero “Jersey JAX-RS 2.0 RI bundle” y descomprimirlo en nuestro disco duro.
b. Copiar las bibliotecas de los tres directorios a WEB-INF/lib (o incluirlas en el proyecto, si es así, tener en cuenta que hay que añadirlas en el deploy).

También debemos instalar Jackson, que es el parser para transformar a JSON las comunicaciones del servidor RESTFUL:

a. Ir a http://wiki.fasterxml.com/JacksonDownload y descargar la última versión
b. Copiar las bibliotecas a WEB-INF/lib (o incluirlas en el proyecto, si es así, tener en cuenta que hay que añadirlas en el deploy).

En el caso de que estemos usando Maven

En primer lugar podemos convertir un proyecto que no tengan Maven en uno que sí lo tenga. En Eclipse con m2e es muy sencillo: pulsamos con el botón auxiliar del ratón en el proyecto, luego en Configure->Convert to Maven Projects

jax1

Una vez tenemos el proyecto convertido a Maven podemos incluir las dependencias en el fichero pom.xml; se puede hacer tanto con la interfaz gráfica de Eclipse como editando el código. Habría que incluir las siguientes:


<dependencies>
 <dependency>
 <groupId>org.glassfish.jersey.containers</groupId>
 <artifactId>jersey-container-servlet</artifactId>
 <version>2.5.1</version>
 </dependency>
 <dependency>
 <groupId>org.glassfish.jersey.core</groupId>
 <artifactId>jersey-client</artifactId>
 <version>2.5.1</version>
 </dependency>
 <dependency>
 <groupId>com.fasterxml.jackson.jaxrs</groupId>
 <artifactId>jackson-jaxrs-json-provider</artifactId>
 <version>2.2.3</version>
 </dependency>
 </dependencies>
 

Las dos primeras dependencias indican que se va a emplearn Jersey versión 2.5.1 (la última cuando se creó este post). El siguiente, com.fasterxml.jackson.jaxrs, es provider de JSON para poder transformar la comunicación RESTful a este protocolo.

3. Crear el web.xml para que la aplicación funcione correctamente en nuestro contenedor de servlets (Tomcat 7 en nuestro caso)

<?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" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
 id="WebApp_ID" version="3.0">
 <display-name>js6</display-name>

<servlet>
 <servlet-name>JAX-RS Servlet</servlet-name>
 <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
 <init-param>
 <param-name>jersey.config.server.provider.packages</param-name>
 <param-value>com.mysticalpotato.JAX</param-value>
 </init-param>
 <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
 <servlet-name>JAX-RS Servlet</servlet-name>
 <url-pattern>/rest/*</url-pattern>
 </servlet-mapping>

<welcome-file-list>
 <welcome-file>index.html</welcome-file>
 <welcome-file>index.htm</welcome-file>
 <welcome-file>index.jsp</welcome-file>
 <welcome-file>default.html</welcome-file>
 <welcome-file>default.htm</welcome-file>
 <welcome-file>default.jsp</welcome-file>
 </welcome-file-list>
</web-app>

¿Qué es importante en este web.xml?

Principalmente que existe un servlet (org.glassfish.jersey.servlet.ServletContainer, solo en el caso de JAX-RS 2) que se encarga de capturar todas las peticiones que le lleguen al subdirectorio “rest” del servidor y que las procesará como RESTful: tendrá en cuenta la URL y las operaciones de la cabecera y las asignará a determinadas clases. El directorio “rest” puede ser cambiado por el que nosotros indiquemos.

Además hay otro parámetro muy interesante, jersey.config.server.provider.packages, que indica en qué paquete están las clases a las que se tiene que mapear las peticiones RESTful. No existe un fichero de asignación como tal, sino que, a través de anotaciones, en cada clase se indica la URL a la que responde cada una, y dentro de ella, qué métodos responden a cada tipo de operaciones y qué resultado o entrada procesan. En el caso del ejemplo está asignado a “com.mysticalpotato.JAX” pero puedes cambiarlo por lo que creas conveniente.

4. Crear una aplicación básica

Una vez tenemos configuradas las bibliotecas y el contenedor de servlets, podemos comenzar a desarrollar nuestro primer servicio:

En primer lugar vamos a crear una sencilla clase de datos, un POJO que contiene tres características de un coche. Lo albergamos en el paquete com.mysticalpotato.data:

package com.mysticalpotato.data;

public class Coche {

 String marca;
 String modelo;
 long potencia;
 public String getMarca() {
 return marca;
 }
 public void setMarca(String marca) {
 this.marca = marca;
 }
 public String getModelo() {
 return modelo;
 }
 public void setModelo(String modelo) {
 this.modelo = modelo;
 }
 public long getPotencia() {
 return potencia;
 }
 public void setPotencia(long potencia) {
 this.potencia = potencia;
 }
}

De este clase implementaremos un objeto que será el que se transforme de modo automático a formato JSON cuando se publique el punto de acceso RESTful;

Creamos un paquete para albergar los servicios RESTful, que coincida con lo indicado en el fichero web.xml como parámetro del servlet RESTful. En el caso de este ejemplo tenemos com.mysticalpotato.JAX.

Dentro vamos a crear la clase “Coches” con el siguiente código:

package com.mysticalpotato.JAX;

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

import com.mysticalpotato.data.Coche;

@Path("coches")
public class Coches {

@GET
 @Produces(MediaType.APPLICATION_JSON)
 public Coche getIt() {
 Coche c = new Coche();
 c.setMarca("Ferrari");
 c.setModelo("F45");
 c.setPotencia(430);
 return c;
 }
}

Cosas importantes:

  • Se ha declarado “coches” como la URL de acceso a este punto RESTful. Entonces la URL será del tipo http://servidor.com/rest/coches. El “directorio” rest viene de la confijguración del servlet en el web.xml.
  • Cuando se haga una petición de tipo GET (la que se hace cuando se pide una página en un navegador), se accederá al método getIt(). La razón es que hemos indicado esto con la anotación @GET.
  • El tipo de datos de salida será de tipo JSON, y funcionará además porque hemos usado el proveedor Jackson, que se encargará de hacerlo funcionar.

La salida será:

{"marca":"Ferrari","modelo":"F45","potencia":430}

Un aspecto importante: será necesario indica que cuando el proyecto se despliegue en el servidor, también lo hagan las librerías de Maven que se han indicado. Para ello ir a propiedades del proyecto->Deployment Assembly y añadir las dependencias de Maven o de los ficheros si se ha hecho en manual.

Anuncios

CouchDB como base de datos para AngularJS

Vamos a ampliar el ejemplo anterior hecho con AngularJS (https://mysticalpotato.wordpress.com/2014/01/31/creando-una-sencilla-aplicacion-con-angularjs-y-yeoman/) con una interesante funcionalidad que nos permitirá guardar el estado del dibujo que hayamos hecho en un momento determinado y claro está, volver a recuperarlo más adelante.

Como se vio en el post anterior, se utiliza una estructura de objetos en Java (por tanto convertible a formato JSON) para almacenar el número de veces que se había visitado cada una de las celdas del ejemplo. De este modo, si almacenamos este objeto, podremos reproducir fielmente la posición en un momento determinado.

Existen diferentes opciones para guardar los diferentes estados para nuestra aplicación. Por ejemplo podemos utilizar el localstorage de HTML5 o también podemos usar una base de datos. En este último caso lo mejor es tratar con una base de datos que pueda manejar la información en formato JSON. Existen múltiples alternativas, pero en este caso nos vamos a decantar por CouchDB.

Usar CouchDB para este ejemplo nos ofrece varias ventajas. La primera es que es una base de datos NoSQL que maneja documentos JSON de forma nativa. Otra ventaja es que para interaccionar con ella, monta un servidor RESTful a través de HTTP. Esto facilita enormemente las llamadas AJAX que tenemos que hacer en AngularJS.

Vamos pues en primer lugar a ver cómo se instala y se crea la base de datos para salvar el estado del dibujo en cada momento con CouchDB. Más adelante veremos lo fácil que es modificar utilizar AngularJS para soportar esta nueva funcionalidad.

Seguimos los siguientes pasos:

  1. Descargar la última versión de CouchDB (al momento de escribir este artículo era la 1.5.0). Accedemos a su sitio web y descargamos el fichero. Están en http://couchdb.apache.org/
  2. Instalamos CouchDB. Muy fácil, sólo hay que indicar el lugar del disco duro donde lo pondremos. A vuestro gusto. Una cosa que no he hecho es hacer que se arranque como servicio.
  3. Accedemos al directorio de instalación/bin y ejecutamos CouchDB.bat . Así se arranca el servidor, que responde en http://127.0.0.1:5984/

Si queremos podemos probar a llamarlo desde un navegador y hacer llamadas. También podemos usar cURL para invocar las acciones de la API. Tienes una información detallada en la documentación oficial de esta base de datos: http://docs.couchdb.org/en/latest/intro/api.html

Por simplificar el proceso, CouchDB incluye un gestor Web integrado en su servidor. Su acceso es muy fácil, http://127.0.0.1:5984/_utils/ . Este manager se llama “Futon”. Como dato de la versatilidad de CouchDB, esta base de datos permite almacenar documentos y servirlos como si de un servidor web se tratase. Futon es una aplicación Web que sirve la base de datos, e incluso nosotros podremos incluir nuestra aplicación (HTML+CSS+JS) y hacer que CouchDB la sirva a los navegadores. Son los llamados Design Documents (http://guide.couchdb.org/draft/design.html).

Veamos cómo crear la base de datos dentro de CouchDB.

En primer lugar creamos una base de datos para almacenar la información. Entramos en Futon (http://127.0.0.1:5984/_utils/) Pulsamos sobre “Create Database…” y le damos el nombre “dibujo”. Aquí se almacenarán los objetos Javascript que determinan el estado de cada celda del dibujo, esto es, el número de veces que se ha pasado el ratón por encima.

couch1

Si abrimos el navegador e introducimos la dirección http://127.0.0.1:5984/dibujo podremos ver información acerca de la base de datos recién creada. Después de varios usos podremos obenter algo así:

{"db_name":"dibujo","doc_count":28,"doc_del_count":0,"update_seq":30,"purge_seq":0,"compact_running":false,"disk_size":90216,"data_size":13301,"instance_start_time":"1391813178196821","disk_format_version":6,"committed_update_seq":30}

Sin entrar mucho en detalle, CouchDB almacena por cada uno de los registros un identificador (_id) y un número de revisión (_rev). El Id se asigna autmáticamente si no indicamos nada, lo mismo que el número de revisión (_rev). Sí, aquí se almacenan diferentes versiones de un mismo registro, de modo que hay un histórico de datos. Estos dos valores son básicos, y se les tienen que unir el resto de datos que vamos incluyendo. Al final podríamos obtener algo así:

{
 "_id": "9cd4cfb4841782207f7f3460f1002285",
 "_rev": "3-313a13f005e452222cbc1e4aca28121a",
 "e": {
 "a2": 2,
 "b2": 2,
 "c2": 2,
 "d6": 1,
 "c6": 1,
 "b6": 2,
 "b7": 3,
 "a7": 2,
 "c3": 1,
 "d3": 1,
 "d4": 1,
 "c4": 1,
 "c5": 1,
 "b5": 1,
 "a8": 1,
 "a9": 1,
 "c7": 1,
 "d7": 1,
 "d9": 1,
 "b9": 1
 },
 "t": 1391809957089
}

Donde:

  • “e” es el atributo que representa cada uno de las celdas sobre las que ha pasado el ratón.
  • “t” es la fecha en la que se ha insertado el registro (un simple new Date().getTime() en Javascript).

Cabe suponer por lo tanto, que usando la característica RESTful de CouchDB, podamos sacar un registro concreto de la base de datos mediante la llamda a esta URL: http://127.0.0.1:5984/dibujo/9181ffd10aa47c52e1cee65ce0006ecd . Esto es, usamos el campo _id para identificar el elemento, asique lo tenemos fácil a la hora de recuperar la información de la base de datos.

Por otra parte también parece interesante recuperar un listado de los elementos que hay en la base de datos, para poder cargar la información. En una base de datos tradicional NoSQL haríamos algo así como  SELECT * FROM dibujo. Pero esto es una base de datos NoSQL y se emplea otro paradigma: MapReduce . A grandes rasgos es una combinación de dos funciones, Map y Reduce, que se aplican de forma secuencial, una detrás de otra y que construyen a partir de un conjunto de datos un conjunto de resultados:

  • Map se encarga de recorrer todos los elementos de la base de datos y extraer como resultados los que cumplen el criterio indicado. Es decir, para un conjunto de datos almacenados en la base de datos extrae otro conjunto de resultados.
  • Reduce se aplica sobre los elementos resultados obtenidos de Map para obtener un valor reducido, como por ejemplo la suma de un determinado campo de los resultados. Gracias a la arquitectura interna de CouchDB basado en árboles-B (B-tree) se consiguen unos rendimientos espectaculares.

Vamos a crear una función Map que nos devuelva los elementos que en la base de datos usando Futon (http://127.0.0.1:5984/_utils/).

Acedemos a nuestra base de datos (dibujo) y a la derecha arriba seleccionamos en el desplegable “View” la opción “Temporary view…”. Aparecerá una tabla con dos grandes celdas: la de la izquierda para escribir la función de Map y la de la derecha para Reduce. Vamos a ir solamente con la de Map.

couch1

Si vemos la función por defecto es la siguiente:

function(doc) {
 emit(null, doc);
}

couch1

Esta función Javascript se aplica a todos y cada uno de los documentos de la base de datos. Por ello tiene un parámetro llamado “doc”, que representa cada documento. Sí, se llama una vez por cada documentos que exista. El siguiente elemento que destaca es la función “emit”, que se encarga de incluir un resultado. Este resultado se emite en forma de dupla: [key, value]. Los resultados se ordenan respecto a la key (recordad la estructura de árbol-B). Además debemos saber que esta función se aplica cuando hay un cambio en la base de datos, y no cada vez que se llama la vista. Esto es una gran ventaja de cara al rendimiento de la base de datos.

Nuestra función Map será la siguiente:

function(doc) {
if(doc.e && doc.t)
 emit(doc.t, doc.e);
}

Que no es otra cosa que emite un resultado cuando “e” (los elementos del grid y las veces que se ha pasado el ratón sobre ellos) y “t” (la fecha) no sean nulos. Además emite como resultado la dupla (t,e) , es decir, el tiempo o fecha en la que se ha creado el registro y el contenido. Pulsamos sobre el botón “Run” y vemos los resultados (es posible que no veas nada porque aún no tienes datos… en este caso crea varios documentos nuevos con New Document). Si los resultados son los esperados, salvamos pulsando el botón Save As. De nombre elegiremos “_design/dibujo” y de nombre de la vista será “todos”.

Si hemos hecho todo bien veremos cómo podemos acceder a esta vista mediante la URL: http://127.0.0.1:5984/dibujo/_design/dibujo/_view/todos

Es el turno ahora de modificar el ejemplo de AngularJS para poder escribir y leer los registros de la base de datos.

En primer lugar debemos saber que AngularJS dispone de una interfazz para hacer llamadas AJAX contra el servidor fácilmente, al estilo JQuery. Esta interfaz es $http (http://docs.angularjs.org/api/ng.$http) y está especialmente diseñada para soportar el protocolo REST.

Comenzaremos por modificar el controlador existente para añadir una nueva función que salve el estado actual del juego:

$scope.saveElementos = function()
{
    $http.post("http://127.0.0.1:5984/dibujo", {"e":$scope.elementos,"t":new Date().getTime()})
    .success(function(datos)
    {
       $scope.listadoElementos.push(datos);
       $scope.respuestaServidor = datos;
    });
}
 

Podemos ver cómo hacemos una petición de tipo POST a la URL de la base de datos, pasando por parámeotr un objeto que tiene dos atributos “e” para salvar los elementos y “t” para salvar la fecha y hora. Los elementos se pueden sacar de $scope.elementos, que como ya sabemos de artículos anteriores, está continuamente actualizado gracias al doble binding con la vista de angular. La fecha se saca simplemente los milisegundos con el new Date().

Como se trata de una función asíncrona, preparamos una función anónima con el resultado del servidor. Por una parte insertamos con push() el resultado (que es el elemento insertado) en un listado en el que tenemos todas las partidas guardadas. Por otra parte actualizamos una simple etiqueta que muestra por pantalla el resultado. No hemos implementado la función de error.

Así se fácil es. Se crea una función que usa $http y funciona correctamente. ¡Ah! Es buena idea incluir la inyección del elemento $http en el controlador que acabamos de modificar.

angular.module('angularJs2BbApp').controller("ctrlDibujo", ["$scope","$http",function($scope, $http, elementosProvider)

El último paso para salvar elementos es crear un botón o un enlace que llame a esta función. Esto se tiene que hacer en la vista:

<button ng-click="saveElementos()"class="btn btn-warning">Salva elementos...</button>

Cuando se haga click sobre este botón se llamará a la función y se creará un nuevo estado en la base de datos con los datos de las celdas del dibujo.

Problema CORS

Aún queda un problema por resolver… y es el CORS (Cross-Origin resource sharing). Que resuminéndolo en una frase sería una llamada AJAX hecha de un dominio a otro. Es decir, nuestra aplicación puede estar corriendo en 127.0.0.1:9000 si la ejecutamos con gruntJS, pero el servidor CouchDB y su interfaz RESTful responden en 127.0.0.1:5984. Aunque es la misma IP no nos debemos olvidar del puerto, que hace que sea otro dominio diferente, y por eso dé el error. ¿Cómo podemos resolver esto?

La solución pasa por hacer que el servidor CouchDB emita una cabecera HTTP en cada llamada, indicando que admite que le hagan peticiones desde determinados dominios. Esta cabecera es “Access-Control-Allow-Origin”. ¿Cómo configuramos CouchDB par que la emita?

Es muy sencillo, sólo dos pasos (asegurante de tener una versión que lo soporta, la 1.5.0 sí que lo soporta – desconozco a partir de qué versión funciona):

1. ir a /etc/local.ini
2. Introducir estos valores:

[httpd]
enable_cors = true
[cors]
origins = *

3. Reinicamos el servidor CouchDB

Con esto le indicamos al servidor de CouchDB que vamos a recibir peticiones AJAX desde otro dominio, y que las aceptamos. Nuestra aplicación Angular, al recibir la contestación, verá que en la cabecera se permite su dominio, esto es “Access-Control-Allow-Origin:http://127.0.0.1:9000&#8221;. Así debería funcionar todo correctamente.

Además de ir almacenando las partidas, parece interesante disponer de algún método para ver las partidas que ya hay almacenadas. Es en este momento cuando vamos a utilizar la vista creada unos párrafos atrás. Cada vez que se cargue la aplicación web mostraremos un listado con las partidas guardadas en la base de datos.

Para ello procedemos de modo parecido a antes, utilizando la interfaz que nos proporciona $http para hacer llamadas AJAX:

$scope.loadTodosElementos = function()
{
 $http.get("http://127.0.0.1:5984/dibujo/_design/dibujo/_view/todos").success(function(datos)
 {
 $scope.listadoElementos=datos.rows;
 });
}

La URL que invocamos es la de la vista creada, que se encuentra dentro del documento_design dibujo. Esta vista nos devuelve un objeto JSON con metainformación, y dentro de su campo “row” se encuentra el listado de resultados que necesitamos. Utilizamos la variable “listadoElementos” para guardar cada uno de los estados del juego.

Usamos AngularJS para mostrar todos los elementos con un simple ng-repeat

<ol>
    <li ng-repeat="lista in listadoElementos"><a ng-click="loadElementosById(lista.id)" >{{formateaFecha(lista.key)}}</a></li>
</ol>

Si te fijas, para mostrar la fecha hemos usando la función “fromateaFecha”, que también es una función que forma partel del controlador que hemos creado. Vamos a crearla:

$scope.formateaFecha = function(milis)
{
    console.log("fecha: " + milis);
    return moment(new Date(milis)).format('llll');
}

Para facilitar el trabajo se ha usado http://momentjs.com/ que es una biblioteca de Javascript para el manejor de fechas. La puedes bajar directamente e incluirla dentro del index.html o usar bower si has usado Yeoman:

bower install -S momentjs

En el listado de las partidas, si te fijas, he incluído un enlace con ng-click y la llamada a la carga de la función por Id. Puedes suponer que se trata de otra función que hace uso del API RESTful de CouchDB:

$scope.loadElementosById = function(id)
{
    $http.get("http://127.0.0.1:5984/dibujo/"+id).success(function(datos)
    {
        $scope.elementos = datos.e;
    });
}

Con la URL apuntando hacia el ID correspondientes descargaremos el estado del dibujo determinado y como estamos usando AngularJS, al cambiar el modelo (la variable $scope.elementos), se cambia rápidamente la vista (que es el grid de divs sobre los que se pasea el ratón). El resultado total sería algo como esto:

angular2

En resumen, hemos visto cómo usar $http para conectar con una base de datos CouchDB que pone a nuestra disposición una interfaz RESTful para el desarrollo de aplicaciones AngularJS de una forma rápida y escalable. 

PS: podéis acceder al GitHub de esta aplicación en: https://github.com/4lberto/AngularJS_CouchDB

Objetos en Javascript (I). Prototipos

Conocer el funcionamiento básico y los fundamentos de los objetos en Javascript es necesario para poder entender cómo se utilizan las librerías que existen para este lenguaje que tanto recorrido está teniendo en los últimos años. Sirva esta pequeña guía para refrescar los conceptos.

En  Javascript, casi todo es un objeto. Todo aquello que no es una cadena de texto, un número, un valor boolean (true o false), null o undefined, es un objeto. Incluso las funciones son objetos. Cuando usamos bibliotecas como jQuery, Underscore o AngularJS podemos intuir que hacemos referencia a objetos que nos brindan multitud de operaciones que facilitan la vida. Si vamos más allá y revisamos el código fuente veremos la particular forma que tienen de declarar los objetos.

Los objetos en Javascript no siguen un paradigma similar al de otros lenguajes basados en programación orientada a objetos como por ejemplo Java, donde los objetos son instancias de una clase bien definida que contiene propiedades (datos) y métodos (operaciones)

En Javascript un objeto puede considerarse más como un conjunto de propiedades, es decir, de pares de identificadores y valores asociados. Se podría ver como una tabla hash por ejemplo o algo parecido a las estructuras (structs) de otros lenguajes de programación tipados.

Una cosa muy interesante de Javascript es que se pueden asignar funciones a variables, de manera que así se crean los métodos que operan sobre estos objetos.

Las propiedades de un objeto además son dinámicas, no sólo en la asignación de los valores, sino en su existencia: se pueden añadir o eliminar propiedades en cualquier momento. Por supuesto, los objetos en Javascript se manejan por referencia.

Para crear un objeto en Javascript simplemente se deben definir sus propiedades (y métodos, si es procedente) utilizando literales, esto es, indicando las propiedades entre llaves:

var obj1 = {prop1:2, prop2;3.14};

//Podemos incluir otra propiedad a posteriori
obj1.prop3=23;

//O también se pueden incluir métodos, gracias a la asignación de funciones anónimas a las propiedades
obj1.suma = function(){return this.prop1 + this.prop2};

//Invocamos a la propiedad, sabiendo que es una función - usando los paréntesis
obj1.suma();

//Es posible llamar de diferentes modos a una propiedad de un objeto
obj1.prop1;
obj1["prop1"];

//Y como siempre, está la notación JSON
obj2 = {"p1":2,"p2":3};
typeof(obj2);

//Se puede eliminar si se desea una propiedad mediante el comando delete:
delete(obj1.p1);

También hay otros métodos de creación, como es el caso del operador new, que crea un nuevo objeto y lo inicializa a partir de la llamada a una función que devuelve un objeto. Esto se parece mucho más al modo de funcionar de Java. Por ejemplo:

Date;    //Devuelve que Date es una función nativa del sistema
var fecha = new Date();  //Crea un objeto de la clase Date()

//Creamos una función que como sabemos, es un objeto en Javascript
function MiObjeto(){return {"prop1":1,"prop2":2}};

//Creamos un objeto con el operador new que llama a la función constructora anterior
var miob = new MiObjeto();

//Comprueba que realmente se trata de un objeto
typeof(miob);

//O simplemente hacemos uso de una función anónima que siempre nos devolverá un objeto nuevo al no poder hacer referencia desde otro lado
var unObj = function(){return {prop1:1}};
unObj instanceof Object  //Devuelve true

Hasta aquí todo sencillo y familiar (salvo quizá la posibilidad de definir los métodos como funciones asignadas a una propiedad). Pero ahora es donde surge la característica especial de Javascript: los Prototipos.

Todo objeto en Javascript tiene un objeto secundario asociado llamado Prototipo, y además hereda las propiedades (atributos y métodos) de él. Los objetos que están definidos de forma explícita con literales (al estilo JSON) tienen como objeto prototipo el objeto Object.prototype. Los objetos que se crean utilizando el operador new reciben el indicado en el constructor. Por ejemplo los creados con new Object reciben Object.prototype, y los creados con new Date, el objeto Date().

De este modo el objeto prototipo dota a los objetos a los que está asociado de funcionalidades más o menos básicas: el Object.prototype por ejemplo tiene el método toString().

Los objetos prototipos además pueden tener a su vez otros prototipos. Por ejemplo el objeto asociado a Date, que es el Date.prototype, a su vez tiene como prototipo Object.prototype, que le da el método toString por ejemplo. Sin embardo, el Object.prototype no tiene ningún otro prototipo asociado.

Es importante tener en la mente que los objetos heredan propiedades (atributos y métodos) de sus prototipos.

¿Cómo se crea un objeto y se especifica su prototipo? Utilizando la función Object.create(), que recibe como parámetro el objeto que queremos usar como prototipo.

//Creamos un objeto con dos atributos y un método que los suma
var obj1 = {"p1":1,"p2":2,"suma":function(){return this.p1+this.p2}}

//Vemos que devuelve el valor 3
obj1.suma();

//Creamos un objeto que tiene como prototipo a obj1 y que por lo tanto hereda sus propiedades (atributos y métodos)
var obj11 = Object.create(obj1);

//Vemos que tiene la función suma y que devuelve 3, puesto que los valores de sus atributos son 1 y 2
obj11.suma();

//Cambiamos el valor de p1
obj11.p1 = 6;

//Vemos que ahora devuelve 8
obj11.suma();

Se puede ver cómo el nuevo objeto obj11 tiene los atributos y operaciones de su prototipo. Si cambiamos las propiedades de obj11 que tienen el mismo nombre que las de obj1, se sobreescriben los valores en obj11, pero no cambian para obj1.

Si creamos un tercer objeto que tenga como prototipo a obj1

var obj12 = Object.create(obj1);

//Podremos ver que la suma sigue siendo 3 ya que obj1 permanece invariable
obj12.suma()

//Y que obj11 sigue valiendo 8
obj11.suma()

//Pero si cambiamos el valor de p2 de obj1, cambiarán los valores de p2 en obj11 y obj12, ya que se basan en estos:
obj1.p2 = 10;

obj11.suma();   //16

obj12.suma();  //10

//Pero si cambiamos el valor de p1 en obj1
obj1.p1 = 0

//El valor de obj11.suma() no varía con este cambio, puesto que antes se ha sobreesrcrito al hacer obj11.p1 = 6; Sigue dando 16:
obj11.suma();

//Siempre se puede comprobar si un objeto es el prototipo de otro mediante una función que viene en Object.prototype llamada isPrototypeOf:
var obj1 = {x:1};

var obj11 = Object.create(obj1);

obj1.isPrototypeOf(obj11);

Si se invoca Object.create() sin usar parámetros, se empleará el prototipo por defecto que es Object.prototype.

Obtener imágenes de un enlace (al estilo Facebook) usando YQL, JSON y jQuery

(Ejemplo directo en: http://jsfiddle.net/4lberto/mGxGB/12/)

En Facebook, Google+ y otros sitios actuales es posible compartir un enlace a una página web con nuestros contactos. Una de las características de estos enlaces es que suelen aparecer con imágenes asociadas como ilustración del enlace. En este post veremos cómo podemos obtener en tiempo real las imágenes de un enlace cualquiera para mostrárselas a un usuario.

La herramienta principal que usaremos será YQL, abreviatura de Yahoo Query Language. Se trata de una utilidad que permite utilizar la potencia de análisis de datos de yahoo mediante una interfaz de comandos al estilo SQL, de modo que se evita tener que programar engorrosos métodos de parseo para tratar la información en formato Web.  Permite muchas otras utilidades.

La llamada a la herramienta YQL la realizaremos a través de una llamada AJAX de jQuery por ejemplo y obtendremos como respuesta un JSON con la información requerida. En nuestro caso le pasaremos al motor de YQL una URL y como salida esperaremos un JSON con todos los elementos img que cumplan las caracterísicas que hemos demandado.

YQL tiene una interfaz web donde se pueden programar la consultas al estilo de los clientes SQL para bases de datos relacionales. En esta interfaz podremos tanto programar la consulta y ver sus resultados como configurar el formato de salida. Para ello se accede a http://developer.yahoo.com/yql/ . El acceso a la consola de pruebas es: http://developer.yahoo.com/yql/console/

Nuestra consulta estará basada en la siguiente:


select * from html where url="http://www.engagdet.com" and xpath="//img"

Esta consulta nos devuelve todos los elementos img de la URL http://www.engagdet.com. Si seleccionamos el formato JSON, además de metainformación de la consulta, el resultado que obtenemos será algo similar al siguiente:

"results": {
"img": [
{
"alt": " Foto: BBC MUNDO",
"height": "305",
"src": "http://p2.trrsf.com/image/get?src=http%3A%2F%2Fimages.terra.com%2F2012%2F10%2F27%2F01-morgue.jpg&w=407&h=305&o=cf",
"title": " Foto: BBC MUNDO",
"width": "407"
},
{
"alt": " Foto: BBC MUNDO",
"height": "41",
"src": "http://p2.trrsf.com/image/get?src=http%3A%2F%2Fimages.terra.com%2F2012%2F10%2F27%2F01-morgue.jpg&w=55&h=41&o=cf",
"title": " Foto: BBC MUNDO",
"width": "55"
},
{
"alt": " Foto: DIFUSION",
"height": "41",
"src": "http://p2.trrsf.com/image/get?src=http%3A%2F%2Fimages.terra.com%2F2012%2F10%2F27%2F02-oxford.jpg&w=55&h=41&o=cf",
"title": " Foto: DIFUSION",
"width": "55"
},

Como dentro de una página Web existen multitud de imágenes y no todas ellas están relacionadas con el contenido, se puede filtrar por tamaños, por lo que podemos exigir un tamaño mínimo que nos  evite estas imágenes. Desgraciadamente diferenciar por el contenido es una operación inviable. También podemos ordenar los resultados por tamaño de modo que los primeros sean los mayores. Es probable que las imágenes más grandes de una página Web sean las que acompañan a la noticia. Desafortunadamente aquellas foto que no tengan un tamaño específicos en los atributos width y height de img no podrán ser catalogadas.

La consulta con estas condiciones aparece a continuación:


select * from html where url="http://www.terra.com" and xpath="//img" and width>60 | sort(field='width', descending='true')
<pre>

Finalmente configuraremos el resultado en formato JSON que es lo que vamos a analizar con la llamada AJAX de jQuery. Tomamos la URL de llamada que nos indica YQL. La podemos encontrar en la zona inferior de la consola de YQL. En nuestro ejemplo la URL de llamada será la siguiente (* contiene saltos de línea para que sea legible) :


http://query.yahooapis.com/v1/public/yql?
q=select%20*%20from%20html%20where%20url%3D%22http%3A%2F%2F
www.terra.com%22%20and%20xpath%3D%22%2F%2Fimg%22%20and%20
width%3E60%20%7C%20sort(field%3D'width'%2C%20descending%3D'true')

Una vez tenemos la consulta hecha y probada ejecutando la URL en el navegador y probando la salida en servicios como http://json.parser.online.fr/, podemos pasar a programar la parte de Javascript con jQuery.

Tenemos que tener en cuenta de que se trata de una llamada AJAX a un dominio diferente al de residencia del Javascript. Este tema ya se trató en otro post de este blog (https://mysticalpotato.wordpress.com/2010/11/18/jsonp-salvando-la-limitacion-de-dominio-de-xhr-cuando-se-hace-cross-scripting-xss/). jQuery soluciona fácilmente esto añadiendo un parámetro de callback que completa automáticamente.

También habrá que parametrizar la URL del enlace. Para ello tenemos un formulario cualquiera con un input con id=”input_web”. Mejor lo vemos en código fuente:


function procesa_web() {

var url_web = $("#input_web").val()
//console.log("Valor completo:" + url_web);

$("#resultado").html("");

$.ajax({
url: 'http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20html%20where%20url%3D%22' + escape(url_web) + '%22%20and%0A%20%20%20%20%20%20
xpath%3D"%2F%2Fimg"%20and%20width%3E35%20and%20
height%3E35&format=json&diagnostics=true&callback=?',
async: false,
cache: false,
dataType: 'json',
success: function(data) {
imagenes_from_web = data.query.results.img; //asignamos a la variable global
var salida = "<b>Imágenes de Web</b><br />";
salida += "<a href=\"javascript:void(0);\" onclick=\"javascript:prev_image()\">Anterior</a>&nbsp;|&nbsp;";
salida += "<a href=\"javascript:void(0);\" onclick=\"javascript:next_image()\">Siguiente</a><br />";
salida += "<img id=\"img_from_web\" data-contador=\"0\" src=\"" + imagenes_from_web[0].src + "\"/>";

$('#resultado').html(salida);
}
});
}

Como se puede ver se lee el valor de la URL a procesar del input en la variable url_web y se escapa para incluirla en la URL. Adicionalmente se incluye el parámetro callback=? para salvar el cross-domain indicado anteriormente.

Los parámetros de AJAX son los utilizados en otros post: síncrono, sin caché porque son datos dinámicos y el tipo de datos JSON.

Dentro del sucess de la llamada AJAX se procesa el resultado. En primer lugar tomamos los valores obtenidos en el JSON para asignarlos a una variable global a la página que será compartida por todas las funciones.  Obviamente navegamos por el JSON para sacar sólo el listado de imágenes:

</pre>
imagenes_from_web = data.query.results.img

Posteriormente pintamos en la capa resultado la primera imagen y dos botones que permitirán navegar por las imágenes. Estos dos botones hacen referencia a dos funciones que no hacen otra cosa que ir refrescando el contenido de la imagen que se muestra en ese momento con el de la siguiente o la anterior del vector global a la página que almacena el resultado. Nótese que en el tag img dejamos el atributo data-contador para almacenar el índice del vector de imágenes. La posibilidad de incluir atributos para datos es una de las posibilidades que ofrece HTML5 (http://html5doctor.com/html5-custom-data-attributes/). Las funciones para navegar por las fotos son las siguientes:


function next_image() {

var contador = parseInt($('#img_from_web').attr("data-contador"));
contador++;
var url_imagen = imagenes_from_web[contador].src;

$('#img_from_web').attr("src", url_imagen);
$('#img_from_web').attr("data-contador", contador);
}

function prev_image() {

var contador = parseInt($('#img_from_web').attr("data-contador"));
contador--;
$('#img_from_web').attr("src", imagenes_from_web[contador].src);
$('#img_from_web').attr("data-contador", contador);
}​

El ejemplo acaba aquí, pero si queréis incluirlo al estilo Facebook para una noticia, simplemente, en el momento de que el usuario confirme el enlace, se tomará el índice del vector de imágenes y se leerá la URL para asignarla a un formulario o llamada AJAX.

El ejemplo completo y funcional podéis visitarlo en: http://jsfiddle.net/4lberto/mGxGB/12/

Web Workers en HTML5. Paseo Aleatorio

Ver Demo

Una de las novedades de HTML5 es la posibilidad de crear hilos de ejecución para ejecutar tareas en segundo plano con el fin de no entorpecer la interacción del usario con la página. De este modo acciones que requieran un gran tiempo de procesamiento, especialmente en dispositivos con poca capacidad de proceso, y que bloquean la interface (más de 250ms), pueden ser ejecutadas en segundo plano. Estos hilos han sido denominados Web Workers, y como todo lo de HTML5, se programan usando Javascript. De momento sólo funcionan en las versiones más actuales de Firefox y Google Chrome. El código de este ejemplo está creado usando Google Chrome 20+.

Para usar un WebWorker simplemente hay que instanciarlo en el código JavaScript:

var worker = new WebWorker ("hilo1.js");

Como se puede ver, recibe por parámetro un fichero de Javascript, que contiene el código que se va a ejecutar cuando se lance el hilo. Para ejecutar un hilo simplemente hay que enviarle un mensaje que será captado mediante un listener para ese evento como veremos después.

worker.sendMessage();

Dentro del código del hilo (hilo1.js) está lo necesario para responder a esta llamada, devolviendo otro mensaje.

self.addEventListener('message',function(e)
{
 self.postMessage('fin');
});

Finalmente el hilo principal también registra un listener para “escuchar” los mensajes del web worker:

 worker.addEventListener('message',function(e){
 alert(e.data); //pinta fin en una ventana de alert
 });

El código del WebWorker es Javascript y tiene una serie de limitaciones muy importantes que determinan la arquitectura a la hora de usarlos. No tienen acceso a:

  • document
  • parent
  • window
  • otras librerías como jQuery o Prototype

Con estas limitaciones queda claro que dentro de los hilos no se puede hacer cualquier cosa ni pueden operar de forma independiente sin la interacción con el programa princopal. Realmente los hilos deberían limitarse a recibir y devolver información de tipo texto o JSON al hilo principal, de modo que sea éste el que haga las operaciones de manipulación de componentes del DOM. Por ejemplo, un caso de interacción típico sería:

  1. El hilo principal instancia un WebWorker para que haga una llamada AJAX y le devuelva la información en JSON. Esta información la mostrará en una capa DIV. Si lo hace el hilo principal tarda 1000ms debido al tamaño del JSON (o más en un ordenador/dispositivo lento).
  2. El WebWorker contiene en su código un “EventListener” donde está progrmaado el procesamiento de la acción indicada en el punto 1. Esta acción se ejecutará cuando la pida el hilo principal.
  3. El hilo principal llama al WebWorker y en éste se ejecuta el evento, lanzando la petición AJAX, que recibe una gran cantidad de texto en formato JSON. Parsea esta información a un objeto (y esta operación le lleva bastante tiempo).
  4. El WebWorker notifica al hilo principal que ha acabado, pasándole por parámetro el objeto JSON ya procesado.
  5. El hilo principal recive el evento de ese WebWorker y dispara una función que printa el objeto JSON en el DIV (algo muy rápido una vez se tiene el JSON).

De forma genérica se podría decir que los pasos que tiene que seguir una aplicación que use Web Workers debería ser:

  1. Hilo principal instancia el WebWorker y le ordena comenzar.
  2. El WebWorker tiene un EventListener que se activa cuando el hilo principal le ordena comenzar. En esta llamada se puede incluir información como un texto o un JSON.
  3. El WebWorker procesa y hace una llamada al hilo principal.
  4. El hilo principal tiene otro EventListener que se activa cuando el WebWorker termina. En esta llamada se puede incluir información como un texto o un JSON.

En nuestro ejemplo lo que intentamos programar es un programa que calcule caminos aleatorios (http://es.wikipedia.org/wiki/Camino_aleatorio) partiendo de un valor dado (100). Cada camino tiene 100 valores que son calculados por los hilos que lanzamos, y su resultado será pintado en un objeto canvas.

Un camino aleatorio parte de un punto, que en nuestro caso será el valor 100, y va generando, mediante números aleatorios diferentes valores basados en el paso anterior, así, partiendo del valor 100, el siguiente paso puede ser 101, el siguiente 104, el siguiente 102, luego 98… etcétera. Esto generará una gráfica con forma de rayo. Se trata simplemente de un proceso que puede ser computacionalmente costoso y que si se llevara a cabo en primer plano bloquearía la interfaz de usuario. Los hilos generarán estas series de 100 valores por cada camino en segundo plano y lo enviarán en formato JSON al hilo principal para que simplemente los pinte en el canvas.

En primer lugar vamos a analizar qué necesitamos:

  1. Un worker que genere series de 100 valores que representan caminos aleatorios y que envía al hilo principal en formato JSON
  2. Un generador de caminos aleatorios, incluyendo una función básica como es un generador de números aleatorios.
  3. Un listener en el hilo principal que se encarga de pintar el camino recibido en formato JSON en un canvas.
  4. Un control para parar el proceso del hilo cuando se desee.

Vamos por partes. En primer lugar tenemos que construir el listener para lanzar el evento de paso de mensaje. Hasta que este evento no se produce no hay lanzamiento del hilo:

self.addEventListener('message',function(e)
{
 var iteraciones = e.data.iteraciones;
 for(w=0;w<iteraciones;w++)
 {
 if (!stop)
 main(e);
 else
 {
 break;
 self.close();
 }
 }
});

Hay que notar un par de cosas:
1. Que existe una variable – global al worker – llamada “stop” con un valor booleano. Será modificada por otro evento para indicar que se debe para el hilo (de ahí el break y el self.close(), que cierra el bucle y termina el hilo).
2. Los parámetros que se le pasan al web worker, que será mediante JSON. En este caso se indican en el campo iteraciones el número de paseos aleatorios que va a generar cada hilo. Se lee mediante e.data.iteraciones.

Una vez tenemos el número de iteraciones se llama a la función principal, main(e) que genera un camino aleatorio y lo envía al hilo principal:

function main(e)
{
 var volatilidad = e.data.volatilidad;
 var media = e.data.media;
 var base = e.data.base;

var salida = "[";
 for(j=0;j<100;j++) //series de 100 valores
 {
 salida+="{\"V\":"+base+"},"; //compone la salida en array de JSON
 base=(siguiente_paso(media, volatilidad, base));
 }
 salida = salida.substring(0,salida.length-1);
 salida +="]";
 self.postMessage(JSON.parse(salida)); //Devuelve la salida en formato JSON
}

Se puede ver que se toman los parámetros de volatilidad, media y base desde el JSON de entrada desde el hilo principal. A partir de ahí, llamando a la función siguiente_paso, se va generando mediante un bucle el listado de puntos del camino aleatorio. Se hace en una cadena de texto con formato JSON para posteriormente ser transformada a objeto JSON con la función JSON.parse. Finalmente se envía al hilo principal a través de self.posMessage.

La operación siguiente_paso genera el siguiente valor del camino aleatorio, partiendo del valor actual y aplicándole un número aleatorio de una función normal 0,1 -N(0,1)- . Para su cálculo se opera con la volatilidad y el crecimiento medio indicado. Alta volatilidad permite obtener caminos más dispersos. La media es el crecimiento medio orgánico en porcentaje de cada paso.

function siguiente_paso(media, volatilidad, anterior)
{
 return (anterior+((anterior*media)+(volatilidad*anterior*randN())));
}

La función randN() es la que mayor calculo requiere y hace costoso el programa. Se emplean 100.000 iteraciones para su cálculo, aunque probablemente el óptimo sea mucho menor (12). Se ha elegido un número tan alto de iteraciones para hacer artificialmente costoso (pero preciso) el programa e ilustrar claramente la utilidad de los web workers:

function randN()
{
 var iteraciones = 100000; //lo óptimo son sólo 12, pero para simular carga de CPU
 var salida = 0;
 for(i=0;i<iteraciones;i++)
 {
 salida+=Math.random();
 }
 salida-=(iteraciones/2);
 salida*=Math.sqrt(12/iteraciones);

return salida;
}

Finalmente se ha incluíduo un mecanismo de parada del hilo que actúa en el bucle principal mediante el valor booleano de la variable “stop”, que se comprueba en cada iteración del bucle si debe continuar con el proceso o parar. La API de Web Workers permite enviar otros tipos de mensajes más allá del método “postMessage()”. Mediante el método “terminate()” enviamos un mensaje al Web worker que activa el evento “close” y que se captura mediate el listener correspondiente. Por tanto debemos añadir el siguiente código a nuestro worker:

self.addEventListener('close',function(e)
{
 stop=true;
});

La mejor manera de para un hilo como el que hemos desarrollado es haciendo una parada controlada del proceso, es decir, generando una serie de números completa. Para ello nos valdremos de una vaiable global que se modifica mediante el evento close y que está dentro del bucle de ejecución del worker. De este modo, si esta variable es true se parará el hilo y se cerrará, saliendo del bucle y cerrando el hilo mediante el método “close()”

if (!stop)
 main(e);
else
{
 break;
 self.close();
}

En el lado del hilo principal, el mecanismo consiste en lanzar tantos workers como se indique y registrar un listener por cada uno de ellos con el fin de recibir los mensajes de los hilos. Se guarda la referencia en un array para poder terminarlos posteriormente. El array es una variable global al hilo.

function start_workers()
{
 var num_workers = parseInt(document.getElementById("hilos").value);
 for (i = 0 ;i<num_workers;i++)
 {
 array_workers[i] = new Worker('js/w1.js');
 array_workers[i].addEventListener('message',function(e){
 pinta_serie_canvas(e.data);
 });
 array_workers[i].postMessage({'volatilidad':document.getElementById("volatilidad").value,'media':document.getElementById("media").value,'base':100,'iteraciones':document.getElementById("iteraciones").value}); //ojo:
 }
 }
 

Como se puede ver en el código, se instancia cada objeto web worker pasándole por parámetro el fichero javascript que define su comportamiento y que se ha descrito en líneas anteriores. Posteriormente se hace la llamada mediante la función postMessage. Se le pasa por parámetro JSON los valores de la volatilidad, media, iteraciones y valor 100 de partida. Como se puede ver se leen de unos input text que tienen id’s correspondientes. Cada web worker se mete en un array para tener su referencia.

El listener para cada mensaje de vuelta de los hilos, que contiene el paseo aleatorio de 100 números en formato JSON, hace una llamada la función pinta_serie_canvas, que lo dibuja en un canvas de HTML5

function pinta_serie_canvas(serie)
{
 var x_step = Math.max(1,Math.floor(800/serie.length)); //incremento eje X, al menos 1. siempre entero

 var canvas = document.getElementById('canvas');
 var context = canvas.getContext('2d');
 context.strokeStyle = generaColor();
 context.beginPath();

 var nuevaPosX = 0;
 var nuevaPosY = 250;
 for(j=1;j<serie.length;j++) //el primer valor es la base, es el de partida
 {

 context.moveTo(nuevaPosX,nuevaPosY); //mueve el cursor a la posición de partida para este paso
 nuevaPosX +=x_step;
 nuevaPosY +=-Math.floor(Math.round(serie[j].V - serie[j-1].V)); //avanza la diferencia que haya en v.absoluto

 context.lineTo(nuevaPosX,nuevaPosY);
 context.stroke(); //pinta la línea
 }

 context.closePath();
}

Básicamente se trata de partir del valor medio de la parte izquierda del canvas (punto 0, 250; ya que el canvas tiene 800*500 píxeles) e ir incrementando los valores de X (en 8 píxeles) y de Y (en tantos píxeles como números enteros haya de diferencia entre el punto actual y el siguiente) e ir pinando una línea mediante lineTo. También se genera un color diferente para cada camino pintado. La forma de manejar el canvas es sencilla.

Finalmente se añade el mecanismo para cancelar la ejecución de cada hilo. Tenemos guardadas su referencias dentro de un array, que recorreremos mandando el mensaje de close -mediante terminate()- a cada uno. Este proceso lo realiza la función terminate_workers();

function terminate_workers()
{
 for(l=0;l<array_workers.length;l++)
 {
 array_workers[l].terminate();
 array_workers[l]=null;
 }
}

Mediante la función terminate se envía un mensaje de tipo “close” al hilo, que como se ha visto antes, pone a true la variable “stop” que es tenida en cuenta en el bucle principal del worker.

Finalmente se ha incluído código HTML para crear la interfaz y algo de JavaScript para poder ligar los eventos de los botones al programa principal.

Podéis descargar el ejemplo haciendo un clone del repositorio en: https://github.com/4lberto/webworkers_paseo_aleatorio

Obsérvese cómo el proceso de cálculo de cada camino aleatorio no bloquea la interfaz web del usuario.

Struts2: cuando la vista es un stream (JSON, plain text…) y no un JSP

Hay ocasiones en las que el MVC de Struts2 no tiene por qué tienes como vista una página JSP o HTML al uso, sino que queremos escribir directamente en la salida de datos a través de un stream. Tal es el caso de una acción que responde con un código (un ID por ejemplo) a una llamada AJAX, que lee la respuesta en Javascript para confirmar que la operación se ha llevado a cabo.

Para ello tenemos que definir un tipo especial de resultado en el fichero struts.xml, de tipo “stream”. Dentro, podemos configurar el formato de la cabecera HTTP que indica el contentType, así como el nombre del inputStream que se usará para escribir los resultados. Lo que escribamos en este stream será lo que devuelva como vista el servidor.

<action name="MiAccion" method="create" class="com.wordpress.mysticalpotato.acciones.MiAccion">
<result type="stream" name="success">
<param name="contentType">plain/text</param>
<param name="inputName">inputStream</param>
</result>
</action>

El siguiente paso es editar la acción (controlador) de struts2. Como siempre, tendremos un fichero con un método llamado “create” (no es necesario, pero la acción de ejemplo tiene otros métodos que responden a diferentes patrones de URL, de ahí que se haya indicado) que tiene como salida un String.


public class MiAccion extends ActionSupport

{

private InputStream inputStream;//Salida para escribir directamente

public String create()

{

String miRespuesta = "666";

inputStream = new StringBufferInputStream(miRespuesta );

return SUCCESS

}

public InputStream getInputStream()

{

return inputStream;

}

Simplemente con este código, le indicaremos, que en la salida SUCCESS (viene del atributo del ActionSupport) tiene que tomarla del inputStream. Nótese cómo el inputStream es un atributo de la clase entera. Además, hay un getter para este atributo para exponer al framework dicho stream.

La salida será “666” en una comunicación HTTP con content-type: plain/text que puede ser leída perfectamente desde Javascript como respuesta a una llamda AJAX

HTML5 localStorage y JSON para dotar de persistencia a una Extensión de Chrome

Uno de los problemas a la hora de trabajar con Javascript+HTML que se ha tenido siempre es cómo almacenar información persistente de la aplicación que hemos creado en el navegador. Javascript fue concebido principalmente para manejar el árbol DOM y hacer modificaciones en la interfaz de usuario para hacerla más atractiva, de donde surgió aquel término llamado DHTML (Dynamic HTML). La tendencia actual es a crear aplicaciones autónomas en el navegador, tan potentes como las tradicionales aplicaciones ejecutables en un sistema operativo pero dentro del navegador, gracias a la aparición de nuevas funcionalidades, como las llamadas asíncrontas y sobre todo a la optimización de los motores de ejecución de Javascript en los navegadores, que se convierten en máquinas virtuales potentes sobre las que ejecutar scripts, con los benficios de la interfaz de HTML.

Como decía, uno de los problemas es dotar de más autonomía a la aplicación escrita en Javascript+HTML, y esto suele pasar por disponer de algún mecanismo de persistencia que permita almacenar la información del usuario para que la próxima vez disponga de la misma configuración o datos históricos. Antes de HTML5 se empleaban algunas técnicas como el uso de cookies para estas labores; o el método más correcto de tener un servidor de aplicaciones con un perfil de usuario en el que se almacena la información en remoto, pero esto requiere de conexión a Internet y de una infraestructura de servidor que realice esta tarea, con el coste que ello tiene.

Sin embargo, desde la aparición de HTML5 y de los navegadores que implementan esta especificación, podemos usar una de sus muchas ventajas: el almacenamiento local o localStorage.

Mediante localStorage podremos almacenar en el espacio que nos proporciona el navegador pares de atributos “string”,”string” hasta un máximo de 5 megas de espacio (las limitaciones a cadenas de texto y almacenamiento corresponden a la implementación que hacen los navegadores actuales de HTML5). De este modo podremos almacenar y recuperar estos valores en cualquier momento en nuestro código Javascript.

Para almacenar una cadena de texto en una variable procederemos esta forma:

localStorage.setItem("mivariable","mivalor");
//o lo que es lo mismo:
localStorage.mivariable = "mivalor";

La lectura del valor sigue el procedimiento esperado:

var valor = localStorage.getItem("mivariable");
//o lo que es lo mismo:
var valor = localStorage.mivariable;

La información permanecerá en localStorage de manera persistente y fiable, de modo que podemos apoyarnos en ella para almacenar datos como la configuración de la aplicación o información histórica el usuario.

A continuación se va a emplear localStorage de Chrome para ampliar la extensión básica que se creó en el post https://mysticalpotato.wordpress.com/2011/01/15/extension-sencilla-para-google-chrome/, de modo que se almacena el histórico del tiempo de uso. El resultado de esta modificación se corresponde con la versión 0.3 de la extensión, que puede descargarse en https://chrome.google.com/extensions/detail/lkhahcnmgjhbbdkieijbndecclgcibjp?hl=en#

Para almacenar el histórico de tiempo necesitamos conocer qué valores tenemos que almacenar y cuando. Necesitamos conocer el tiempo de inicio y el de final en cada sesión, que se encuentran en formato de milisegundos en la página background.html, que si se revisa el post anterior de la extensión, se verá que se carga cuando la extensión comienza la ejecución. De este modo el par de datos (inicio, fin), nos dará el tiempo de inicio y el tiempo que ha durado la sesión (fin-inicio). Están en formato Date, que se transforma a milisegundos desde el 1 de enero de 1970, para poder localizarlo con precisión en el tiempo. El resultado del almacenamiento es por lo tanto un array de pares de tiempo.

En primer lugar debemos crear un objeto en Javascript que modele este tiempo:

//Time object
function tiempo (inicio, fin)
{
	this.inicio = inicio;
	this.fin = fin;
}

Lo que queremos almacenar por lo tanto será un array de objetos tiempo. El problema radica ahora en encontrar la forma de transformar el array de tiempo a una cadena de texto y viceversa. El mejor método es hacer uso de JSON. En post anteriores se ha podido ver cómo emplear JQuery y algunas librerías especiales para manejar este tipo de objetos. En esta ocasión usaremos métodos nativos del navegador Chrome. La explicación es que la extensión es específica de Chrome, y tenemos la certeza absoluta de que el usuario dispone de este requisito para correr la app.

Almacenaremos una String de este tipo, que contiene un array de objetos tiempo en formato JSON:

[{"inicio":1295726332072,"fin":1295726339310},{"inicio":1295726339311,"fin":1295726527498},{"inicio":1295726527501,"fin":1295727082467}]

Suponiendo que en tiempo_instancia tenemos un objeto “tiempo” correctamente formado, su transformación a JSON es sencilla:

JSON.stringify( tiempo_instancia );

El problema está en cuándo realizar el almacenamiento de la información: cuando se cierra el navegador o se desactiva la extensión. Estos dos eventos tienen en común que el cierre de background.html, de manera que se puede capturar el evento y llamar a una función:

 //Event when extension closes.
window.onunload =
	function () {
	guardaEstadisticas();
};

Efectivamente, cuando se cierra la extensión o se cierra el navegador se llama a la función guardaEstadisticas(). Ésta se encarga de almacenar el objeto tiempo de la sesión actual, lo que significa encontrarse frente a dos situaciones: que no exista ningún valor anterior o que ya haya un array con uno o varios valores previos. En el primer caso habrá que añadir los corchetes “[]” para indicar que es un array de un sólo elemento. En el segundo caso habrá que recuperar el array , transformándolo desde JSON a array de objetos de Javascript, hacer un push para añadir el último objeto al array y volver a pasarlo a cadena de texto en formato JSON:

function guardaEstadisticas()
{
	//When extension ends, adds the time it's been used
	var tiempo_instancia= new tiempo(tiempo_inicio.valueOf(), (new Date()).valueOf());
	if(!localStorage.COT_history) //first time 
	{
		localStorage.COT_history = "[" + JSON.stringify( tiempo_instancia ) + "]";
	}
	else  //there was some data before
	{
		var historia = JSON.parse(localStorage.COT_history);
		historia.push(tiempo_instancia);
		localStorage.COT_history = JSON.stringify(historia);
		console.log("Añadido:" + JSON.stringify(historia));
	}
}

Como se puede comprobar, pasar de String en formato JSON a array de objetos se puede hacer a través del método JSON.parse que provee Javascript de Chrome. La comprobación de si existe registro anterior es sencilla: if(!localStorage.COT_history) . Como se puede ver, se ha elegido la clave COT_history para llamar a esa variable en el almacenamiento del navegador.

Otro aspecto es cómo se muestran las estadísticas. En este caso he empleado una página nueva llamada history.html enlazara con un enlace normal desde popup.html. Se encarga de recuperar la información del localStorage, pasarla de JSON a array de objetos tiempo y mostrarla mediante un bucle en una tabla, con la particularidad que muestra los tiempos en modo descendiente hasta un máximo de 15 tiempos.

function pintaEstadisticas()
{
	var tiempos = JSON.parse(localStorage.COT_history);
	var salida = "<table><thead><tr><td>Start Time</td><td>Opened for</td></tr></thead><tbody>";
	var limite = Math.max (tiempos.length-15, 0 );
	for (i = tiempos.length-1;i>=limite;i--)
	{
		salida = salida + "<tr><td>";
		salida = salida + (new Date(Number(tiempos[i].inicio))).toString().substr(0,24);
		salida = salida +"</td><td>" + formatTime(Number(tiempos[i].fin)-Number(tiempos[i].inicio));
		salida = salida + "</td></tr>";
	}
	salida = salida + "</tbody></table>";

	document.write(salida);
}

El resultado es:

Finalmente existe la posibilidad de borrar las estadísticas, que no es otra cosa que borrar la información que corresponde a la clave local “COT_history”. Para ello se ha incluido un enlace en history.html que lanza una función que realiza el reset del almacenamiento:

function clear_stats()
{
	localStorage.COT_uses = 1;
	localStorage.removeItem("COT_history");
	window.location.reload()

	return null;
}

Para concluir, podemos ir a la ventana de extensiones pulsando en la llave de opciones de Chrome -> tools -> extensions y activar el “developer modo”, ir a la extensión y pulsar sobre el enlace background.html. Aparecerá la ventaja de inspección (en cualquier página pulsando Ctrl+Shift+j). Pulsando sobre el icono Storage (3º por la derecha) podremos ver los datos del localStorage.

El resultado de este post y otras modificaciones más dieron lugar a la version 0.3 de la aplicación, que puede verse aquí: https://chrome.google.com/extensions/detail/lkhahcnmgjhbbdkieijbndecclgcibjp?hl=en#

Adjunto el zip de la extensión. OJO: RENOMBRAR A .ZIP

JSONP. Salvando la limitación de dominio de XHR cuando se hace Cross-Scripting (XSS)

Existe una variación de JSON llamada JSONP (P de padding – con relleno-) que permite salvar uno de los problemas que existe en las llamadas AJAX a través del objeto XMLHttpRequest. Esta limitación consiste en que desde la página web no podemos escribir un Javascript que llame a otro dominio. Esto es así para evitar problemas de seguridad.

Por ejemplo, en uno de los post anteriores en el que se probaba la utilidad de JSON con Jquery se hacía una llamada asíncrona desde JQuery a un servlet en el mismo servidor que respondía con datos en formato JSON. Parte del código de la llamada era

$.post("../jqueryJSON.action", {parametroJSON:$.toJSON(person)} ,

El parámetro de la URL de llamada es ../jqueryJSON.action, que es una dirección relativa y está en el propio servidor, que en este caso es localhost.
Esta vez colocaré la dirección de forma explícita (haz acto de fe que es la buena):

<pre>$.post("http://localhost:8084/TwitterPrueba1/jqueryJSON.action", {parametroJSON:$.toJSON(person)}</pre>

La página sigue haciendo peticiones sin problemas porque su URL es: http://localhost:8084/TwitterPrueba1/jqueryExamples/jqueryJSON.html

Ahora imaginemos que el servidor está en otra URL. Como estoy trabajando en local, voy a hacer un truco, que es cambiar localhost por 127.0.0.1. Todos sabemos que son equivalentes, pero a nivel de dominio son diferentes. Entonces tengo ahora

$.post("http://localhost:8084/TwitterPrueba1/jqueryJSON.action", {parametroJSON:$.toJSON(person)}

Recargo la página y si utilizo una consola de Javascript como firebug de ffox o la de Chrome, tengo el siguiente error al hacer la petición.

XMLHttpRequest cannot load http://127.0.0.1:8084/TwitterPrueba1/jqueryJSON.action/jqueryJSON.action. Origin http://localhost:8084 is not allowed by Access-Control-Allow-Origin.

Como se puede comprobar, no es posible hacer una llamada asíncrona a una url fuera del dominio

Por aclarar, se está usando Jquery, que enmascara todo el proceso de uso del objeto XMLHttpRequest, pero internamente se usa.

¿Para qué queremos cargar información de otros servidores? Muchas de las compañías de servicios de Internet publican sus API a través de este formato (Twitter,Flicker, Google, Yahoo…), de modo que podemos utilizar la potencia de sus servicios fácilmente.

Para poder hacer llamadas a otros servidores y cargar la información de otros servicios, se puede utilizar JSONP, que realmente lo que hace es, en vez de hacer una llamada con el objeto XMLHttpRequest (XHR), imita la inserción de un script en nuestra página, que contendrá básicamente la información de vuelta enformato JSON y la llamada a un función de Javascript que hemos creado.

Por tanto lo que tiene que devolver el servidor es algo del tipo

funcion(Respuesta_formato_JSON)

Este texto como respuesta se meterá dentro de un tag script, de modo que se podrá ejecutar sin llamar al objeto XHR. Por tanto en el cliente, se incluye algo parecido a esto:

<script src="URL_que_devuelve_JSONP"></scrip>

Esa URL como digo devolverá texto en formato JSONP que no es otra cosa que JSON “envuelto” entre paréntesis como parámetro de una función.

Entonces, si vamos más allá y vemos cómo se comporta el escript con la URL, quedará algo así:

<script>funcion(datos_JSONP)</script>

Este código será ejecutado normalmente por el navegador como cualquier otro Javascript

¿Cómo se especifica el nombre de la función que “envuelve” el JSON de respuesta?

Sencillamente se añadirá un parámetro más a la llamada de la URL, por ejemplo “callback”. En este parámetro se le indicará al servlet cómo se llama la función del cliente.

Por usar un servicio conocido que emplee JSONP, el buscador Yahoo provee los resultados en este formato:

http://search.yahooapis.com/ImageSearchService/V1/imageSearch?appid=YahooDemo&output=json&query=pepe&callback=MIFUNCION

Como se puede comprobar, devuelve los respuesta a la búsqueda en Yahoo “pepe” en formato JSON, incluyendo además la función MIFUNCION envolviendo toda la información.

Por lo tanto, en el lado del servidor se debe preparar para devolver la respuesta incluyendo esta función, cuyo nombre proporciona el cliente en Javascript a través de un parámetro.

Modificando el ejemplo de Struts2 en el que se usa Gson, simplemente sería añadir un nuevo parámetro cuyo valor se inyecta, siempre que tengamos el setter preparado:

private String callback;   //Nuevo parámetro

public void setCallback(String callback) {
	this.callback = callback;
}

El otro cambio que hay que hacer es la salida, envolviendo la cadena de salida en formato JSON con el nombre de la función que ha indicado el cliente:

salidaFormatoJSON = callback + "(" + salidaFormatoJSON + ")";

El resultado final de la acción de Struts2 que sirve el código en formato JSONP usando Gson es:

imports...

public class jqueryJSONP extends ActionSupport implements
        ServletRequestAware, ServletResponseAware {

    private HttpServletRequest request;
    private HttpServletResponse response;

    private String parametroJSON;
    private String callback;   //Nuevo parámetro

    public jqueryJSONP() {
    }

    public String execute() throws Exception {

        System.out.println("Parámetro de llamada:" + parametroJSON);

        Gson gson = new Gson();
        ServletOutputStream sos= response.getOutputStream();

        Persona persona=gson.fromJson(parametroJSON, Persona.class);
        System.out.println("Persona Recibida. Nombre:" + persona.nombre + ";Edad:" + persona.edad);

        //1. Conversión de un ArrayList de Objetos a JSON
        ArrayList prueba1Array = new ArrayList();
        for (int i = 0; i &lt; persona.edad; i++) {
            prueba1Array.add(new Persona("Nombre_" + i, ((int)(Math.random()*100))));
        }
        String salidaFormatoJSON = gson.toJson(prueba1Array);

        salidaFormatoJSON = callback + "(" + salidaFormatoJSON + ")";
        sos.print(salidaFormatoJSON);
        System.out.println("Salida:" + salidaFormatoJSON);

        return null;

    }

    public void setServletRequest(HttpServletRequest hsr) {
        this.request = hsr;
    }

    public void setServletResponse(HttpServletResponse hsr) {
        this.response = hsr;
    }

    public String getParametroJSON() {
        return parametroJSON;
    }

    public void setParametroJSON(String parametroJSON) {
        this.parametroJSON = parametroJSON;
    }

    public void setCallback(String callback) {
        this.callback = callback;
    }

    //Inner class
    private static class Persona {

        String nombre;
        int edad;

        //Necesario para el método fromJson
        public Persona() {
        }

        public Persona(String n, int e) {
            nombre = n;
            edad = e;
        }
    }
}

De este modo hemos creado una acción en Struts2 que puede ser llamada desde cualquier dominio usando JSONP. Es importante tener en cuenta que cuando publicamos algo con JSONP es accesible por cualquier cliente, de modo que debe ser información NO SENSIBLE.

Ahora toca el trabajo en el lado del cliente. Vamos a utilizar el ejemplo modificado que se empleó en el post de explicación de Json desde Jquery un poco modificado.

La llamada al servidor deberá ser de tipo GET, ya que no es una llamadd XHR, sino que es una llamada para hacer un include. Más información en http://www.markhneedham.com/blog/2009/08/27/jquery-post-jsonp-and-cross-domain-requests/

Por tanto vamos utilizar la función $.get de jQuery para recoger la información en formato JSONP con el añadido de la llamada a la función. El procedimiento es muy parecido, sólo que en esta ocasión se construirá la URL incluyendo los parámetros, tal y como se hace con Get

En este caso el código Javascript envía al servidor un objeto persona, con nombre y edad. El servidor devolverá otros objetos persona, tantos como edad tenga el individuo indicado. Por ejemplo 10 años, devolverá como respuesta en JSONP un array con 10 individuos.

Los parámetros en la llamada de la función $.getJSON (o $.get, ya que sólo evita poner el tipo “json” al final), debe constuirse en la URL, ya que de otro modo no funciona.

Esto es incorrecto

$.getJSON("http://localhost:8084/TwitterPrueba1/jqueryJSONP.action", {parametroJSON:$.toJSON(person),callback:"?"} ,

La URL debe construirse de forma explícita, concatenando cadenas:

$.getJSON("http://localhost:8084/TwitterPrueba1/jqueryJSONP.action?parametroJSON="+ $.toJSON(person)+"&callback=?" ,

Si se han seguido las indicaciones anteriores, se esperará que el parámetro callback tenga el nombre de la función de vuelta que tiene que incluir el servidor en la respuesta JSONP. La explicación de que sea una interrogación “?” es que JQuery sustituye la interrogación por un valor determinado por él, y será la referencia a la función de resultado que tiene $getJSON.

A continuación se muestra un ejemplo de salida del servidor en formato JSONP. Obsérverse el nombre elegido por jQuery para la función de callback = jsonp1290095678230

jsonp1290095678230([{"nombre":"Nombre_0","edad":31},{"nombre":"Nombre_1","edad":52},{"nombre":"Nombre_2","edad":82},{"nombre":"Nombre_3","edad":19},{"nombre":"Nombre_4","edad":14},{"nombre":"Nombre_5","edad":33},{"nombre":"Nombre_6","edad":86},{"nombre":"Nombre_7","edad":70},{"nombre":"Nombre_8","edad":56},{"nombre":"Nombre_9","edad":37}])

El resto del proceso no difiere de una llamada JSON normal y corriente. Se ejecutará la función de respuesta, que es otro parámetro de getJSON. En este caso esta función rellena, para mostrar un ejemplo, un select con opciones basadas en las “personas” obtenidas, borrando las que hubiera con anterioridad.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script language="javascript" src="jquery/jquery.js"></script>
        <script language="javascript" src="jquery/jquery.json-2.2.js"></script>

        <script language="javascript">
            $(document).ready(function() {

                //Petición AJAX, que envía dos parámetros y recibe la respuesta con JSON
                $("#envioPeticion").click(function(e){
                    e.preventDefault();

                    //Crea el objeto que se va a enviar
                    person = new persona($("#nombre").val(),$("#edad").val());

                    $.getJSON("http://localhost:8084/TwitterPrueba1/jqueryJSONP.action?parametroJSON="+ $.toJSON(person)+"&amp;callback=?" ,
                    function(response){

                        console.log("no hay response");

                        var i = 0;
                        var textoSalida = "";

                        console.log(response);
                        

                        $("#selector> option").remove();


                        for (i=0;i<response.length;i++)
                        {
                            textoSalida = textoSalida + response[i].nombre + " - " + response[i].edad;
                            textoSalida = textoSalida + "<br/>";
                            
                            //Añade además al selector los valores
                            $('<option/>').attr("value",response[i].edad).text(response[i].nombre).appendTo("#selector");
                        }
                        //$("#vueltaServidor").html(textoSalida);    //Pega lo recibido en el div
                    });
                });

             

                //Objeto persona
                function persona (nombre, edad)
                {
                    this.nombre = nombre;
                    this.edad = parseInt(edad);
                }

            });
        </script>
    </head>
    <body>

        <h1>Prueba de JSONP</h1>


        <form>
            <input type ="text" id ="nombre" value ="Luis" />
            <input type ="text" id ="edad" value ="10"/>
            <a href="#" id="envioPeticion">Envía la petición</a>
        </form>

        <hr />
        <h3>Esta es la vuelta del servidor</h3>
        <p>Se recibe un objeto JSON y se dibuja en la pantalla</p>

        <form>
            <select id="selector">
                <option value="pepe">pepe</option>
                <option value="luis">luis</option>
            </select>
        </form>

        <div id ="vueltaServidor" style=""></div>


    </body>
</html>

Mediante la utilización de JSONP se podrá acceder a la información que proveen otros servidores, no solamente el servidor en el que se encuentra la página. Existen multitud de servicios que proveen de datos mediante este sistema, como por ejemplo Yahoo o Twitter.

Algunos enlaces útiles:

JSON en Java utilizando la biblioteca com.google.gson

Como ya se trató en post anteriores, JSON no es más que un formato de transmisión de información que es de especial utilidad cuando interviene algún script escrito en Javascript.

En un principio para la comunicación entre sistemas de diferente naturaleza XML parecía ser un lenguaje adecuado, pero su excesivo formalismo y poca eficiencia en el proceso de parseado provocó que se empezara a utilizar JSON par transmitir los mismos datos. Se puede encontrar un ejemplo en el artículo correspondiente de la wikipedia

Al fin y al cabo, como digo, JSON no es más que una cadena de texto que contiene los valores de los atributos de un objeto. El formato que debe cumplir se puede encontrar en json.org.

En este post centraremos la atención de la transformación de texto en JSON a objeto de Java y viceversa. Esta es una tarea sencilla pero tediosa, ya que hay que escribir código para pasar a texto o parsear el texto, algo que nunca nos gusta a los programadores porque siempre se acaba poniendo condiciones poco elegantes. Aquí un ejemplo de un post anterior, en el que se genera una cadena de texto en formato JSON que contiene un array de objetos

String parametro;
StringBuilder salida = new StringBuilder();
int contador = 0;

while (enumeration.hasMoreElements()) {
	contador++;
	parametro = enumeration.nextElement();
	System.out.println("Parámetro " + parametro + ": " + request.getParameter(parametro).toString());

	//Escribe a la salida de forma directa
	salida.append("{\"Parametro\":\"" + parametro + "\",\"Valor\":\"" + request.getParameter(parametro).toString() + "\"},");
}

//Quita la coma final poruqe sobra al ser un array
salida.deleteCharAt(salida.length()-1);

if (contador &gt; 1) {
	salida.insert(0, "[");
	salida.append("]");
}

System.out.println("Salida:" + salida);

Afortundamente existen librerías en java para llevar a cabo esta labor con el esfuerzo mínimo. En la web json.org se pueden ver librerías para todos los lenguajes.

En este caso se va a utilizar la librería gson de Google, que como todos los proyectos tiene su documentación propia. En este ejemplo se da una muestra básica de su uso, para información más detallada conviene visitar la documentación del sitio oficual.

Aquí muestro un ejemplo sencillo de uso con una inner class llamada Persona, que está al final del código. Esta clase solamente tiene dos atributos, nombre y edad, que será la información que se incluye en el texto en formato JSON. Los pasos del código son los siguientes:

  1. Paso de Objeto Java a JSON
    1. Creación del objeto gson de la clase Gson, que contiene los métodos para realizar las conversiones.
    2. Creación de un ArrayList de 3 objetos Persona de prueba, que serán convertidos a JSON.
    3. Obtención de la equivalencia en JSON del array de personas mediante gson.toJson(prueba1Array);
  2. Paso de JSON a Objeto Java
    1. Definición de una cadena de texto que contiene los atributos de un objeto persona según la especificación JSON
    2. Obtención del objeto equivalente de Java mediante gson.fromJson(personaEnJson, Persona.class). Se debe indicar en el segundo parámetro la clase del Objeto, en este caso Persona.class, lo que obliga a disponer de un constructor sin parámetros para dicha clase

El código fuente es el siguiente:

import com.google.gson.Gson;
import java.util.ArrayList;

/**
 *
 * @author Alberto Moratilla alberto.moratilla@gmail.com
 */
public class pruebaGson {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        pruebaGson pgs = new pruebaGson();
        Gson gson = new Gson(); //Objeto que lleva a cabo las operaciones de transformación


        //1. Conversión de un ArrayList de Objetos a JSON
        ArrayList prueba1Array = new ArrayList();
        for (int i = 0; i &lt; 3; i++) {
            prueba1Array.add(new Persona(&quot;Nombre_&quot; + i, i));
        }

        String salidaFormatoJSON = gson.toJson(prueba1Array);
        System.out.println(&quot;Conversión a JSON:&quot; + salidaFormatoJSON);

        //2. Paso de JSON a objeto predefinido
        String personaEnJson = &quot;{\&quot;nombre\&quot;:\&quot;Manolo\&quot;,\&quot;edad\&quot;:32}&quot;;
        Persona personaFromJson = gson.fromJson(personaEnJson, Persona.class);

        System.out.println(&quot;Persona Cargada desde JSON. Nombre:&quot; + personaFromJson.nombre + &quot;Edad:&quot; + personaFromJson.edad);
    }

    //Inner class
    private static class Persona {

        String nombre;
        int edad;

        //Necesario para el método fromJson
        public Persona() {}

        public Persona(String n, int e) {
            nombre = n;
            edad = e;
        }
    }
}

La salida de dicho programa coincide con las conversiones esperadas:

Conversión a JSON:[{"nombre":"Nombre_0","edad":0},{"nombre":"Nombre_1","edad":1},{"nombre":"Nombre_2","edad":2}]
Persona Cargada desde JSON. Nombre:ManoloEdad:32

Envío de parámetros por Post y lectura de respuesta JSON con JQuery

En este ejemplo se muestra cómo se puede utilizar JQuery para realizar una llamada sencilla de tipo AJAX a una acción de Struts2, que recoge los parámetros de la llamada y devuelve esos mismos parámetros en formato JSON, haciendo una lectura posterior con Javascript.

Por lo tanto hay 2 partes en la arquitectura: un servidor con Struts2 que recibe la petición y un cliente en Javascript que reliza la petición y recibe la respuesta, ayudado de las librerías de JQuery.

En primer lugar se crea una página Web con un formulario, que será el que se utilice para la introducción de la información que se va a enviar al servidor

<form>
	<label for="entrada">Pon lo que quieras:</label>
	<a href="#" id="envioPeticion">Envía la petición</a>
</form>

Como se puede ver, se usará un enlace para desencadenar la acción. Este enlace tiene como id “envioPeticion”.

La cabecera de la página Web, tendrá el correspondiente código de Javascript, usando la librería JQuery

$(document).ready(function() {
	//Petición AJAX, que envía dos parámetros y recibe la respuesta con JSON
	$("#envioPeticion").click(function(e){
		e.preventDefault();
		$.post("../jqueryRate.action", {param1:$("#entrada").val(),param2:$("#entrada").val()+"-dos"} ,
                   function(response){
                        var i = 0;
                        var textoSalida = "";

                        for (i=0;i&lt;response.length;i++)
                        {
                            textoSalida = textoSalida + response[i].nombre + &quot; - &quot; + response[i].edad;
                            textoSalida = textoSalida + &quot;<br />";
                        }
                        alert(textoSalida);
                        $("#vueltaServidor").html(textoSalida);    //Pega lo recibido en el div
                    }, "json");
	});
});

La clave está en la instrucción post de JQuery, que envía los parámetros indicados a la URL, en este caso jqueryRate.action. Existen dos parámetros, el primero de ellos es el texto que el usuario ha introducido y el segundo es el mismo pero añadiendo la cadena “-dos” para utilizar de ejemplo. Obsérvese el formato de los parámetros. Esta información pasará por POST a la petición de la URL indicada.

$.post("../jqueryRate.action", {param1:$("#entrada").val(),param2:$("#entrada").val()+"-dos"} ,

El código del lado del servidor es sencillo: está preparado para recibir la petición por POST con Struts2. Para flexibilizar el ejemplo no se esperan dos atributos en los que se cargan los valores de los parámetros, ya que auque figuran con nombres “param1” y “param2” la acción está preparada para cualquier parámetro, de ahí que se lea directamente del objeto Request. De otro modo, si los parámetros son constantes, se podrían haber hecho sendos atributos en la clase con sus getters y setters para que el interceptor inyectara los valores.

El objetivo de la acción es recibir estos dos parámetros en formato estándar para un pos y devolverlos en formato JSON, sin hacer uso de ninguna librería para su transformación (esto lo dejamos para otro post).

La acción implementa ServletRequestAware, ServletResponseAware para tener acceso a los objetos request y response y poder recibir los valores y escribir en la salida. No tiene vista, puesto que necesitamos escribir como respuesta una cadena de texto en formato JSON, no una página JSP, Velocity o HTML. Esa cadena de texto será la que reciba el cliente de Javascript.

import com.opensymphony.xwork2.ActionSupport;
import java.util.Enumeration;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.interceptor.ServletRequestAware;
import org.apache.struts2.interceptor.ServletResponseAware;

public class jqueryRate extends ActionSupport implements
        ServletRequestAware, ServletResponseAware {

    private HttpServletRequest request;
    private HttpServletResponse response;
    private ServletOutputStream sos;

    public jqueryRate() {
    }

    public String execute() throws Exception {

        sos = response.getOutputStream();

        Enumeration enumeration = request.getParameterNames();
        String parametro;

        StringBuilder salida = new StringBuilder();

         //Escritura en formato JSON del array
        int contador = 0;
        while (enumeration.hasMoreElements()) {
            contador++;
            parametro = enumeration.nextElement();
            salida.append("{\"Parametro\":\"" + parametro + "\",\"Valor\":\"" + request.getParameter(parametro).toString() + "\"},");
         }
        //Quita la coma final
        salida.deleteCharAt(salida.length() - 1);

        if (contador &gt; 1) {
            salida.insert(0, "[");
            salida.append("]");
        }

        //Imprime el resultado
        sos.print(salida.toString());

        return null;    //Devuelve NULL para no tener vista

    }

    public void setServletRequest(HttpServletRequest hsr) {
        this.request = hsr;
    }

    public void setServletResponse(HttpServletResponse hsr) {
        this.response = hsr;
    }
}

La salida de esta acción no tendrá una vista como todas las acciones (de ahí que el return del execute sea null), sino que escribe un texto plano de vuelta que estará en formato JSON. En este caso se trata de los parámetros de la llamada, cualesquiera que sea su número. En este caso son dos:

[{"Parametro":"param2","Valor":"pepe-dos"},{"Parametro":"param1","Valor":"pepe"}]

Esta cadena de texto es leída como parámetro de la función de post de JQuery.

Existen dos modos de tratar la respueta de JSON

  • Mediante la función eval se convierte este objeto en formato JSON a objetos de Javascript. MEdiante su manipulación con el lenguaje Javascript y JQuery se pintan en una capa cuyo id es “vueltaServidor”
  • O mediante el último parámetro del $.post, es decir “json”, de modo que ya se encargan JQuery de dar en “response” un objeto de Javascript, fruto del parsing que ha hecho a la cadena de respuesta. Este método es más seguro y eficiente, por lo que se recomienda su uso.
  •   function(response){
    
    	alert(response.length);
    	var i = 0;
    	var textoSalida = "";
    
    	for (i=0;i&lt;response.length;i++)
    	{
    		textoSalida = textoSalida + response[i].Parametro + &quot; - &quot; + response[i].Valor;
    		textoSalida = textoSalida + &quot;<br />";
    	}
    	alert(textoSalida);
    	$("#vueltaServidor").html(textoSalida);    //Pega lo recibido en el div
    }, "json");
    

    Capa en la que se pinta la cadena de texto textoSalida:

    <div id = "vueltaServidor"></div>

    Resumiendo: hemos hecho una llamada a una acción de Struts2 mediante JQuery y su función POST de AJAX, adjuntando mediante POST dos parámetros estándar (no JSON), uno escrito en una caja de texto y otro derivado de éste por fines didácticos. La acción ha recuperado estos parámetros y los ha devuelto en formato JSON, que mediante eval de Javascript hemos transformado en un array de objetos y se han pintado en una capa DIV. Todo ello haciendo uso de la funcionalidad de llamadas asíncronas de AJAX.