Páginas

viernes, 30 de septiembre de 2011

Peticiones Ajax en Tapestry


Apache Tapestry
Tapestry posee un excelente soporte para trabajar con Ajax incluso llegando al punto de no ser necesario escribir ni una sola línea de javascript para hacer este tipo de peticiones. Aquellos componentes de Tapestry que poseen el parámetro zone (como por ejemplo ActionLink, EventLink o Form, ...) permiten actualizar un fragmento de la página (o varios a la vez) mediante una petición Ajax. Además de utilizar un componte que posea un parámetro zone hay que definir las zonas, mediante el componente Zone, que pueden ser actualizadas.

En el ejemplo Ajax ActionLink de la aplicación demostración Jumpstart puede verse un ejemplo en funcionamiento de actualización de un fragmento de la página, tambien puede verse el código fuente de los elementos implicados.

Una de las cosas que hay que destacar es lo sencillo que es pasar de una aplicación no-ajax a una ajax si esto se ajusta a lo que necesitamos, para ello basta usar los parametros zone de los componentes y definir las zonas en la propia página, hay que notar que no es necesario separar ese contenido de la zonas en otro archivo para devolverlo únicamete cuando se haga la petición ajax todo está en un único archivo y Tapestry se encarga de devolver únicamente el contenido de la zona cuando esta vaya a a ser actualizada en una petición Ajax.

El actualizar fragmentos de una página con el contenido html generado por una zona cubre la mayoría de los casos en los que es necesario trabajar con Ajax, sin embargo, en alguna ocasión podenos necesitar trabajar a más bajo nivel haciendo nosotros mismos la petición Ajax y devolviendo datos en formato json en el servidor que luego procesaremos en el cliente para tratarlos, en el siguiente ejemplo se ve como es. El componente hace una petición ajax sobre el evento onGetColores del mismo al cabo de 5 segundos de cargarse la página en la que es incluido. En el cliente hace un alert para cada uno de los elementos devueltos en el evento.

//Colores.java
package com.blogspot.elblogdepicodev.tapestry.components;

import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.json.JSONArray;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;

@Import(library = { "Colores.js" })
public class Colores {

    @Environmental
    private JavaScriptSupport javaScriptSupport;
    
    @Inject
    private ComponentResources componentResources;

    Object onGetColores() {
 return new JSONArray("Rojo", "Verde", "Azul", "Negro");
    }

    protected void afterRender(MarkupWriter writer) {
 String link = componentResources.createEventLink("getColores").toAbsoluteURI();
        
 JSONObject spec = new JSONObject();
 spec.put("link", link);

        javaScriptSupport.addInitializerCall("colores", spec);
    }
}

// Colores.js
function Colores(spec) {
 this.link = spec.link;

 var sthis = this;
 setTimeout(function() { sthis.getColores(); }, 5000);
}
Colores.prototype.getColores = function() {
 $j.ajax({
  url: this.link,
  success: function(data) {
                    $j.each(data, function(index, color) { 
                        alert(color); 
                    });
                }
 });
}

Tapestry.Initializer.colores = function(spec) {
 new Colores(spec);
}

El javascript del ejemplo utiliza la librería jQuery para hacer la petición Ajax y la referencia a ella con la etiqueta script habría que incluirlo en la página, $j es una variable con una referencia a ella.

Todo realmente sencillo, en la mayoría de los casos no será necesario que trabajemos a bajo nivel con las peticiones Ajax. Otra cosa más que hace que trabajar con Tapestry sea muy agradable.

Referencia:
Documentación sobre Apache Tapestry
Peticiones Ajax en Tapestry (II)
http://tapestry.apache.org/ajax-and-zones.html
http://tapestry.apache.org/javascript.html
http://jumpstart.doublenegative.com.au/home.html
http://jumpstart.doublenegative.com.au/jumpstart/

viernes, 23 de septiembre de 2011

Comparar cadenas en Java ignorando tildes

Java
A veces es necesario comparar cadenas de texto (String) para realizar ordenaciones de elementos. Pero en esta tarea aparentemente sencilla probablemente necesitemos ignorar mayúsculas y minúsculas y las palabras con tildes en el caso del Español y si necesitamos la misma funcionalidad de ordenación en otros idiomas aún se puede complicar más ya que pueden utilizar más caracteres que pueden presentar problemas (ã, ä, å, â, æ, mapa de caracteres). Una solución que se suele aplicar habitualmente es hacer un reemplazo de esos caracteres (á, ã, ä, å, â, ...) para igualar diferentes caracteres de las cadenas (a). Pero para ello hay que conocer todos esos caracteres para cada idioma lo que puede ser un problema. Para facilitar la tarea de ordenar cadenas Java dispone de la clase Collator teniendo en cuenta ciertas reglas del idioma en concreto.

List paises = ...;
Locale locale = ...;

Collator c = Collator.getInstance(locale);
c.setStrength(Collator.PRIMARY);

Collections.sort(paises, new CollatorComparator(c));

Con una clase propia, CollatorComparator, que implementa la interfaz Comparator podemos hacer la ordenación de una lista de cadenas:

import java.util.Comparator;
import java.text.Collator;

public class CollatorComparator implements Comparator {
 
 private Collator collator;

 public CollatorComparator(Collator collator) {
  this.collator = collator;
 }

 public int compare(Object o1, Object o2) {
  return collator.compare(o1, o2);
 }

 public boolean equals(Object obj) {
  return false;
 }
}

Algo importante es la «fuerza» del comprador, que permite ajustar cuales son los caracteres que se consideran diferentes en la comparación. Disponemos de las siguientes posibilidades:
  • PRYMARY: Considera como distintos los distintos caracteres («a» vs «b»)
  • SECONDARY: Considera como distintos diferentes formas acentuadas («a» vs «á»)
  • TERTIARY: Considera como diferentes mayúsculas y minúsculas («a» vs «A»)
  • IDENTICAL: Solo se consideran idénticos («a» vs «a»)
Referencia:
java.util.Comparator
java.text.Collator
java.text.RuleBasedCollator

viernes, 9 de septiembre de 2011

Componente AjaxSpinner para Tapestry 5

Apache Tapestry
Hoy en día las aplicaciones web que hace peticiones de forma asíncrona con AJAX son la mayoría ya que es una técnica que permite obtener únicamente los datos que se necesitan sin tener que hacer una cargar entera de la página web en el navegador. Esta técnica hace que las peticiones al devolver únicamente los datos necesarios (y no toda la página) y los recursos para procesar la petición sean menos, también hace que el resultado de las peticiones sean más pequeños con lo que el tiempo de carga también se reduce al generar menos tŕafico de red, asi mismo el usuario nota un aumento de tiempo de respuesta. También hace que en ciertos casos las aplicaciones sean más fáciles de desarrollar por no tener que tratar en el servidor en cada petición el estado concreto de cada cliente.

Por la naturaleza de las peticiones, asíncronas y ejecutandose en segundo plano, es recomendable mostrar al usuario algún tipo de notificación que informe a este de que hay una petición AJAX en curso de tal forma que pueda saber que se está haciendo algo y que se ha completado.

En el siguiente ejemplo voy a desarrollar un componente para Apache Tapestry 5 que implemente esta funcionalidad de tal forma que siempre que se esté realizando una petición AJAX se muestre un pequeño tooltip del mismo estilo del que existe en gmail o google reader pero con los puntos suspentivos finales animados (de «.» a «..» a «...» a «....» y otra vez al inicio de «.»). En la vesión 5.2 de Tapestry aún se usa prototype y las peticiones AJAX que inicia Tapestry las realiza con esta librería. Por suerte siempre que Prototype inicia una petición o alguna finaliza lanza dos eventos informando de ello. Por lo que nuestra tarea será registrar un par de funciones propias para estos dos eventos con la lógica que queremos implementar.

El código Java del componente es el siguiente, lo importante está en el método afterRender donde se genera el código javascript de inicialización que crea la instancia del objeto javascript AjaxSpinner cuando se carga la página:

package com.blogspot.elblogdepicodev.tapestry.components;

import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;

@Import(stylesheet = { "AjaxSpinner.css" }, library = { "AjaxSpinner.js" })
public class AjaxSpinner {

    @Environmental
    private JavaScriptSupport javaScriptSupport;
    
    @Inject
    private ComponentResources componentResources;

    protected void afterRender(MarkupWriter writer) {
        String id = componentResources.getId();

        JSONObject spec = new JSONObject();
        spec.put("id", id);
        
        javaScriptSupport.addInitializerCall("ajaxSpinner", spec);
    }
}

La plantilla del componente se encarga de generar el html con las capas que van a contener los tooltips, uno para indicar que se está realizando una petición, otro para indicar que aún está en funcionamiento si la petición tarda un rato y otro para indicar que la petición está tardando más de lo normal. Se mostrará una capa u otra dependiendo del tiempo que lleve realizandose. El código de la plantilla AjaxSpinner.tml del componente es:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<t:container xmlns:p="tapestry:parameter" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd" xmlns="http://www.w3.org/1999/xhtml">
 <div t:type="any" t:id="div1" id="${componentResources.id}1" class="ajax-spinner">
  Cargando...
 </div>
 
 <div t:type="any" t:id="div2" id="${componentResources.id}2" class="ajax-spinner">
  Aún en funcionamiento. Cargando...
 </div>
 
 <div t:type="any" t:id="divX" id="${componentResources.id}X" class="ajax-spinner ajax-spinner-tardando">
  Está tardando demasiado...
 </div>
</t:container>

El código javascript (AjaxSpinner.js) para el componente es:

function AjaxSpinner(spec) {
 this.id = spec.id;
 this.t1 = null;
 this.t2 = null;
 this.tX = null;
 this.peZ = null;

 this.d1 = $('#' + this.id + '1');
 this.d2 = $('#' + this.id + '2');
 this.dX = $('#' + this.id + 'X');

 this.d1.css('margin-left', -1 * (this.d1.width() / 2));
 this.d2.css('margin-left', -1 * (this.d2.width() / 2));
 this.dX.css('margin-left', -1 * (this.dX.width() / 2));

 var _this = this;

 $(document).ajaxStart(function() {
  _this.clear();
  _this.t1 = setTimeout(function() {
   _this.timeout1();
  }, 500);
  _this.t2 = setTimeout(function() {
   _this.timeout2();
  }, 15000);
  _this.tX = setTimeout(function() {
   _this.timeoutX();
  }, 45000);
  _this.peZ = setInterval(function() { 
   _this.timeoutZ();
  }, 1000);
 }).ajaxStop(function() {
  _this.clear();
 });
}

AjaxSpinner.prototype.timeout1 = function() {
 this.d1.show();
 this.d2.hide();
 this.dX.hide();
}

AjaxSpinner.prototype.timeout2 = function() {
 this.d1.hide();
 this.d2.show();
 this.dX.hide();
}

AjaxSpinner.prototype.timeoutX = function() {
 this.d1.hide();
 this.d2.hide();
 this.dX.show();
}

AjaxSpinner.prototype.timeoutZ = function() {
 var e = $('div[id^=' + this.id + ']:visible');
 var t = e.html().trim();
 if (t.endsWith('....')) {
  t = t.substring(0, t.length - 3);
 } else {
  t += '.';
 }
 e.html(t);
}

AjaxSpinner.prototype.clear = function() {
 if (this.t1)
  clearTimeout(this.t1);
 if (this.t2)
  clearTimeout(this.t2);
 if (this.tX)
  clearTimeout(this.tX);
 if (this.peZ)
  clearTimeout(this.peZ);
 this.d1.hide();
 this.d2.hide();
 this.dX.hide();
}

Tapestry.Initializer.ajaxSpinner = function(spec) {
 new AjaxSpinner(spec);
}

Y los estilos CSS (AjaxSpinner.css) para dar a los tooltips el mismo aspecto que los de gmail y google reader:

div.ajax-spinner {
 display: none;
 position: fixed;
 padding: 5px;
 top: 0px;
 left: 50%;
 text-align: center;
 background-color: #fff1a8;
 border-bottom-left-radius: 5px;
 border-bottom-right-radius: 5px;
 z-index: 9999;
 vertical-align: middle;
}

div.ajax-spinner-tardando {
 background-color: #f8baba;
}

Una vez desarrollado el componente lo único que tendríamos que hacer es hacer uso del componente en la plantilla de la página donde queramos usarlo, por ejemplo:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd" xmlns:p="tapestry:parameter">
<head>
    <title>${titulo}</title>

    <link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Ubuntu:regular,italic,bold,bolditalic"></link>
    <link t:type="any" rel="shortcut icon" href="context:/images/favicon.ico"></link>
    <meta name="description" content="${message:descripcion-app}"/>
    <meta name="keywords" content="${message:keywords-app}"/>
    
    <t:extension-point id="head">
    </t:extension-point>
</head>
<body>
    <t:ajaxspinner t:id="principal"/>

    <div class="body">
        <t:extension-point id="cabecera">
        </t:extension-point>
        
        <t:extension-point id="menu">
        </t:extension-point>
        
        <t:extension-point id="cuerpo">
        </t:extension-point>

        <t:extension-point id="pie">
        </t:extension-point>
    </div>
</body>

El resultado cuando se hace un petición AJAX que tarda bastante tiempo es:

Tal vez te preguntes... ¿solo hace falta poner <t:ajaxspinner t:id="principal"/> para usarlo? ¿y el archivo .js con el código javascript no hay que meterlo en el head de la plantilla? ¿y la instanciación del AjaxSpinner en javascript? ¿y el CSS tampoco hay que meterlo en el head? No, no hace falta saber que el componente hace uso de un archivo de javscript ni la hora de estilos que necesita ni saber como se inicializa de eso ya tiene conocimiento tapestry por la definición del componente (mediante la anotación Import) y se encarga de incluir todo y de hacerlo eficientemente agregando el contenido de los archivos en uno solo. También el propio componente se encarga de generar el código javascript que necesita para inicalizarse.

Esta es una de las grandes características de los componentes tapestry ya que como véis lo único que hace falta es usarlo y pasarle los parametros necesarios (aunque en esta caso no tiene ninguno), no hace falta saber como funciona internamente, los estilos y que javascript necesita. Los componentes de tapestry permite abstraernos enormemente de sus detalles y eso es bueno, muy bueno ¿no crees?.

Referencia:
Documentación sobre Apache Tapestry

viernes, 2 de septiembre de 2011

Integración continua con Jenkins

Jenkins
Jenkins es una herramienta de integración continua (CI) que permite realizar tareas periódicas y automáticamente como generar el archivo del proyecto o ejecutar los casos de prueba. Usar una herramienta de integración continua tiene varios beneficios como detectar problemas de forma temprana haciendo que sean más fáciles de resolver y automatizar procesos repetitivos que nos liberan de tiempo. Todo ello en un entorno independiente de los de desarrollo.

Su instalación y uso es muy sencillo basta con descargar u archivo .war y arrancarlo con:

java -jar jenkins.war

O desplegarlo en un contenedor de aplicaciones como Tomcat ya que es una aplicación web java. Su configuración se puede hacer completamente desde la interfaz de usuario que proporciona. Algunas otras características de Jenkins son:

- Puede proporcionar el conjunto de cambios realizados en un commit al repositorio de control de versiones como CVS/Subversion.
- Proporciona enlaces permanentes para porder utilizarlos en otras herramientas.
- Tiene nitificiones por RSS, correo electrónico y mensajería instantánea.
- Informes de las pruebas JUnit y TestNG.
- Puede distribuir las compilaciones y pruebas entre varias máquinas.
- Y una de sus mejores características, el soporte para plugins que le añaden funcionalidades. Y hay un montón de ellos para diversas tareas.

Jenkins no necesita una base de datos para funcionar lo que simplifica su configuración pero necesita guardar datos y lo hace por defecto en el directorio ~/.jenkins. Para actualizar a una nueva versión de jenkins basta con reeemplazar el archivo jenkins.war por el nuevo. Para hacer una copia de seguridad de los datos o copiarlos a otra máquina basta con copiar el directorio ~/.jenkins.

Nada más arrancar Jenkins y acceder con el navegador a la dirección http://localhost:8080/jenkins veremos la siguiente página:

Vamos a ver como configurar una tarea para sea ejecutada por jenkins cada cierto tiempo y vamos a ver como instalar un plugin, en concreto el plugin de git.

Para crear una tarea que haga el trabajo que queramos pulsaremos el enlace «Crear nueva Tarea», le daremos un nombre a la tarea y seleccionaremos la opción «Crear un proyecto de estilo libre». Al configurar la tarea veremos un montón de opciones pero por suerte de cada una de ellas tiene una «?» con la que podremos obtener ayuda. Algunas importantes son la configuración para que jenkins obtenga el código fuente de la aplicación. El apartado de disparadores de las ejecuciones por una expresión cron o por consulta de repositorio de control de versiones. El apartado de Ejecutar para lanzar la tarea que deseemos. Y las acciones que queramos que haga jenkins despues de ejecutar la tarea como enviar correos electrónicos o ejecutar otros proyectos. En la captura de pantalla he configurado una tarea (o proyecto) para que se ejecute cada minuto lanzando el comando date del sistema (en este caso de un sistema Linux).



Una vez que la tarea (o proyecto) se lanza en la opción Salida de consola podemos ver la salida de la tarea lanzada, muy util si queremos ver que ha sucedido en ella.

Para instalar el plugin de git basta ir al apartado «Administrar Jenkins > Administrar Plugins > Todos los plugins» marcar el de git, pulsar el botón instalar al final de la página y reiniciar jenkins.



En la pantalla de Panel de control podemos obtener en laces a fuentes RSS de los proyectos fallidos, de los más recientes o de todos los trabajos para recibir notificaciones por esta vía.

Usar una herramienta continua como jenkins además de las ventajas que ya de por si aporta con la integración continua es motivador para el equipo de desarrollo ya que en cierta forma le obliga a conseguir que la última ejecución de las tareas sea en todo momento correcto. Es una de esas herramientas imprescindibles a usar en cualquier proyecto.

Referencia:
http://en.wikipedia.org/wiki/Continuous_integration
http://jenkins-ci.org/
https://wiki.jenkins-ci.org/display/JENKINS/Meet+Jenkins
https://wiki.jenkins-ci.org/display/JENKINS/Use+Jenkins
https://wiki.jenkins-ci.org/display/JENKINS/Extend+Jenkins
https://wiki.jenkins-ci.org/display/JENKINS/Containers