viernes, 28 de octubre de 2011

¡Hola Planeta Código!

Planeta Código
Últimamente las entradas que estoy escribiendo en el blog están más orientadas a la programación entorno a la plataforma Java que a GNU/Linux aunque siempre relacionadas con el software libre o código abierto. Por ello he incluido mi blog en el agregador de Planeta Código más orientado a temas de programación.

Al igual que cuando incluí mi blog en Planeta Linux el objetivo es llegar a más lectores y si es posible recibir más visitas y comentarios en el blog. Para los que no conozcan mi blog podeís visitarlo en http://elblogdepicodev.blogspot.com. La temática del mismo es el software libre, linux, la programación y cualquier cosa que tenga que ver con la tecnología o la informática.

Algunas de las últimas entradas que he escrito en él son:

Motivos para elegir el framework Apache Tapestry
Peticiones Ajax en Tapestry (II)
Peticiones Ajax en Tapestry
Seleccionar el lenguaje (locale) según el dominio en Apache Tapestry
Usar Apache Tapestry 5 con Groovy (u otros lenguajes de la JVM)
Componente AjaxSpinner para Tapestry 5

Ejemplo sencillo con JavaCC de un analizador léxico y sintáctico
Configuración de entorno en Java con ayuda de Groovy
Comparar cadenas en Java ignorando acentos
Integración continua con Jenkins
Internacionalización a lenguajes con diferentes formas plurales en Java
Hola mundo con Symfony 2 y Cherokee
Elegir herramientas para un proyecto Java

Espero que a alguno os resulten interesantes, si es así estáis invitados a suscribiros al feed de mi blog.

viernes, 21 de octubre de 2011

Peticiones Ajax en Tapestry (II)

Apache Tapestry
Como comentaba en la entrada Peticiones Ajax en Tapestry para hacerlas no es necesaria ni una sola linea de javascript. En la entrada Peticiones Ajax en Tapestry expliqué como hacer una petición Ajax que actualizaba un fragmento de una página y como hacer un petición Ajax que devolviese datos en Json para ser procesados en el cliente.

Pero el soporte para las peticiones Ajax de Tapestry no se queda en esos dos casos. Otro escenario habitual en las páginas es la necesidad de actualizar más de una región de una misma página con una única petición. Para ello Tapestry proporciona la clase MultiZoneUpdate (en la versión 5.3 será desaconsejado su uso en favor del uso de AjaxResponseRenderer) que será el objeto que deberemos devolver en el método del evento que atiende la petición. Este objeto contendrá las referencias de las zonas que queremos actualzar en la página y los nuevos contenidos a mostrar en ellas, Tapestry se encargará en el cliente de actualizarlas automaticamente por nosotros sin que tengamos que hacer nada más.

Veámoslo con un ejemplo.

<t:zone t:id="usuarioZone" update="show">
    <t:if test="service.usuario">
        <span>${service.usuario.login} (<a t:id="cerrarSesion" t:type="actionlink">Cerrar sesion</a>)</span>
        <p:else><span>Invitado (<a t:type="pagelink" page="index">Iniciar sesión/Registrarse</a>)</span></p:else>
 </t:if>
</t:zone>
...
<t:zone t:id="formularioZone" update="show">
 <t:if test="!service.usuario">
  <t:form t:id="usuarioRegistradoForm" zone="datosZone">
   <t:html5textfield type="email" value="urForm.usuario" validate="required" placeholder="Usuario"/>
   <t:html5textfield type="password" value="urForm.contrasena" validate="required" placeholder="Contraseña"/><br/>    
   <t:submit t:id="usuarioRegistradoSubmit" value="Iniciar sesión" class="btn"/>
  </t:form>
 </t:if>

 <t:if test="service.usuario">
  <t:form t:id="cerrarSesionUsuarioRegistradoForm">
   ${service.usuario.login}<br/>
   <t:submit t:id="cerrarSesionUsuarioRegistradoSubmit" value="Cambiar de usuario" class="btn"/>
  </t:form>
 </t:if>
</t:zone>

Object onSubmitFromUsuarioRegistradoForm() throws IOException {
// Registrar al usuario (service.getUsuario() devolverá el objeto que representa al usuario)
...
 if (!service.getRequestGlobals().getRequest().isXHR()) {
  return null;
 }

 MultiZoneUpdate mzu = new MultiZoneUpdate("formularioZone", formularioZone.getBody()).add("usuarioZone", sesionZone.getBody());;

 return mzu;
}

En el ejemplo hay definidas dos zonas (usuarioZone, formularioZone) que son los componentes definidos con <t:zone>. Las zonas representan fragmentos de página cuyo contenido puede ser actualizado por una petición Ajax. Cuando se envía el formuario formularioZone se llama al método onSubmitFromUsuarioRegistradoForm en el cual comprobamos si la petición es una petición Ajax (service.getRequestGlobals().getRequest().isXHR()) si no lo es se actualizará toda la página pero todo seguirá funcionando, si lo es se devuelve un objeto MultiZoneUpdate y se actualizarán las zonas formularioZone y usuarioZone con el contenido de los propios componentes formularioZone y sesionZone aunque podríamos actualizarlas con el contenidos de otros componentes. Los eventos lanzados por Tapestry siguen por defecto una convención que es on[Evento]From[Componente] aunque podrímos darle el nombre que queramos si utilizamos anotaciones.

En Tapestry pasar de una aplicación no Ajax a una Ajax es muy sencillo basta insertar las zonas y en los métodos de los eventos devolver las zonas y contenidos con las que las queremos actualizar. Otro punto importante a destacar y que contribuye a facilitar la adición del soporte Ajax es que el contenido con el que se actualizan las zonas no lo tenemos que saparar en archivos individuales ni supondrá una reestructuración drástica en los archivos del proyecto como tendríamos que hacer en otros frameworks. También a destacar es que esta funcionalidad de actualizar dos fragmentos o zonas de una página no está presente en otros frameworks de por sí donde normalmente cada petición Ajax actualiza un único fragmento. El soporte en Tapestry para trabajar con Ajax es ¡sencillo y excelente!, de lo mejor que hay.

Nota: A partir de la versión 5.3 la clase MultiZoneUpdate ha sido marcada como obsoleta y la nueva clase y método a utilizar es ajaxResponseRenderer.addRender.

Referencia:
Peticiones Ajax en Tapestry
Documentación sobre Apache Tapesty

sábado, 15 de octubre de 2011

Ejemplo sencillo con JavaCC de un analizador léxico y sintáctico

Java
Hay ocasiones en que necesitamos procesar una determinada expresión, por ejemplo para hacer una búsqueda por una serie de criterios obtenidos de la misma. La forma habitual es hacerlo creando un algoritmo específico más o menos complejo según lo sea la expresión con varios splits, expresiones regulares, condiciones, bucles, etc..., que normalmente resulta en código ofuscado difícil de desarrollar, mantener, entender lo que hace y poco flexible ante cambios. Esta es la primera opción que se nos ocurre pero no es la mejor forma de hacerlo como veremos en esta entrada.

Cuando nos enfrentamos a un problema de procesar una expresión debemos tener en cuenta primeramente dos cosas: cual es su léxico (las palabras que lo forman) y su sintaxis (las reglas que definen el orden del léxico). Más tarde por otra parte para procesar la expresión necesitaremos de acciones léxicas y sintácticas que es código que se ejecutará para realizar las tareas que necesitemos al procesar la expresión.

Para facilitar la tarea existen los compiladores como resultado de la invención de los primeros lenguajes y aunque parecen algo complejo de hacer no lo son tanto como desarrollar un algoritmo específico. JavaCC es una herramienta que nos permite definir el léxico de una expresión o lenguaje, la sintaxis del mismo y las acciones léxicas y sintácticas generando posteriormente con la definción de estas cosas una serie de archivos .java con el código fuente de un analizador léxico, sintáctico y otra serie de archivos .java de utilidad para los mismos.

Supongamos que tenemos una aplicación en la que el usuario tiene una caja de búsqueda en la que puede introducir una serie de palabras separadas por espacios, en la que también puede agrupar varias palabas rodeándolas con " y también puede introducir fechas en varios formatos y con distintos separadores para el día, mes y año pudiendo especificar día, mes y año, solo mes y año, solo el mes o solo el año, por ejemplo dd.MMM.yyyy, dd/MMMM/yyyy, MMMM-dd-yyyy, MMM.yyyy, MMM, yyyy, ... Para complicarlo aún más los meses pueden estar en diferentes idiomas. Un ejemplo de expresión podría ser: «"real madrid" enero.2012 febrero.2012 fútbol» en la que intenta buscar elementos relacionados con el real madrid y fútbol y en los meses de enero o febrero de 2012.

Veamos el código fuente de nuestro pequeño compilador cuya misión sera interpretar la expresión de la mejor de las formas y devolver un objeto org.hibernate.criterion.Criterion con el que podremos hacer una búsqueda en Hibernate según los criterios de la expresión. El compilador está dividido en varias partes:

PARSER_BEGIN y PARSE_END: define el nombre de nuestro analizador e incluye métodos de utilidad (en perfecto código Java) que será incluidos en el analizador sintático sin modificar y que podremos usar desde las acciones sintácticas. Los métodos importantes de esta primera parte son los constructores (JavaCC inserta unos pero como vemos podemos definir más), el método main, buildCriterionTermino y buildCriterionFecha que construirán un Criterion cuando el analizador sintáctico detecte un término o fecha respectivamente, la misión principal de nuestro compilador. Estos métodos no tienen mayor complicación son puro código Java. (en azul).

SKIP y TOKEN: esta parte es la que define el analizador léxico con las palabras de nuestra expresión o lenguaje. Ahí están la forma de las fechas, los términos, el día, mes y año, los separadores. Básicamente son una forma de expresiones regulares para definir cada uno de ellos (en morado).

procesarQuery, procesar, termino, fecha: Son propiamente los métodos del analizador sintáctico y van a definir la sintáxis de nuestro lenguaje. procesar contiene una de las partes más importantes ya que es el punto de partida, va cogiendo los tokens proporcionados por el analizador léxico (que genera JavaCC) y determina si es una fecha, término o algo desconocido. Según lo detectado se ejecuta el bloque de código posterior que va entre {} y que constituye una acción sintáctica. Como se ve la acción sintáctica es perfecto código Java y puede usar las variables definidas en el bloque procesar como ct, ft y r. Después de procesar todos los términos de la expresión se ejecuta otra acción sintáctica que agrupa todos los Criterion recogidos en ct, cf en uno solo y que será lo que devuelve el analizador (en verde).

En la acción sintáctica de termino tenemos que tener en cuenta que el término puede ser un mes (enero, ...) por lo que se intenta procesar como una fecha con buildCriterionFecha y si devuelve algo es que se trataba de un mes sino se procesará como un termino con buildCriterionTermino.

¿Por qué se trata un elemento DESCONOCIDO? Porque sino el analizador sintáctico daría un excepción al no saber lo que es, teminaría y no devolvería nada. De esta forma conseguimos que si una expresión que no se entiende se ignore y se devuelvan al menos el resto de expresiones en el Criterion. Este será el caso de un término fecha mal expresado como «01.enero/2012» donde mezcla diferentes separadores en la misma fecha. Tal como están definidos los tokens, el analizador léxico no sabría que es.

Y eso es lo principal de nuestro compilador. No es tan complicado hacer uno como podría parecer a priori, sin duda mucho más fácil que hacer un algoritmo específico para ello incluso para una expresión tan simple como la tratada, ahora imagínate una que pueda ser como «(<expr> or (<expr>and (<expr>or <expr>) or <expr>))».

Esta es una de esas herramientas muy útilies y con la cual sabiendo usarla o al menos tener conocimiento de ella nos puede ahorrar mucho tiempo y conseguir hacer las cosas mejor. Además y dado que lo que genera como resultado son una serie de archivos .java podremos utilizarlos en cualquier entorno, como en alguna administración pública cuyo nombre no citaré aquí y en la que no esta permitido usar librerías no homologadas por ellos, dado que se trata de código fuente y que no tiene dependencias sobre otras librerías no tendremos ningún problema en usar JavaCC en casos como este.

Para compilar el compilador podemos hacerlo con ant con la siguiente tarea (también podemos utilizar los propios comandos de JavaCC):


<javacc target="src/main/java/com/blogspot/elblogdepicodev/jj/Buscador.jj"
 outputdirectory="src/main/java/com/blogspot/elblogdepicodev/jj/buscador"
    javacchome="/home/[user]/javacc-5.0"/>

Los archivos generados serían:

  • Buscador.java
  • BuscadorConstants.java
  • BuscadorTokenManager.java
  • ParseException.java
  • SimpleCharStream.java
  • Token.java
  • TokenMgrError.java


// Buscador.jj
options {
 STATIC = false;
}

PARSER_BEGIN(Buscador)
package com.blogspot.elblogdepicodev.jj.buscador;

import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Restrictions;
import org.hibernate.type.IntegerType;
import org.hibernate.type.Type;

public class Buscador {
 private static final String[] FORMATOS_FECHAS = new String[] { 
  "d/MMM/yyyy", "d/MMMM/yyyy", "MMM/d/yyyy", "MMMM/d/yyyy", "MMM/yyyy", "MMMM/yyyy",
  "d.MMM.yyyy", "d.MMMM.yyyy", "MMM.d.yyyy", "MMMM.d.yyyy", "MMM.yyyy", "MMMM.yyyy",
  "d-MMM-yyyy", "d-MMMM-yyyy", "MMM-d-yyyy", "MMMM-d-yyyy", "MMM-yyyy", "MMMM-yyyy",
  "MMM", "MMMM", "yyyy" 
 };

 private Locale locale;

 public Buscador(Locale locale) {
  this(new StringReader(""), locale);
 }
 
    public Buscador(InputStream is, String encoding, Locale locale) {
  this(is, encoding);
                this.locale = locale;
 }
 
 public Buscador(Reader r, Locale locale) {
  this(r);
        this.locale = locale;
 }

    public static void main(String[] args) throws ParseException, TokenMgrError {
     StringBuffer sb = new StringBuffer();
     for(int i = 0; i < args.length; ++i) {
   if (i > 0) {
    sb.append(" ");
      }
   sb.append(args[i]);
     }

     Buscador parser = new Buscador(new Locale("es"));
     Criterion criterion = parser.procesarQuery(sb.toString());
     System.out.println(criterion);
 }
 
 // Utilidades
 private Criterion or(List<Criterion> criterions) {
  Criterion lhs = null;
  for (Criterion criterion : criterions) {
   if (lhs == null) {
    lhs = criterion;
   } else {
    lhs = Restrictions.or(lhs, criterion);
   }
  }
  return lhs;
 }

 private Criterion and(List<criterion> criterions) {
  Criterion lhs = null;
  for (Criterion criterion : criterions) {
   if (lhs == null) {
    lhs = criterion;
   } else {
    lhs = Restrictions.and(lhs, criterion);
   }
  }
  return lhs;
 }
 
 private Criterion buildCriterionTermino(String term) {
  List<Criterion> coincidencias = new ArrayList<Criterion>();
  
  String t = "%" + term + "%";

  coincidencias.add(Restrictions.ilike("nombre", t));
  coincidencias.add(Restrictions.ilike("ciudad", t));
  coincidencias.add(Restrictions.ilike("direccion", t));

  Criterion criterio = or(coincidencias);
  
  return criterio; 
 }
 
 private Criterion buildCriterionFecha(String term) {
  Criterion criterio = null;
  
  for (int i = 0; i < FORMATOS_FECHAS.length; ++i) {
   String formatoFecha = FORMATOS_FECHAS[i];
   SimpleDateFormat sdf = new SimpleDateFormat(formatoFecha, locale);

   // Fecha
   try {
    Date fecha = sdf.parse(term);

    Calendar calendario = Calendar.getInstance(locale);
    calendario.setTime(fecha);

    switch (i) {
    case 0:
    case 1:
    case 2:
    case 3:
    case 6:
    case 7:
    case 8:
    case 9:
    case 12:
    case 13:
    case 14:
    case 15:
     // Día, mes, año
     criterio = Restrictions.sqlRestriction("(day({alias}.fecha) = ? and month({alias}.fecha) = ? and year({alias}.fecha) = ?)", new Object[] {
       calendario.get(Calendar.DAY_OF_MONTH), calendario.get(Calendar.MONTH) + 1, calendario.get(Calendar.YEAR) }, new Type[] { IntegerType.INSTANCE,
       IntegerType.INSTANCE, IntegerType.INSTANCE });
     break;
    case 4:
    case 5:
    case 10:
    case 11:
    case 16:
    case 17:
     // Mes, año
     criterio = Restrictions.sqlRestriction("(month({alias}.fecha) = ? and year({alias}.fecha) = ?)", new Object[] {
       calendario.get(Calendar.MONTH) + 1, calendario.get(Calendar.YEAR) }, new Type[] { IntegerType.INSTANCE, IntegerType.INSTANCE });
     break;
    case 18:
    case 19:
     // Mes
     criterio = Restrictions.sqlRestriction("month({alias}.fecha) = ?", calendario.get(Calendar.MONTH) + 1, IntegerType.INSTANCE);
     break;
    case 20:
     // Año
     criterio = Restrictions.sqlRestriction("year({alias}.fecha) = ?", calendario.get(Calendar.YEAR), IntegerType.INSTANCE);
     break;
    default:
     assert (false);
     break;
    }
   } catch (java.text.ParseException e) {
   }
   
   if (criterio != null) {
    break;
   }
  }

  return criterio;
 }
 
 private class CriterionInfo {
  public boolean isFecha;
  public Criterion criterio; 
 }
}
PARSER_END(Buscador)

SKIP : { " " | "\t" | "\n" | "\r" | "\r\n" }

TOKEN : { < #NO_SKIP : ~[" ", "\t", "\r", "\n"] > }
TOKEN : { < #SEP1 : "/" > }
TOKEN : { < #SEP2 : "." > }
TOKEN : { < #SEP3 : "-" > }
TOKEN : { < #SEP : (<sep1> | <sep2> | <sep3>) > }
TOKEN : { < #LETRA : ~[" ", "\t", "\r", "\n", "/", ".", "-"] > }
TOKEN : { < #NUM : ["0"-"9"] > }
TOKEN : { < #MES : (<letra>)+ > }
TOKEN : { < #DIA : (<num> | <num><num>) > }
TOKEN : { < #ANO : (<num><num><num><num>) > }

TOKEN : { < FECHA : (
 <dia><sep1><mes><sep1><ano> | <mes><sep1><dia><sep1><ano> | <mes><sep1><ano> | 
 <dia><sep2><mes><sep2><ano> | <mes><sep2><dia><sep2><ano> | <mes><sep2><ano> |
 <dia><sep3><mes><sep3><ano> | <mes><sep3><dia><sep3><ano> | <mes><sep3><ano> |
 <num><num><num><num>) > }
TOKEN : { < TERMINO : ("\""(~["\""])+"\"" | (<letra>)+) > }
TOKEN : { < DESCONOCIDO : (<no_skip>)+ > }

Criterion procesarQuery(String query) :
{
 Criterion criterio = null;

 ReInit(new StringReader(query));
}
{
 criterio = procesar()
 {
  return criterio;
 }
}

Criterion procesar() :
{
 List<Criterion> ct = new ArrayList<Criterion<();
 List<Criterion> cf = new ArrayList<Criterion<();

 Criterion criterio = null;
 CriterionInfo criterioInfo = null;
 Token t = null;
}
{
 (
  criterio = fecha()
  {
   if (criterio != null) {
    cf.add(criterio);
   }
   //System.out.println(criterio);
  }
 |
  criterioInfo = termino() 
  {
   criterio = criterioInfo.criterio;
   if (criterio != null) {
    if (criterioInfo.isFecha) {
     cf.add(criterio);
    } else {
     ct.add(criterio);
    }
   }
   //System.out.println(criterio);
  }
 |
  t = <desconocido>
  {
   //System.out.println(t.image);
  }
 )*
 <eof>
 {
  List<Criterion> r = new ArrayList<Criterion>();
  if (!ct.isEmpty()) {
   r.addAll(ct);
  }
  if (!cf.isEmpty()) {
   r.add(or(cf));
  }
  return (r.isEmpty())?null:and(r);
 } 
}

CriterionInfo termino() :
{
 Token t = null;
}
{
 t = <termino>
 { 
  //System.out.println(t.image);
  String term = t.image;
  CriterionInfo ci = new CriterionInfo();
   
  // Comprobar si se trata de un mes
  ci.criterio = buildCriterionFecha(term);
  
  if (ci.criterio != null) {
   ci.isFecha = true;
  } else {
   ci.isFecha = false;

   if (term.startsWith("\"") && term.endsWith("\"")) {
    term = term.substring(1, term.length() - 1);
   }
   ci.criterio = buildCriterionTermino(term);
  } 
  return ci;
 }
}

Criterion fecha() :
{
 Token t = null;
}
{
 t = <fecha>
 { 
  String term = t.image;
  return buildCriterionFecha(term);
 }
}

Referencia:
http://javacc.java.net/
http://www.lpsi.eui.upm.es/webcomp/jgperez/java/IntrodJavaCC.pdf

viernes, 7 de octubre de 2011

Configuración de entorno en Java con ayuda de Groovy

JavaGroovy
Una necesidad habitual en prácticamente todas las aplicaciones y proyectos es tener una forma de poder configurar una aplicación en función del entorno en el que se vaya a ejecutar. Cosas habituales que cambian dependiendo del entorno son la configuración de logging, conexión a la base de datos, tal vez la configuración de hibernate si lo usamos, parámetros, etc... Habitualmente necesitaremos un entorno con su configuración para la máquina en la que desarrolla cada programador del proyecto que será el entorno de desarrollo, también es habitual tener un entorno de pruebas independiente del entorno de cada desarrollador y el entorno de producción que es donde se ejecuta la aplicación.

La configuración del multientorno la podemos la podemos hacer de diferentes formas, mediante archivos de propiedades, con xml u otras formas. Aqui vamos a ver como hacerlo con la ayuda de groovy de una forma sencilla sin tener que pelearnos con procesar la forma del archivo, que con xml puede llegar a ser laborioso y con archivos .properties un tanto limitado en cuanto a posibilidades.

Veámos primero lo que sería el archivo de configuración:

[
    entorno: 'desarrollo',
    
 //
 valores: [
  'valor1', 
  'valor2', 
  'valor3'
 ] as String[],

 // Redefinición para configuración para entornos
    entornos: [
        desarrollo: [
      log4j:      '/cfg/log4j-desarrollo.properties',
      hibernate:  '/cfg/hibernate-desarrollo.cfg.xml',
      quartz:     '/cfg/quartz-desarrollo.properties',
  ],
        pruebas: [
   log4j:      '/cfg/log4j-pruebas.properties',
   hibernate:  '/cfg/hibernate-pruebas.cfg.xml',
   quartz:     '/cfg/quartz-pruebas.properties',
  ],
        produccion: [
   log4j:      '/cfg/log4j-produccion.properties',
   hibernate:  '/cfg/hibernate-produccion.cfg.xml',
   quartz:     '/cfg/quartz-produccion.properties',

   valor: [
    'valor-produccion1', 
    'valor-produccion2', 
    'valor-produccion3'
   ] as String[],
  ]
    ]
]

El archivo de configuración es código groovy que define un mapa con una serie de propiedades y valores. La propiedad entorno indica el entorno que será usado en tiempo de ejecución por la aplicación. La propiedad entornos tiene las propiedades específicas de cada entorno. Las propiedad «valores» está definda de forma global independiente de entorno y es común para los entornos de desarrollo y pruebas pero que se redefine para el entorno de producción.

La clase Entorno nos permitirá procesar el archivo de configuración groovy y la clase Utilidades tiene unos métodos de utilidad para facilitar la tarea.

// Entorno.java
package com.blogspot.elblogdepicodev.misc;

import java.io.InputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Entorno {

 private static Logger logger = LoggerFactory.getLogger(Entorno.class);
 
 public enum Ent {
  DESARROLLO, PRUEBAS, PRODUCCION
 }
 
 public static final String PROP_LOG4J = "log4j";
 public static final String PROP_HIBERNATE = "hibernate";
 public static final String PROP_QUARTZ = "quartz";

 private static Object conf;
 private static Ent entorno;
 private static Object configuracion;
 
 public static void initialize(Object conf) throws Exception {
  initialize(conf, null);
 }
 
 public static void initialize(Object conf, Ent ent) throws Exception {
  Entorno.conf = conf;
  Entorno.entorno = (ent != null)?ent:Ent.valueOf(((String) getPropiedadGlobal("entorno")).toUpperCase());
  Entorno.configuracion = getPropiedadGlobal("entornos." + Entorno.entorno.toString().toLowerCase());
  
  logger.debug("Entorno inicializado (" + ent + ")");
 }
 
 public static Object getPropiedad(String propiedad) {
  try {
   String p = propiedad.replaceAll("\\.", "?.");
   // Ver si es una propiedad del entorno
   Object o = getPropiedadEntorno(p);
   if (o == null) {
    // No es una propiedad del entorno, ver si es una propiedad global
    o = getPropiedadGlobal(p);
   }
   return o;
  } catch (Exception e) {
   throw new RuntimeException(e);
  }
 }
 
 public static String getString(String propiedad) {
  return (String) getPropiedad(propiedad);
 }
 
 public static InputStream getResourceAsStream(String propiedad) {
  return (InputStream) Entorno.class.getResourceAsStream(getString(propiedad));
 }
 
 public static Ent getEntorno() {
  return entorno;
 }
 
 public static boolean isEntorno(Ent e) {
  return entorno.equals(e);
 }
 
 public static boolean isDesarrollo() {
  return isEntorno(Ent.DESARROLLO);
 }
 
 public static boolean isPruebas() {
  return isEntorno(Ent.PRUEBAS);  
 }
 
 public static boolean isProduccion() {
  return isEntorno(Ent.PRODUCCION);
 } 
 
 public static Object getPropiedadGlobal(String propiedad) throws Exception {
  return Utilidades.groovy("conf." + propiedad, Utilidades.map("conf", conf));
 }
 
 public static Object getPropiedadEntorno(String propiedad) throws Exception {
  return Utilidades.groovy("configuracion." + propiedad, Utilidades.map("configuracion", configuracion));
 }
}

// Utilidades.java
package com.blogspot.elblogdepicodev.misc;

import groovy.text.SimpleTemplateEngine;
import groovy.text.TemplateEngine;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Type;
import java.text.MessageFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;

import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.blogspot.elblogdepicodev.misc.Entorno.Ent;

public class Utilidades {

 private static Logger logger = LoggerFactory.getLogger(Utilidades.class);

 private static ScriptEngine engine;

 static {
  ScriptEngineManager manager = new ScriptEngineManager();
  engine = manager.getEngineByName(Constantes.GROOVY_ENGINE_NAME);
 }

 public static void initializeEntorno() throws Exception {
  initializeEntorno(null);
 }

 public static void initializeEntorno(Ent ent) throws Exception {
  StringBuffer sb = new StringBuffer();

  InputStream is = Utilidades.class.getResourceAsStream("/cfg/Configuracion.groovy");
  Reader r = new InputStreamReader(is);

  char[] b = new char[4096];
  int i = r.read(b);
  while (i != -1) {
   sb.append(b, 0, i);
   i = r.read(b);
  }
  Object configuracion = Utilidades.groovy(sb.toString(), Collections.EMPTY_MAP);
  Entorno.initialize(configuracion, ent);
 }

 public static Object groovy(String script, Map bindings) throws ScriptException {
  Bindings b = engine.createBindings();
  if (bindings != null) {
   b.putAll(bindings);
  }
  return engine.eval(script, b);
 }
}

Los métodos importantes de esta clase son el initializeEntorno de clase Utilidades que se encarga de leer el archivo de configuración, evaluarlo como una expresión groovy y pasárselo al método initialize de la clase Entorno. La llamada a initializeEntorno la haremos cuando se arranque la aplicación posiblemente en un ContextLitener si se trata de una aplicación web. Una vez tengamos en entorno inicializado podremos llamar a getPropiedad de Entorno para obtener el valor de una propiedad, este método primeramente buscará entre la propiedades específicas entorno en ejecución y si no existe en él buscará entre las propiedades globales, devolverá lo primero en lo que encuentre algo.

Con estas dos clases tenemos una solución sencilla pero a la vez muy potente de configuración de entornos. Dado que las propiedades las estamos de definiendo con groovy las propiedades devueltas no será solo datos String que luego tendremos que parsear en nuestra aplicación sino que con la ayuda de groovy las propiedades devueltas podrán ser objetos, Double, Long, Date, listas, mapas, etc .... Y esta no es la unica forma de definir el archivo de configuración podríamos hacer que fuese un objeto en vez de un mapa de propiedades y podríamos definir métodos con alguna lógica y llamarlos al obtener una propiedad. ¡Las posibilidades son muchas para estas pocas líneas de código!

Referencia:
Obtener información del entorno de una aplicación web en Java

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